Skip to main content

Human-in-the-Loop Approval

AI agents perform actions with real-world consequences -- executing trades, deleting records, sending emails, modifying production systems. Many of these actions require human oversight before proceeding.

Without a schema-level approval mechanism:

  • Approval configuration lives in opaque SDK-level metadata
  • Different SDKs implement incompatible approval conventions
  • The schema cannot validate approval configuration; typos are silent
  • There is no portable way to express "this tool requires human approval"

Agent Format solves this by making approval a first-class schema concept with rich support for message templates, conditional evaluation, and governance integration.

The Three Forms of Approval​

The approval field is a union type that accepts three forms:

FormMeaningExample
approval: trueAlways require approval. Runtime generates a default message.High-risk irreversible actions.
approval: falseExplicitly exempt from inherited approval.Safe read-only tools opted out of a blanket server-level approval.
approval: { ... }Detailed configuration with optional message template and/or condition.Conditional approval based on argument values.

When the approval field is omitted entirely, no approval is required (unless inherited from a blanket server-level or governance policy).

Empty object: approval: {} (an ApprovalConfig with no fields set) is equivalent to approval: true -- approval is always required and the runtime generates a default message. This is the safe interpretation: an explicit approval object, even if empty, signals the author's intent to require approval.

Message Templates​

Agent Owners provide human-readable message templates with mustache-style placeholders that the runtime resolves at invocation time:

approval:
message_template: "Approve {{tool_args.order_type}} order: {{tool_args.action}} {{tool_args.quantity}} shares of {{tool_args.symbol}} at ${{tool_args.price}}?"

Available Template Variables​

VariableDescriptionExample Value
{{tool_name}}Alias of the tool being invokedexecute_trade
{{tool_args.<key>}}A specific argument valueAAPL, 100, 150.25
{{tool_args}}Full tool arguments as JSON string{"symbol": "AAPL", ...}
{{agent_alias}}Alias of the agent executing the tooltrading_agent
{{agent_id}}The agent's metadata IDfinancial_analyst_v2
{{skill_id}}A2A skill ID (for remote agent skills)process-payment
{{skill_args.<key>}}Skill input field (for remote agent skills)500.00

For the full list of available variables, see the Template Variable Catalog.

Template Rendering Rules​

  1. Missing variables render as empty string (no error).
  2. Nested access is supported: {{tool_args.order.details.amount}}.
  3. Templates are pure interpolation -- no logic. Conditional logic belongs in the condition block.

When message_template is omitted, the runtime constructs a default message including the tool name and arguments.

Conditional Approval​

The condition field makes approval conditional -- required only when the condition evaluates to true at invocation time.

Single Condition Group (AND Semantics)​

approval:
message_template: "Approve transfer of ${{tool_args.amount}}?"
condition:
args_match:
amount: { gt: 10000 }
currency: "USD"
# Both must be true: amount > 10000 AND currency == "USD"

Multiple Condition Groups (OR-of-ANDs)​

approval:
message_template: "Approve transfer of ${{tool_args.amount}}?"
condition:
- args_match:
amount: { gt: 10000 }
- args_match:
recipient_type: "external"
# Approval if amount > 10000 OR recipient is external

Match Expression Syntax​

The following match expressions are available in args_match blocks. For the complete reference, see Condition Matcher Reference.

ExpressionMeaningExample
Literal valueExact matchcountry: "US"
{ gt: N }Greater thanamount: { gt: 10000 }
{ gte: N }Greater than or equalamount: { gte: 100 }
{ lt: N }Less thanrisk_score: { lt: 0.5 }
{ lte: N }Less than or equalrisk_score: { lte: 0.5 }
{ ne: V }Not equal (string, number, or boolean)status: { ne: "approved" }
{ pattern: "..." }Regex matchemail: { pattern: ".*@external\\.com$" }
{ in: [...] }Value in listcategory: { in: ["delete", "modify"] }
{ not_in: [...] }Value not in listregion: { not_in: ["restricted", "embargoed"] }

MCP Server Approval: Blanket and Per-Tool​

MCP servers support two levels of approval that compose:

mcp_servers:
- alias: external_api
server_ref: partner.example.api-v2
approval: true # blanket: all tools require approval
allowed_tools:
- name: health_check
approval: false # exempt from blanket
- name: create_resource
approval:
message_template: "Approve creating '{{tool_args.resource_type}}'?"
- list_resources # inherits blanket approval: true

Resolution Rules​

  1. String entry (e.g., "read_table") -- inherits server-level approval (if present) or no approval.
  2. Object entry with approval -- overrides server-level for that tool.
  3. Object entry with approval: false -- exempted from blanket.
  4. Governance policies can add approval regardless of what the Agent Owner declares (union semantics).

Approval and Governance: Union Semantics​

Approval requirements can come from two independent sources:

SourceAuthored ByConfigured In
Action space declarationAgent Owner.agf.yaml action_space entries
Governance policyGovernance TeamExternal Policy Registry

The composition rule is simple: if ANY source requires approval, approval is required.

Agent OwnerGovernance PolicyEffective
No approval fieldNo approval policyNo approval
approval: trueNo approval policyApproval required
No approval fieldPolicy requires approvalApproval required
approval: falsePolicy requires approvalApproval required (governance wins)

The Agent Owner's approval: false is a preference (primarily for per-tool exemption from blanket server-level approval), not an override of governance. The tighten_only_invariant applies: governance can only add restrictions, never remove them.

Approval on Agent Delegation​

The approval field on local_agents[] and remote_agents[] gates the delegation decision -- the runtime requests approval before invoking the sub-agent. This is distinct from tool-level approval within the sub-agent: a sub-agent may have its own approval requirements on its tools, which are evaluated independently during the sub-agent's execution.

local_agents:
- alias: financial_executor
source: agents/executor.agf.yaml
approval:
message_template: "Delegate to financial executor for {{parent.input.action}}?"
condition:
args_match:
parent.input.risk_level: { in: ["high", "critical"] }

In this example, the runtime requests approval before delegating to financial_executor when the risk level is high or critical. The sub-agent's own tool-level approvals (e.g., on execute_trade) are evaluated separately during its execution.

What the Runtime Handles​

The schema declares what requires approval. The following are runtime concerns, not schema concerns:

ConcernWhy It's Runtime
Approval delivery mechanism (WebSocket, Slack, email, CLI)Depends on Runtime Owner's infrastructure
Approval timeoutDepends on approval channel; governed by governance policy
Timeout fallback behavior (deny, approve, escalate)Governance/Runtime Owner decision
What additional context to attach to the requestRuntime decides based on UI capabilities
How "approve with modifications" worksRuntime/SDK implementation