Skip to content
Anlyon
Esc
↑↓navigate↵open⌘Jpreview
On this page

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:decide scope 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_approve the low-risk cases (small refunds, staging) so humans only see what matters. To clear an action’s own requiresApproval flag the policy must name that action with matchActionName. A catch-all auto_approve cannot disarm every gated action at once.
  • require_approval, optionally with N-of-M approvers, for the consequential ones.
  • auto_deny what 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.

Was this page helpful?