Approvals & policies
Park a production call for a human decision, route it with policy evaluated on every invocation, and record who decided what.
An approval pauses a call until a human decides, and records who decided what, when, and why. A policy decides, deterministically and per environment, which calls need a human at all.
This page is the concept overview. It applies to both ways of requiring a human, and they are not equivalent, so start there.
Two shapes, and they hold different things
Gate the action, and Anlyon holds the call
The primary shape. Mark an action requiresApproval, or let a policy require it, and the invocation parks instead of executing. Anlyon is the one that would have made the request, so Anlyon is the one holding it.
const { data } = await anlyon.actions.invoke('refund-order', {
charge: 'ch_3P9x',
amount: 12000,
});
if (data!.pendingApproval) {
// 202. Nothing has been sent to Stripe.
console.log('Waiting on a human:', data!.approvalId);
}
On approval, Anlyon executes the request snapshot frozen when the invocation was requested, using the vaulted credential. Editing the action while somebody is deciding cannot change what their yes sends.
Gate your own function, and your process holds the call
When the call cannot move into Anlyon, wrap it. The SDK creates the approval, waits for the decision, and calls through only on approve.
const guardedRefund = client.approvals.gate(
{ title: 'Refund a customer?', timeoutMs: 10 * 60_000 },
refund,
);
await guardedRefund('ord_42', 129.99); // blocks until a human approves
A denied request raises ApprovalDeniedError. A timeout raises ApprovalPendingError and leaves the approval open.
Be precise about what this buys you. The wrapper waits for a decision before calling your function, and it depends on your code going through the wrapper. It does not move your credential into Anlyon, and Anlyon cannot attest that your function ran, or did not. You get the human decision and the record. You do not get credential isolation. See Local approval gates.
Decisions arrive in the dashboard Approvals inbox in realtime for either shape, or through client.approvals.approve() / deny() from your own tooling.
Two rules, always enforced server-side
- The agent can’t approve itself. Deciding requires the separate
approvals:decidescope and a distinct credential. The credential that requested an approval cannot decide it. No scope grants that and no policy relaxes it. - Every decision is recorded: decider, timestamp, note, and the policy (and its version) that governed the routing.
Any workspace member can decide an approval in the console.
Policies do the routine work
Policies evaluate at request time, per environment, in ascending priority. The first match wins, so routing is deterministic and explainable:
auto_approvethe low-risk cases (small refunds, staging) so humans only see what matters. To clear an action’s ownrequiresApprovalflag the policy must name that action withmatchActionName. A catch-allauto_approvecannot disarm every gated action at once.require_approval, optionally with N-of-M approvers, for the consequential ones.auto_denywhat should never happen.
Create and version policies in the dashboard under Approval Policies, or from deployment code with an operator credential (see Policy as code). Every policy states what it applies to: one action, every action, custom requests, or every request. Nothing is inferred from its name, and a broad auto_deny needs an explicit confirmation.
Every governed approval records which policy version routed it and a human-readable explanation. An automatic denial is also written onto the invocation, as a persisted decision (source, reason code, safe explanation, and the policy’s name and version as they were), so an invocation explains itself even after the policy is edited or deleted.
Who decided is recorded, not inferred
Each approval carries a decisionOrigin: human, policy, system (expiry) or assistant (an Anlyon Vigil approval). The console inbox opens on decisions waiting for a person. Approver history and Automated decisions are separate views, so decisions a policy made are an audit trail rather than a queue.
Loud by default
A new workspace hears about approvals with no setup. approval_requested alerts go to the in-app inbox. Email alerts are included during Early Beta Access, up to 500 emails per workspace per UTC month. Once connected, alerts also go to Slack, Discord and a webhook. Each alert links to the approval. Configure channels and recipients under Settings → Notifications.
It’s all in the trace
Pass runId to the gate and the approval emits approval_wait and approval_decision spans into that run’s trace. The human wait sits on the same timeline as the model and tool calls. Approvals are never metered, on any plan.
Related
- Actions: defining the call Anlyon holds.
- Bound approvals: what an approval of a governed action is bound to.
- Local approval gates: the hands-on guide for the wrapper.
- Environments & the kill switch: halting blocks even approved calls.
- EU AI Act mapping: how approvals relate to the Article 14 oversight obligation.

