Human Approval
Some actions are too consequential to allow automatically and too rare to block outright — wiring a payment, deleting a table, deploying to production. For these, Strathon pauses the agent and waits for a human to approve or deny. This is the human-in-the-loop control the EU AI Act Article 14 calls for, applied at the tool-call boundary.
How it works
Approval is a policy action. Set a policy's action to require_approval, and
any tool call that matches its CEL expression pauses instead of executing. The
SDK polls the receiver and blocks the calling thread until a decision arrives, so
no architectural change is needed in your agent — the tool call simply does not
return until an operator acts (or the request expires).
attrs["gen_ai.tool.name"] in ["transfer_funds", "delete_record", "deploy"]With the action set to require_approval, a matching call creates a pending
approval request. The agent resumes only when an operator approves; on denial the
call is refused the same way a block would refuse it.
Where operators decide
Pending requests surface in the places a team already watches:
- Dashboard — an approval card shows the agent, the tool, the arguments, and the matched policy, with approve and deny actions.
- Slack — an interactive Block Kit message with approve and deny buttons.
- Discord — a rich embed with interactive components.
From the command line, operators can list and act on requests directly:
strathon approvals list --status pending
strathon approvals approve <approval-id>
strathon approvals deny <approval-id>Multi-party approval (N-of-M)
For the highest-risk actions, a single sign-off may not be enough. Strathon supports N-of-M approval: a request can require, say, two of three named approvers before the call is admitted. This mirrors the dual-control pattern used for financial transactions and production changes, where no one person can unilaterally authorize a sensitive operation.
Preventing stuck agents
A blocking approval that no one answers would hang an agent indefinitely. Two mechanisms prevent that:
- Automatic expiry — a pending request that is not answered within its window resolves on its own rather than blocking forever.
- Escalation — requests that sit unanswered are escalated so they reach someone who can act, instead of silently timing out.
Pair require_approval with the SDK's fail-open or fail-closed setting to decide
what happens to an unanswered call: fail-open lets it proceed when a decision
cannot be reached, fail-closed refuses it. See the reliability discussion in
Runtime Intervention.
At the MCP boundary
The same action applies when agents reach tools over MCP. In the
MCP gateway, a require_approval verdict rejects the tools/call with
JSON-RPC error code -32041, telling the agent the call needs human approval
rather than silently allowing or hard-blocking it.
Approvals vs blocks vs halts
These three controls answer different questions:
- block stops a call unconditionally when its policy matches — no human in the loop. Use it for actions that should never happen.
- require_approval stops a call conditionally on a human decision. Use it for actions that are sometimes legitimate and warrant review.
- halts stop an agent (or the whole project) regardless of what it is doing — the operator kill-switch. See Runtime Intervention.
See also
- Runtime Intervention — the full policy and action reference
- CEL Reference — writing the match expression
- Compliance Mapping — how approval maps to EU AI Act Article 14