suspend() pauses a run from inside a handler and waits for an externally-supplied value. It’s the general form of the approval gate (an approval resumes with a bool, a suspension resumes with anything) and it’s how you build tools like ask_user, confirm, or pick_option that let an agent explicitly stop and hand control back to the user mid-loop.
How it works
- A handler calls
suspend(payload, kind=...). - The run ends with status
cancelled/ reasoninput_requiredand emits anInteractionEventcarryingpayload. - The frontend renders the payload (keyed by
kind) and collects a response. - You resume by calling the runnable again with the original run as
parent_idandresume={interaction_id: value}. The handler re-executes andsuspend()returnsvalue.
Writing an interaction tool
A tool is any handler that callssuspend():
suspend is exported at the top level (from timbal import suspend), and the ready-made ask_user / ask_user_multi / confirm tools ship in timbal.tools (from timbal.tools import ask_user, ask_user_multi, confirm).
InteractionEvent
fragment
interaction_id is a deterministic hash of the handler path + payload, stable across the pause/resume round-trip, so the client always sends back the id it received. tool_call_id lets the client correlate the interaction with the exact tool_use block in the chat transcript when the agent triggered it.
Declaring a response schema
Passresponse_schema to suspend() to tell the client what shape the resume value must be. It rides on the InteractionEvent so the frontend can validate the user’s input before resuming, with no extra round-trip to discover a bad value.
None (the default) means any value is accepted.
See also
- Approval gates — gate irreversible actions before any handler code runs
- Resuming a paused run — durable cross-process resume and cancellation
- Client integration (HTTP) — the
/streamwire contract