aiagent.org logo aiagent.org
💬 Enterprise Human-in-the-Loop

ChatOps Agents: Slack Human-in-the-Loop

Deploying enterprise autonomous agents with interactive human approval gates in Slack. Learn asynchronous pause-and-resume state machines (LangGraph & Temporal), cryptographic approval payloads, and immutable audit logs.

Interface: Slack Bolt SDK & Block Kit
Workflow: Stateful Graph Interruption (HITL)
Security: HMAC Signed Approval Tokens

01 Why Fully Autonomous Agents Fail in Enterprise

Governance

In real enterprises, autonomous agents cannot be granted unchecked authority to delete production databases, issue customer financial refunds, or deploy code to Kubernetes clusters. Human-in-the-Loop (HITL) introduces a dual-control governance model where the agent performs research and drafts the action, but a designated human supervisor clicks Approve or Reject in Slack before irreversible state changes occur.

02 Slack Bolt Agent with Interactive Buttons

Python Implementation

Here is a production Slack Bolt service handling agent task requests and posting interactive Block Kit approval cards:

# Install: pip install slack-bolt langgraph
import os
import hmac
import hashlib
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

def generate_approval_token(task_id: str, secret_key: str) -> str:
    """Generates an HMAC-SHA256 signature to prevent button tampering."""
    return hmac.new(secret_key.encode(), task_id.encode(), hashlib.sha256).hexdigest()

def send_approval_request_to_slack(channel_id: str, task_id: str, action_summary: str):
    token = generate_approval_token(task_id, os.environ.get("APPROVAL_SECRET", "super-secret"))
    
    app.client.chat_postMessage(
        channel=channel_id,
        text=f"⚠️ Human Approval Required: {action_summary}",
        blocks=[
            {
                "type": "section",
                "text": {"type": "mrkdwn", "text": f"*Agent Action Pending Approval*\n>{action_summary}"}
            },
            {
                "type": "actions",
                "elements": [
                    {
                        "type": "button",
                        "text": {"type": "plain_text", "text": "✅ Approve Action"},
                        "style": "primary",
                        "action_id": "agent_action_approve",
                        "value": f"{task_id}:{token}"
                    },
                    {
                        "type": "button",
                        "text": {"type": "plain_text", "text": "❌ Reject"},
                        "style": "danger",
                        "action_id": "agent_action_reject",
                        "value": f"{task_id}:{token}"
                    }
                ]
            }
        ]
    )

@app.action("agent_action_approve")
def handle_approval(ack, body, client):
    ack()
    user_id = body["user"]["id"]
    val = body["actions"][0]["value"]
    task_id, token = val.split(":")
    
    # Verify HMAC token validity
    expected_token = generate_approval_token(task_id, os.environ.get("APPROVAL_SECRET", "super-secret"))
    if token != expected_token:
        client.chat_postMessage(channel=body["channel"]["id"], text="❌ Tampering detected. Invalid token.")
        return

    # Update Slack message to show approved state
    client.chat_update(
        channel=body["channel"]["id"],
        ts=body["message"]["ts"],
        text="Action Approved",
        blocks=[{
            "type": "section",
            "text": {"type": "mrkdwn", "text": f"✅ *Approved by <@{user_id}>* — Resuming autonomous workflow..."}
        }]
    )
    # Resume LangGraph / Temporal workflow execution
    print(f"Resuming Task {task_id} approved by user {user_id}")

if __name__ == "__main__":
    handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
    handler.start()

03 Stateful Graph Interruption (LangGraph Checkpoints)

State Machine

By setting interrupt_before=["execute_critical_tool"] in LangGraph, the agent graph serializes its entire execution state into a PostgreSQL checkpointer and halts. Once the human clicks Approve in Slack, the server fetches the checkpoint by thread ID and executes graph.invoke(None, config) to resume seamlessly where it left off.

Frequently Asked Questions

What happens if the human supervisor never responds to the Slack button? →

Production workflows implement an expiration timer (e.g. 2 hours). If no response is received, the checkpoint transitions to a TIMED_OUT status, releases acquired resource locks, and posts an escalation notification to a secondary on-call rotation channel.

How does this architecture satisfy SOC 2 and ISO 27001 audit controls? →

Every approval event logs the immutable Slack user ID, timestamp, HMAC hash, action diff, and execution status into an append-only audit database (like AWS CloudWatch Logs or BigQuery). Auditors have complete proof that destructive operations were explicitly authorized by verified human engineers.