Skip to main content

Human-in-the-Loop AI Agents: How to Keep Humans in Control Without Killing Automation

· 5 min read
sa_1d15a3d8c7aaabd0aaa3a5492759daba

Your agent just decided to refund $12,000, email the customer, and update HubSpot at 2am while you were asleep. Was that a good decision? You will not know until the customer replies confused and finance asks what happened.

Autonomous agents are great until they do something irreversible without you. The fix is not writing longer prompts. It is adding approval gates to actions that mutate state, while letting the rest run untouched.

The Real Problem: Agents Default to Autonomy​

Most agent frameworks default to full execution. Give an agent a tool like send_email or stripe.refund and it calls it the moment its reasoning loop decides to. No pause, no receipt, no rollback.

That works in a demo. It breaks in production when:

  • An agent drafts a plausible SQL migration and executes it against production
  • A support agent offers a discount outside policy because the transcript sounded urgent
  • An ops agent retries a failed webhook 50 times and triggers upstream rate limits

If an agent takes an external action (email, payment, database write), it needs a gate. Internal reasoning, memory writes, and read-only lookups should never wait for human input.

Three Approval Patterns That Work in Practice​

Not every action deserves human attention. We split tool calls into three tiers:

1. Auto-approve (read and internal state)​

  • GET /contacts, query_records, search_memory, read_file
  • Writing to private agent memory or drafting content without publishing
  • No review item created. The platform logs an audit trail and moves on.

2. Gated approval (external and irreversible)​

  • POST /send_email, stripe.charge, hubspot.update, db.migrate
  • The agent proposes the call with its full arguments. The runtime pauses the execution, captures the call as a pending action, and alerts the operator. Only after a human clicks approve does the call execute. The receipt is written to durable storage with the approver ID and returned payload.

3. Escalation (judgment calls)​

  • High-value deals, contract language, or refunds above a specific dollar threshold.
  • The agent routes the decision to a specific human role via the org chart (for example, RevOps lead for refunds over $500) rather than a generic inbox.
graph LR
A[Agent Reasoning] --> B{External action?}
B -->|No| C[Execute + Log Receipt]
B -->|Yes| D{Meets approval policy?}
D -->|No - low risk| C
D -->|Yes| E[Create Pending Action]
E --> F[Human Reviews in Inbox]
F -->|Approved| G[Execute + Write Receipt]
F -->|Rejected| H[Return Rejection to Agent]
H --> I[Agent Replans]

A good starting policy: any tool that mutates state outside the workspace requires approval. You can loosen thresholds later once you trust the agent's baseline performance.

How Approval Routing Works Under the Hood​

When an agent calls adl_proxy_call or an external tool, the runtime intercepts the request before it hits the wire. The call is captured as a pending action with the exact arguments the agent generated. Nothing reaches the external API until a human approves it in the workspace Actions queue.

Once approved, the platform executes the request and injects the result back into the agent's context. The agent never re-runs the tool call, and you get an immutable receipt.

// Agent proposes an external action, but does not send directly
// The runtime captures this as a pending Inbox Action
await adl_proxy_call({
url: "https://api.hubspot.com/crm/v3/objects/contacts",
method: "POST",
headers: '{"Authorization": "secret:hubspot_api_key"}',
body: JSON.stringify({
company: "Acme",
lifecycle_stage: "opportunity"
})
});
// Runtime response while paused:
// { status: "pending_approval", action_id: "ext_abc123",
// message: "Awaiting approval in Inbox -> Actions" }

// After human approves in UI, the execution resumes with the real result:
// { status: "executed", hubspot_id: "12345", receipt_id: "rcpt_789" }

You can query your execution receipts directly:

-- Audit who approved what and when
SELECT action_id, tool, status, approved_by, executed_at
FROM external_action
WHERE status = 'executed'
ORDER BY executed_at DESC
LIMIT 20;

An approval system without receipts is just an unmonitored pause button. You need an audit trail showing who approved the action, the payload sent, and what the remote API returned.

Keeping Approvals From Killing Throughput​

The common objection to human-in-the-loop systems is latency. If every step waits for a human, the agent loses its value. You avoid this by designing around asynchronous execution.

Make approvals asynchronous. The agent should not hang on an active thread waiting for a click. It proposes the mutation, checkpoints its state, and exits. When the approval event fires, the agent resumes execution from memory.

Scope policies narrowly. Gate the three tools that have catastrophic blast radius, not every helper function.

Keep memory durable. Approvals happen hours after the proposal. The agent needs persistent memory to resume without hallucinating previous steps or re-asking the user for context.

Store receipts for subsequent runs. When an agent runs tomorrow, it should be able to query yesterday's approved actions so it does not repeat completed work.

When you set up approval boundaries this way, humans stop acting as manual operators and function as a verification layer. The agent moves fast on internal logic and pauses only when real money or customer data is on the line.