- Gates compose in both single-agent mode and for orchestration workers. A
single-agent run reports
scope.kind = "single"; an orchestration worker reportsscope.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_requestedandaura.approval_completed; conversational also emitsaura.approval_pendingwhile 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
Webhook request
Aura sends a JSON request to the configured webhook URL. The request uses a flat wire shape withkind tags for scope and origin:
Each object in
items describes the tool call awaiting approval:
tool_name— the gated MCP tool being called.arguments— the tool call arguments.tool_call_intent— Optional. A short natural-language rationale the agent may supply for why the tool was called, giving the human approver more context. Aura does not act on it automatically. Because it is agent-generated free text, a webhook receiver that displays it to a human approver should treat it as untrusted and escape or sanitize it like any other model output.- Omitted entirely from the payload (not
null, not an empty string) when the agent supplies no rationale, so its absence does not change the protocolversion.
tool_call_intent appears only in the webhook payload’s items[]. It is not
included in the aura.approval_requested, aura.approval_pending, or
aura.approval_completed SSE events, or in the conversational route’s pending
prompt.
Webhook response
Approve the tool call:Forward headers to the webhook
The webhook route accepts two optional[hitl.route] keys (valid only when
mode = "webhook") that add headers to every approval POST:
headers (with {{ env.VAR }}
interpolation) to authenticate Aura to your own approval service; use
headers_from_request to forward a header from the incoming API request (for
example a request or correlation id) onto the approval POST.
headers— a static map; values are sent verbatim on every approval POST.headers_from_request— maps outgoing header name → incoming request header name; the incoming lookup is case-insensitive.- Precedence: when the mapped incoming request header is present, the forwarded
value overrides any static
headersvalue for the same (case-normalized) name. - When the incoming header is absent, the static
headersvalue remains as a fallback. - Aura sends all resolved outgoing header names in lowercase.
- Both keys are optional; when both are absent, the approval POST is unchanged.
- Invalid header names or values are skipped with a warning and never abort the request.
- CLI-context caveat: outside the web-server chat endpoint there is no inbound
HTTP request. In CLI standalone mode,
headers_from_requestresolves only against headers you supply via theAURA_EXTRA_HEADERSenvironment variable (empty by default, so nothing is forwarded unless you set it). Staticheadersalways apply.
SSE lifecycle events
Approval routes emit lifecycle events on streaming responses. These events are emitted even whenAURA_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.
These approval events do not carry tool_call_intent; that field is part of the
webhook payload only.
Aura also stamps this decision_id on the execute_tool span for every gated
call, regardless of outcome. This lets you
correlate an approval with the authorized action directly in the trace. See
Tracing & Span Layout for the full list
of span attributes, including decision_id.
Conversational route
Use conversational routing when the approver is present on the chat stream. The server parks the worker tool call, sendsaura.approval_pending over SSE, and
waits for a decision on the approval ingress endpoint:
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:
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: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 usesmode = "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.
- With the default in-memory session store, conversational approvals are
single-instance, so only the server process that emitted
aura.approval_pendingcan resolve them. - You can resume a parked approval on a different pod by configuring the optional
Redis or Valkey session store. A
POST /v1/approvals/{id}request that lands on any instance then resolves an approval parked on another. See Session Store. - Webhook egress has no built-in authentication layer. Put auth, signing, or network controls in front of the webhook service.

