Interactions
One contract for asking the user something, in the terminal or over your own protocol.
A tool sometimes needs an answer before it can finish. Writing that against a terminal dialog strands it in the terminal, so @introspection-ai/recipes/interactions gives one call that resolves in the terminal, over RPC, headless, and in a host that pauses a run and resumes it later.
askUserApproval is the same shape for a yes or no. Both return one result envelope whose outcome.type is approved, revision_requested, declined, or awaiting_user. The module registers no tools of its own: your recipe owns the tool and calls the helper from inside it.
Where the question goes
PI_ASK_USER_AUTO_APPROVEfor headless and CI runs. Confirmations resolve approved, everything else declined.- An interactive UI, meaning the built-in dialog walk in terminal and RPC modes.
- A pause-and-resume host, signalled by
PI_INTERRUPT_RESUME. - A fallback that states nothing was shown and tells the model to ask in its normal reply.
The first three are decided for you. Locally you write nothing: run pi --recipe . and the dialog appears. The fourth means a tool never hangs forever on a channel that cannot answer.
The interrupt contract
Case three is the one you implement. A run that cannot answer inline stops and hands your runtime a request to surface, then continues when you answer it. Set PI_INTERRUPT_RESUME and the tool returns a details.interrupt whose outcome is { type: "awaiting_user" } instead of blocking.
| Field | Meaning |
|---|---|
reason | input_required, confirmation, tool_call, or another string |
message | The prompt to show a human |
options | Suggested answers, not a closed enum |
display | Optional structured display copy |
metadata | Renderer or tool hints |
expiresAt | Optional auto-decline instant |
outcome | { type: "awaiting_user" } while it is pending |
Render that in your own surface, then resume the run with a single-question payload. { answer } for a question, { approved, feedback? } for a confirmation, and a decline is a resume with status cancelled and no payload.
This is the same shape an AG-UI style event stream wants: a typed request out, a typed answer back, with the run durable in between. Implementing it once gives every recipe you deploy a working question, without any recipe knowing what your surface is.