Skip to main content
Human-in-the-loop (HITL) approval gates let an agent ask for permission before running selected MCP tools. They compose in both single-agent and orchestration mode. Use them for operations that need a human decision before execution, such as production changes or destructive actions. Current behavior:
  • Gates compose in both single-agent mode and for orchestration workers. A single-agent run reports scope.kind = "single"; an orchestration worker reports scope.kind = "worker".
  • Webhook routing works for unattended approvals.
  • Conversational routing works for attended approvals over an open SSE stream. The AURA CLI in HTTP mode is the first attended client.
  • Matching tool calls are blocked until the configured route approves them.
  • Human denials are returned to the model as normal tool feedback, so the agent can explain the denial without treating it as a transport failure.
  • Timeouts, cancellation, and webhook channel failures still fail closed as tool errors.
  • Conversational HITL requires stream=true; non-streaming requests are rejected because approval prompts are delivered over SSE.
  • Approval lifecycle events emit on streaming responses. Webhook emits aura.approval_requested and aura.approval_completed; conversational also emits aura.approval_pending while the tool call is parked.

Configure a webhook gate

Add a top-level [hitl] table and a required [hitl.route] table:
require_approval is a list of glob patterns matched against MCP tool names. When an agent calls a matching tool, Aura requests approval through the configured route before the MCP tool runs. A tool is gated if it matches any pattern in the list, so pattern order does not affect whether a tool is gated. To leave a tool ungated, do not list a pattern that matches it. When more than one pattern matches, the first in config order is reported as origin.matched_pattern in the webhook payload and SSE events; that is the only effect of ordering. The request_approval tool is never matched by these globs. It is excluded from the gate so the agent can ask for approval without triggering the gate itself. timeout_secs defaults to 300 for webhooks. If the webhook does not return a decision before the timeout, the tool does not run.

Orchestration example

The gate is added before the worker’s MCP tools execute. A denied call returns a successful blocked tool result to the worker:
The worker sees that message and can explain the denial to the user. The MCP tool itself is not called.

Webhook request

Aura sends a JSON request to the configured webhook URL. The request uses a flat wire shape with kind tags for scope and origin:
Fields:

Webhook response

Approve the tool call:
Deny the tool call, optionally with a reason:
Response behavior:

SSE lifecycle events

Approval routes emit lifecycle events on streaming responses. These events are emitted even when AURA_CUSTOM_EVENTS=false because clients may need to react to approval state.
aura.approval_requested includes decision_id, tool_name, origin, and scope. aura.approval_pending is emitted only by the conversational route and contains the attended prompt payload that an Aura-aware client renders before posting a decision. aura.approval_completed includes decision_id, terminal outcome, duration_ms, and scope. Outcome kinds are approved, denied, timed_out, cancelled, and errored; errored means the approval channel failed before a human decision was obtained.

Conversational route

Use conversational routing when the approver is present on the chat stream. The server parks the worker tool call, sends aura.approval_pending over SSE, and waits for a decision on the approval ingress endpoint:
The chat request must set stream=true. Aura rejects non-streaming requests for conversational HITL because there is no channel for the pending approval prompt. An attended client resolves a pending approval by POSTing the same decision shape as a webhook response:
The AURA CLI supports this flow in HTTP mode. It renders aura.approval_pending, prompts for approve/deny, and POSTs the decision back to the server. One-shot CLI mode fails loud instead of prompting because it has no interactive approval surface.

Webhook manual smoke test

Start a webhook service that accepts the request shape above and returns an approval response. Then run Aura with an orchestration config that uses:
Use mock_tool (the tool name from the bundled math orchestration example config) so the glob matches a tool the worker actually calls. Put the webhook on a different port than the mock MCP server (9999) to avoid a collision. Ask an orchestration worker to use the gated tool. An approval should let the tool run. A denial with a custom reason should produce a successful blocked tool result containing that reason.

Conversational manual smoke test

Run Aura with an orchestration config that uses mode = "conversational", a route timeout shorter than [orchestration.timeouts].per_call_timeout_secs, and at least one gated worker tool. Connect with the AURA CLI in HTTP mode and send a query that forces the worker to call the gated tool. Expected behavior:
  • The CLI renders an approval prompt from aura.approval_pending.
  • Approving the prompt POSTs to /v1/approvals/{decision_id} and lets the tool run.
  • Denying the prompt POSTs the denial and returns blocked-action feedback to the worker.

Current limitations

  • The webhook route is synchronous. Aura waits for the webhook response during the tool call.
  • Conversational approvals are in-process only. The parked approval must be resolved by the same server process that emitted aura.approval_pending.
  • Durable approval parking and cross-pod resume are not implemented yet.
  • Webhook egress has no built-in authentication layer. Put auth, signing, or network controls in front of the webhook service.