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.
  • 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:

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).

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:
After expires_at the approval can no longer be decided: nothing runs on it. resume(run_id) then asks again, with a new approval for a person to decide, or end the run with abandon_run. (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, 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) asks again (the run returns awaiting_approval with a new approval), or end the run:
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.
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.