Approval Policy

The approval policy sits between the model's tool call and its execution. It decides whether a tool call needs human confirmation, and the approval gate presents that decision to the user.

Two-part system

ComponentRole
ApprovalPolicyDecides whether approval is needed (configurable logic)
ApprovalGateAsks the user for confirmation at runtime (UI integration point)

Policy: ConfigApprovalPolicy

The default policy uses per-tool overrides from config, falling back to risk-based defaults:

[approval.per_tool]
read_file = "auto"    # never ask
write_file = "ask"    # always ask
run_command = "ask"   # always ask
ActionBehaviour
AutoExecute without asking
AskRequire human confirmation
DenyRefuse to execute (returns a denial error to the model)

When a tool is not listed in config, the risk-based default applies:

ToolRiskDefault
ReadAuto
WriteAsk
DestructiveAsk
NetworkAsk

Gate: RpcApprovalGate

The RPC implementation reads an approvalResponse JSON-RPC 2.0 method from stdin and returns the approved boolean. See RPC Mode for the approval flow.

Example gate

In RPC mode, the RpcApprovalGate reads an approvalResponse method from stdin (see RPC Mode). A REPL, TUI, or editor integration can implement the ApprovalGate trait to present approvals however it chooses.

On denial, a synthetic error result is fed back to the model as if the tool had failed. The model can then adjust its approach — it does not crash or get stuck.

Custom gates

The ApprovalGate trait is async, so any UI can implement it:

#![allow(unused)]
fn main() {
#[async_trait]
pub trait ApprovalGate: Send + Sync {
    async fn request_approval(&self, call: &ModelToolCall, risk: ToolRisk) -> ApprovalDecision;
}
}

ApprovalDecision has three variants:

  • Approved — proceed with execution.
  • Denied — inject a generic denial error and continue.
  • Redirect { message } — inject the user's alternative instructions and return to thinking so the model can re-plan.