Skip to main content

Approvals

When a policy rule says ask, 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() returned awaiting_approval; nothing unapproved ran.
  • Each approval has the tool, the model’s arguments, the rule’s reason and expires_at. command is None here; 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.
A run can pause again after a resume, so decide in a loop:

On the default profile

The examples on this page start from interactive-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:
Every other call is decided as permissive-dev decides it; issue_refund pauses the run for a person, as above.

The three decisions

After a denial, the call does not run and the model reads the note. With the note above (over HTTP), the run answered:
A decision applies once, to that exact call (tool, target and arguments). For a shell command, the pending approval names the commands it would run, one per line, in 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

The same two processes worked unchanged on each durable store; only the store in agent_def.py changes: On Redis, for example, process two printed:
The default in-memory store dies with the process (Memory).

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:
  • ApprovalRequest has capability, target, risk_level, reason, expires_at, and metadata["tool_name"]; not the call’s arguments.
  • Returning None or approved=False refuses the call; nothing is saved for later.
  • StaticApprovalResolver(approved=True) approves everything (tests); not high-risk requests unless allow_static_high_risk_approvals is True.

Refuse instead of pausing: approval_mode="fail"

For unattended jobs:
The same refund request then finished at once:
The tool did not run, and its 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:
The window is how long a person has to decide. A decision made in time stands, however late the run resumes. An approval nobody decides in time is a no: 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-level agent in refund_agent.py, served with omniserve run --agent refund_agent.py --port 8765:
Decide, then resume:
The decision returns the approval (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

In agent_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

resume was called before every approval was decided:
Decide each approval the run lists first. Over HTTP this is a 409.
The approval waited longer than its window:
It is recorded as 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:
Set a longer approval_expires_seconds if people need more time.
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.
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.
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.