Skip to main content
Pause a run to ask a person a question and block until they answer. An interaction suspends the caller mid-run, records the question, and wakes the caller with the answer. The ask_user tool is the core capability; ask_external extends it to human actions that happen on an outside surface. This guide enables the feature, asks in each answer format, answers from the CLI, and wires an external callback end to end. For the model behind it, see Interactions.

Enable interactions

Load the ask_user tool module and the interactions HTTP router — the SSE inbox stream plus the human answer door.
examples/interactions/enable_interactions.yaml
An agent or tool that has ask_user in scope can now pause and ask.

Ask a question

ask_user(question, answer_format=...) is called from inside a tool or agent run. It records the question, blocks the caller, and returns the typed answer once a person responds (or raises on timeout). One question declares one answer format: The first four are answered on the authenticated in-client surface. Example ask_user calls (the tool input a caller passes):
An optional group_id threads related questions, and timeout (seconds) overrides the configured default before the call raises. Any question can carry media — images and links shown above the answer controls in the inbox — by passing media=[...] to ask_user. Media is display-only: the person still answers through the answer_format. The canonical use is a select that shows each choice:
Each item is a {kind, url, caption?} object — an image (an absolute https URL or a data:image/* URI) or a link (an absolute http(s) URL) — validated against the contract before the question is stored:
examples/interactions/ask_with_media.yaml
An agent asks with the builtin ask_user tool using the same shape; media is an array of the same objects:
The Studio Interactions inbox rendering a question together with its attached images and links.

A question with its media rendered in the Studio inbox, above the answer controls.

Media renders in the Studio inbox only — it is not forwarded to a channel delivery, which carries the question text. See Media on a question for the accepted url forms, the caps, and the privacy note.

Answer from the CLI

A pending question surfaces on the authenticated SSE stream — the Studio inbox, or the CLI. List the current backlog, tail it live, or answer one:
The answer value is JSON, matching the question’s format — a string for text and select, a bool for confirm, an object for form:
examples/interactions/interactions_answer_help.sh
The value is validated server-side against the question’s declared format and schema; a mismatch is rejected and the caller stays blocked.

External questions and the callback flow

The external format is for actions a person completes on an outside surface — signing a document, approving a request, paying. The asking tool blocks exactly as for any other format; only the delivery channel differs. The external system delivers the answer back through a public callback door, not the answer door. The ask_external transformer extension packages the whole flow. It wraps a tool that builds an external resource from a callback_url and returns the URL to visit. Attach it in the manifest, binding an optional webhook verifier that authenticates the signed callback:
examples/interactions/attach_ask_external.yaml
The verifier is author-bound extension config — it is closed over at build time and is never an LLM-facing parameter, so a calling agent can neither drop nor forge the callback authentication. The wrapped tool keeps its own inputs; the extension injects question / answer_schema / timeout and hides callback_url (the platform supplies it). At run time, calling the composed tool:
  1. builds the external resource, substituting the minted {callback_url} into the link (or handing it to a callable link that returns the final URL);
  2. blocks the caller while the person acts on that surface;
  3. accepts the answer when the external system POSTs the signed payload to POST /api/interactions/callback/{ticket} (verified first when a verifier is bound), or when a person completes the GET confirm page;
  4. validates the payload against the question’s schema when one is declared, and returns it to the blocked caller.
External questions (and any channel-delivered question) require INTERACTIONS_PUBLIC_BASE_URL — an https:// origin, since the callback URL is a bearer capability — and Redis. Callback payloads arrive through an unauthenticated door, so treat every delivered answer as untrusted input.

Deliver to a channel instead

Passing channel="<name>" to ask_user hands delivery to a registered channel plugin — Telegram, Slack, SMS/WhatsApp — instead of the default inbox: the question mints a public callback ticket exactly like an external ask, the plugin pushes it to the person, and the reply returns through the same callback door. An optional recipient names the per-call address; an unknown channel name or a delivery failure raises loudly before the run would wait on an answer that can never arrive.

See also