Escalations

Some calls should not be a yes or a no decided in advance. An agent searching the web is usually fine; an agent searching the web on a machine you share is a decision you want to make when it happens. Escalation is the third answer — pause, ask, continue.

The terminal paused mid-tool-call, showing search.web with its query and three choices: allow once (just this call), allow always (remember this), and deny (refuse this call)

The agent is suspended mid-call. Nothing has run yet, and nothing will until you answer.

Three outcomes

What happens
allow onceThis call proceeds. The next identical call asks again.
allow alwaysThis call proceeds, and a grant is written so it stops asking.
denyThe call is refused. The agent is told, and carries on.

Escape denies. A policy prompt has no neutral exit — the call is blocked on your answer either way, so dismissing it would just mean waiting for a timeout to deny it for you.

Denial is not a crash. It reaches the agent as an ordinary error naming what was refused, and a well-written agent adapts:

A denied tool call rendered as CAPSULE_POLICY_DENIED: search.web denied by policy, with the agent explaining it cannot access live web results and offering to help another way

Where the rule comes from

Two files can ask for a prompt, and they answer different questions.

axon.config.ts — what this agent needs. The author knows their agent pushes to git and wants a human on it:

// my-agent/axon.config.ts
export default defineAgent({
    policy: {
        process: { allow: ["git *"], escalate: ["git push*"] },
    },
})

profile.config.ts — what you allow on this machine, across every agent, including ones you installed and never read:

// profile.config.ts
export default defineProfile({
    policy: {
        tools:   { search: "escalate" },
        process: { run: "escalate", spawn: false },
    },
})

The profile is a ceiling. An agent can narrow within it and can never widen past it, so search: "escalate" above means every agent asks — whatever its own config says. The stricter of the two layers wins on every call.

That is the division worth internalising: the agent's config is a statement about the agent, the profile's is a statement about your machine. "This agent only reads ./src" belongs to the author. "Nothing on my laptop touches ~/.ssh" belongs to you, because it cannot depend on every agent author agreeing with you forever.

See policy and profile policy for the full field reference.

What "allow always" actually writes

A grant — recorded in your profile, not in either config file.

That placement is deliberate. Editing a config you authored from a palette keypress is invasive, and an agent-level grant could not widen a profile that escalated anyway, so "always" would fail confusingly in exactly the case that produced the prompt.

A grant is keyed to the agent and the exact subject you were asked about, never widened into a pattern. Approving git push origin main does not silently become git push * — if you want that, write it in your config, where it is a decision you made rather than one inferred from a single yes.

A grant can only ever satisfy an escalation. It can never overturn a deny from either layer. "Stop asking me this" and "override the rule I set" are different acts, and collapsing them would let one keypress undo a machine-wide rule you set deliberately.

Grants live in ~/.axon/profiles/<you>/store/grants.jsonl, and revocation appends rather than rewrites — a decision made and later withdrawn is two facts, not zero. The file is the audit trail.

More than one at a time

Escalations queue. Two overlapping wakes can both raise one, and neither may be dropped: a dropped escalation is a denial you never saw, on a call you might well have allowed.

They are answered oldest first — the agent blocked longest is the one closest to timing out inside its capsule. And a request's clock starts when it is shown to you, not when it was raised, so the ones waiting behind the current prompt are not burning their deadline while you read.

When nobody is watching

Headless — axon run, cron, CI, a route handler — there is no surface to ask. Escalations fail closed after the capsule's timeout, 30 seconds by default and configurable via resources.escalationTimeoutMs.

They are recorded as expired rather than denied, which is a real distinction: nothing refused the call, nobody was there. The record survives, so approving the request later writes a grant that the next attempt reads.

The practical consequence: an agent you intend to run unattended should not depend on escalation. Decide those rules in config, where they hold with nobody present.

Seeing what happened

Every escalation is recorded — including ones you answered instantly — which is what makes "three escalations this session, two approved" answerable after the fact.

In a conversation the decision shows inline: capsule:escalating while it waits, capsule:denied if refused. :open log puts the full event stream in your editor, and :open flame shows where the pause sat in the run. See events.

See also

policy — the agent's own block, and every rule shape.

profile policy — the machine-wide ceiling.

Kernel & Policy — how enforcement works, and why a denied call never reaches the function body.