suspend() lets a tool pause the run and hand control to the user, then resume with whatever they send back. Each tool picks a kind (the frontend’s renderer discriminator) and a payload (what to render). The resume value can be any JSON type — a string, a bool, a list, a dict — so the same primitive covers everything from a yes/no to a multi-field form.
See the full reference in the Human in the Loop section. This page is a catalog of shapes you can copy.
A catalog of interaction tools
suspend is exported at the top level (from timbal import suspend). Ready-made ask_user, ask_user_multi, and confirm also ship in timbal.tools (from timbal.tools import ask_user, ask_user_multi, confirm).
Payload shapes at a glance
This is the contract your frontend renders against. Switch your UI onkind, render payload, and send the matching resume value back keyed by interaction_id.
Driving the loop
Give an agent whichever interaction tools fit your product, then run the pause/resume loop. The agent decides which tool to call; you render the payload and resume with the value.A single turn can open multiple interactions at once (the model calls several tools in parallel). You receive one
InteractionEvent per question and send all answers back in one resume map: { "<id1>": ..., "<id2>": ... }. Approval gates ride the same channel — mix freely.Structured form example
ask_form is the workhorse for collecting several values in one round-trip instead of a chain of questions:
InteractionEvent.payload is exactly the dict you passed; resume with the filled values:
Generative UI: render, then continue
review_chart shows how the same mechanism powers “render something, let the user tweak it, then keep going”. The tool emits the data to draw; the resume value carries the user’s edits back into the run:
{"approved": True, "edits": {"range": "90d"}} back and the handler continues with their choice — no separate “apply” endpoint needed.
Resuming across processes
Everything above works in-process. To pause in a browser now and resume in a worker later, configure a durable provider and passparent_id on resume:
/stream wire contract your frontend talks to.