Approvals
When a policy rule saysask, the run
pauses before that call until a person decides, then resumes: in the same
process, another one, or over HTTP.
Every output on this page is what the code printed when it was run. The
model’s wording, and the ids, will differ on your run.
Pause, decide, resume
run()returnedawaiting_approval; nothing unapproved ran.- Each approval has the tool, the model’s arguments, the rule’s
reasonandexpires_at.commandisNonehere; for a shell command it names the commands the call would run (below). arguments=replaced the model’s: 30 was refunded, not 42.- On
resume, the call was asked again and allowed by the recorded decision.
On the default profile
The examples on this page start frominteractive-dev, which also asks
before other things (MCP tool calls, shell commands, high-risk actions). To
keep the default profile, permissive-dev, and ask about one tool only, add
one ask rule to it. An ask outranks the profile’s allows, so the rule takes
effect:
permissive-dev decides it; issue_refund
pauses the run for a person, as above.
The three decisions
approvals[i]["command"]["summary"], each as written, with
its variable settings and redirects (GIT_SSH_COMMAND='…' git push,
echo key >> ~/.ssh/authorized_keys); a newline or other control character
is shown as \n, so a quoted newline cannot pass for a second command. The
decision applies to that exact command text: approving
ls approves nothing else.
Setting up a sandbox is the one exception: an approved network, file system
or environment set-up holds for the rest of the run, for that exact set-up.
Each sandbox session asks for it when it opens, and a resume opens a new
one; the person is not asked the same question twice in one run.
From another process
A paused run lives in the agent’s memory store. With a durable store, any process with an agent built the same way (same memory store, workspace, tools and policy) can decide and resume it.1
One definition, shared
2
Process one: run until it pauses, then exit
3
Process two: decide and resume
agent_def.py changes:
On Redis, for example, process two printed:
Answer in the moment: approval_resolver
To answer while the run waits (a console prompt, a chat button), give a
resolver. It is called at every ask; the run does not pause:
ApprovalRequesthascapability,target,risk_level,reason,expires_at, andmetadata["tool_name"]; not the call’s arguments.- Returning
Noneorapproved=Falserefuses the call; nothing is saved for later. StaticApprovalResolver(approved=True)approves everything (tests); not high-risk requests unlessallow_static_high_risk_approvalsisTrue.
Refuse instead of pausing: approval_mode="fail"
For unattended jobs:
outcome is denied. The model was told the
call was refused because it needs a person’s approval and none can be asked.
(0.4.1 reported it as awaiting_approval and told the model it was waiting.)
Expiry
24 hours by default; per rule:resume(run_id) refuses the call, tells the model the approval expired,
and the run goes on to finish. (In 0.4.1 an expired approval left its run
unable to resume; abandon_run is the way out there.)
Over HTTP, with OmniServe
The refund agent above as a module-levelagent in refund_agent.py,
served with omniserve run --agent refund_agent.py --port 8765:
status denied, decision deny,
approver, note, decided_at); the resume returns the finished run.
Who decided, in the evidence
Each pause and resume is its own trace segment;
get_run_trajectory(run_id)
joins them (Durable runs).
Options
Inagent_config["governance_config"]:
Methods:
resolve_approval(run_id, approval_id, *, decision, approver, note=None, arguments=None),
resume(run_id) (reference).
Asks from inside a call (sandbox network, a command, a sub-agent) pause the
run the same way; a worker’s ask shows on its lead’s run with
delegated_run_id (Sub-agents,
Background agents).
When things go wrong
ValueError: Run ... is still waiting for approval
ValueError: Run ... is still waiting for approval
resume was called before every approval was decided:409.ValueError: Approval ... expired at ...
ValueError: Approval ... expired at ...
The approval waited longer than its window:It is recorded as Set a longer
expired and nothing runs on it. resume(run_id)
refuses the call (the model is told the approval expired) and the run
finishes, or end the run instead:approval_expires_seconds if people need more time.ValueError: Approval ... is already denied
ValueError: Approval ... is already denied
A decision is final; it cannot be changed or made twice:That is the
409 OmniServe returned; in Python it is a ValueError.
Once a resume has applied the decision, the message says already used;
the record’s decision still says which it was.LookupError: No approval ... on run ...
LookupError: No approval ... on run ...
The approval id is not one of this run’s, or the run is not in this
agent’s memory store:In another process, build the agent with the same
memory_router.
ValueError: approver is required means approver was empty: every
decision carries a name.The run did not pause; the model said it needs approval
The run did not pause; the model said it needs approval
approval_mode is "fail", or an approval_resolver returned None.
Either refuses the call on the spot. Remove both to pause for a person.Next
Policies
Decide what is asked about, and test the rules.
Durable runs
Run records, resuming after a crash, and how long evidence is kept.
Budgets
The other pause: a run that needs more budget.
OmniServe
Every route, including approvals and resume.
Security model
How approvals fit with the policy, sandbox and the rest.
Execution
Commands and scripts, and the asks they can raise.