metadata
preserves the agent’s full prompt payload so a future native renderer (approval buttons, choice
pickers, multi-question forms) can pick it up without protocol changes.
Prompt kinds
Two kinds are raised in practice. The worker preserves the kind inmetadata.prompt.kind.
Codex has no
ask_user. Approvals are its only channel to a human, so a Codex agent asks for
input by requesting approval or not at all.
Within ask_user_question the variants are per question, not per prompt — one call can mix them:
Every option list also takes free text. The tool tells the agent so — “the user can always answer
with free text via ‘Other’” — and the worker honours it: a token matching no option is handed back
verbatim. A client that offers only the listed options is narrower than the contract.
A
kind: approval prompt is the user-facing side of the agent’s permissions: approval setting: when an
agent runs under approval, each sensitive action is routed here for a human decision before it proceeds.
If no human answers, the turn ends and waits — it is not denied, and the wait outlives the worker. Under
permissions: autonomous there is no approval gate, so the agent does not raise these prompts. See
the permissions setting for how it is resolved.
pending_prompt (worker → thread)
Posted when the agent’s turn pauses for input. The user is expected to reply by posting one or more
thread items.
content is the question and nothing else — no header line announcing that a question is being
asked, and no instructions for answering. metadata.type is what says the item is a prompt, and
clients are expected to render their own affordance from it rather than reading one out of the text.
The options stay numbered, because the numbering is part of the question: it is what the user is
choosing between, and a reply of 2 is resolved against it.
There is at most one active prompt per thread at any time.
pending_prompt_resolved (worker → thread, audit)
Posted once the prompt has been answered or cancelled. Audit-only — the worker has already delivered
the answer to the agent before this item lands.
The two states carry different fields, and never both: an answered prompt carries answers and no
reason; a cancelled one carries reason and no answers.
Answering
An answer reaches the worker as an inbound thread item. There are two ways to give one, and they are not equally good.Structured answers (what a surface should send)
If the item carriesmetadata.answers — one entry per question, in the same shape the agent’s tool
returns — the worker uses it as it stands. No parsing, no matching text against options, no
guessing which answer belongs to which question.
answer must be a
list. A partial set is refused and the text is mapped instead — padding it would report a question
answered that nobody answered.
This is the only correct way to answer a prompt of several questions. A text answer is mapped
against every question in the prompt, so one string becomes the answer to all of them.
Text answers (the fallback)
With nometadata.answers, the next inbound user item while a prompt is active is the answer,
and its text is mapped per kind:
Items posted while no prompt is active are queued as the next agent turn’s prompt, as usual.
Lifecycle and audit
- Answered — the agent receives the answer and continues. Audit item
prompt_state: answered, carrying theanswersthe agent was given, however they were arrived at. - Cancelled — the prompt was abandoned: the run ended, or a second question superseded it. Audit
item
prompt_state: cancelledwithreason.
Worker restart
An unresolved prompt survives a worker restart. The worker adopts it back off the thread on attach — including itsquestions, which is what lets an answer be matched to them — and answering
it a week later resumes the same agent session.
That adoption is load-bearing rather than cosmetic. The record of which prompt is open lives in
memory, so a worker that came back without it would take the answer for an ordinary message: the
agent would resume looking healthy, no pending_prompt_resolved would ever be posted, and anything
reading “unresolved prompt” as “waiting for input” would wait forever.

