Skip to main content
These patterns apply to any Runnable: Agents, Workflows steps, and standalone Tools. Timbal pauses a run for a human in two ways, and resumes both through one channel:
  • Approval gates (requires_approval) are declarative permission checks that fire before a runnable executes. Use them for irreversible actions: refunds, deploys, account deletions, outbound emails, anything that costs money or moves data.
  • suspend() is a control-flow primitive a handler calls from the inside to ask the user for arbitrary input (a clarifying question, a picked option, a confirmation). Use it to build interaction tools like ask_user or confirm.
Both pause the run, persist it, emit a structured event, and resume when you call the runnable again with resume={...}. They share the same durable rails (parent_id + tracing provider). The only real difference is when they pause and what they resume with.
requires_approval (gate)suspend() (interaction)
Intent”may I do this?""tell me / give me”
Pausesbefore the handler runsinside the handler
Handler ran?no (zero code)yes, up to the suspend() call (re-runs on resume)
Resumes withbool / ApprovalResolutionarbitrary value
Applieddeclaratively on any runnable (even ones you don’t own)by calling suspend() in the handler
EventApprovalEventInteractionEvent
Cancel reasonapproval_requiredinput_required
resume={id: value} is the single channel for continuing any paused run. For an approval gate the value is a decision (True/False or an ApprovalResolution); for a suspend() call it’s the arbitrary value the handler asked for. A Cancel value works for either and aborts the whole run. The id-spaces are disjoint, so one resume dict can settle a mix of pending approvals and suspensions in a single call. Unrecognized ids are ignored with a warning.

On this page group