Status reasons
When the run cancels,OutputEvent.status.reason carries one of:
approval_required— a gate emitted anApprovalEventand is waiting on a decisionapproval_denied— a denying resolution was consumed (direct call only; agents convert this to a tool result)approval_already_claimed— durable claim said another worker already resumed this gateapproval_policy_error— arequires_approval/approval_promptcallable raisedinput_required— asuspend()call emitted anInteractionEventand is waiting on a valuecancelled— aCancelwas supplied on resume (approval or suspension); the whole run aborted and nothing was fed back to the model
Usage counters
OutputEvent.usage records pause-lifecycle counters so you can plot them in dashboards:
approvals:required— a gate emitted anApprovalEventapprovals:approved— a valid approved resolution was consumedapprovals:denied— a valid denied resolution was consumedapprovals:expired— an expired resolution was ignored and the gate re-emittedapprovals:cancelled— aCancelaborted the run at a gatesuspends:required— asuspend()call paused the run
Common patterns
Approve from a UI, resume from a worker
Approve from a UI, resume from a worker
- Configure a durable provider (
JsonlTracingProvider/SqliteTracingProvider/PlatformTracingProvider). - UI calls
agent(prompt=...), capturesApprovalEvent(or polls the trace forpending_approvals()), shows reviewer the prompt + redacted input. - Reviewer clicks Approve/Deny. UI persists
(approval_id, ApprovalResolution)to a queue. - Worker pulls the message and calls
agent(prompt=..., parent_id=run_id, resume={approval_id: resolution}). - If
result.status.reason == "approval_already_claimed", no-op. Otherwise, the handler executed exactly once.
Ask the user a clarifying question mid-task
Ask the user a clarifying question mid-task
Give the agent the
ask_user tool. When it’s blocked it calls ask_user, the run ends input_required with an InteractionEvent, your UI renders the question, and you resume with resume={interaction_id: answer}. Keep everything before the suspend() call idempotent: the handler re-runs from the top on resume.Settle a batch of pauses in one round-trip
Settle a batch of pauses in one round-trip
The agent may call multiple gated/suspending tools in parallel. Each pause emits its own event and the run cancels once they all settle. Collect every id, present them as a checklist, and resume with the full dict in one call: approvals and interactions can be mixed freely.
Force a fresh decision for each call
Force a fresh decision for each call
The default
approval_id is stable across retries of the same (path, input). To require a fresh decision per invocation, include a per-call unique value in the input, typically an idempotency_key=str(uuid4()), so each call derives a distinct id.Hide secrets from reviewers
Hide secrets from reviewers
Use
approval_redact_keys=["api_key", "password"] for the simple case. Use approval_redactor=lambda input: {...} for partial masking (e.g. mask the local-part of an email but keep the domain). The handler still receives the unredacted input.Approve with a correction (edit on approve)
Approve with a correction (edit on approve)
The model proposed a slightly-wrong argument (a typo’d email, an amount that should be lower). Instead of denying and round-tripping, approve with
ApprovalResolution(approved=True, override_input={"to": "fixed@example.com"}). The override merges over the proposal, re-validates through the params model, and the handler runs with the corrected input. Over HTTP send {"approved": true, "override_input": {...}}.Cancel a task the user abandoned
Cancel a task the user abandoned
When the user closes the dialog or navigates away rather than deciding, resume with
Cancel(reason=...) (HTTP: {"type": "timbal.cancel", "reason": "..."}) keyed to any pending id. The run ends status.reason == "cancelled" and nothing is fed back to the model, distinct from a denial, which the agent would see and react to.See also
- Approval-Required Tools example — runnable end-to-end snippet
- Events & Streaming — the event stream
- Tracing — how runs are persisted and replayed
- Context & State Management —
RunContextinternals - Tools — tool configuration reference