> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blobhub.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Interactive

> When the agent asks for input: pending_prompt and pending_prompt_resolved items

Claude Code and Codex occasionally pause to ask the user something — for approval before running a
risky command, to pick from a list of options, or for free-text input. The worker surfaces these as
structured thread items and resolves the user's reply back to the agent.

In v1 users answer with **plain text** in the standard thread widget. The structured `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 in `metadata.prompt.kind`.

| `kind`              | Raised by                                                                                                      | Shape                                                |
| ------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `approval`          | Claude Code's permission callback; each of Codex's three approval methods; a `guarded` policy verdict of `ask` | **Exactly one** question, options `Approve` / `Deny` |
| `ask_user_question` | Claude Code's in-process `ask_user` tool                                                                       | **One or more** questions, each independent          |
| `text`              | nothing today — reserved for a harness with a plain free-text prompt                                           | One question, no options                             |

**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:

| variant       | recognised by                          | answered with       |
| ------------- | -------------------------------------- | ------------------- |
| single-select | `options` present, `multiSelect` falsy | one label           |
| multi-select  | `options` present, `multiSelect: true` | zero or more labels |
| free text     | `options` absent or empty              | one string          |

**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](/worker/session-agent-harness/thread-object) 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.

```yaml theme={null}
content:
  - type: text
    text: |
      What's the deployment target?
        1. staging — push to staging.example.com
        2. production — push to www.example.com
metadata:
  type: pending_prompt
  prompt:
    prompt_id: prm_01J...
    kind: ask_user_question
    prompt_state: pending
    questions:
      - question: "What's the deployment target?"
        multiSelect: false
        options:
          - { label: staging, description: "push to staging.example.com" }
          - { label: production, description: "push to www.example.com" }
```

The agent's turn blocks at the tool call awaiting the answer; no further thread items are emitted
until the prompt resolves.

The `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`.

```yaml theme={null}
# answered
content:
  - type: text
    text: "✓ Answered: staging"
metadata:
  type: pending_prompt_resolved
  prompt:
    prompt_id: prm_01J...
    kind: ask_user_question
    prompt_state: answered            # answered | cancelled
    answers:
      - question: "What's the deployment target?"
        answer: [staging]
```

```yaml theme={null}
# cancelled
metadata:
  type: pending_prompt_resolved
  prompt:
    prompt_id: prm_01J...
    kind: ask_user_question
    prompt_state: cancelled
    reason: "the run ended"
```

## 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 carries `metadata.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.

```yaml theme={null}
content:
  - type: text
    text: "CSV import · KPI cards, Table · dark theme"   # a summary, for people reading the thread
metadata:
  answers:
    - { question: "Where should the data come from?", answer: ["CSV import"] }
    - { question: "Which sections?",                  answer: ["KPI cards", "Table"] }
    - { question: "Any preference for visual style?", answer: ["dark theme"] }
```

The set is **checked, not trusted**: there must be one entry per question and each `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.

<Note>
  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.
</Note>

### Text answers (the fallback)

With no `metadata.answers`, the **next inbound user item while a prompt is active** is the answer,
and its text is mapped per kind:

| kind                                   | mapping                                                                                                                                                                                                                            |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `text`                                 | `answer = [<raw text>]`.                                                                                                                                                                                                           |
| `approval`                             | Affirmative tokens (`approve`, `approved`, `yes`, `y`, `ok`, `allow`, `1`) → `["Approve"]`. **Everything else** → `["Deny"]` — deny on uncertainty, deliberately.                                                                  |
| `ask_user_question`, one question      | A bare number `N` → the Nth option's label; a case-insensitive match to a label → that label; anything else → the raw text, which is the "Other" path. With `multiSelect: true`, comma- or space-separated numbers select several. |
| `ask_user_question`, several questions | The same text is mapped against **each** question independently, so every question receives the same answer. Correct only when there is one question; send `metadata.answers` for anything else.                                   |

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 the `answers` the 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: cancelled` with `reason`.

There is no third state. A prompt is either answered or cancelled, and a worker that cannot post the
audit item logs the failure and leaves both sides open, which is recoverable — it does not invent a
resolution the agent never saw.

### Worker restart

An unresolved prompt **survives** a worker restart. The worker adopts it back off the thread on
attach — including its `questions`, 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.

### If the prompt cannot be posted

The question was never asked, and the agent is told so in those terms — *"Approval unavailable"*,
never *"the user denied"*. The call is still refused, but an agent told a human refused goes and
picks another approach, when the right move is to ask again.

## See also

* [Job Session Object](/worker/session-agent-harness/thread-object)
* [Thread items](/worker/session-agent-harness/thread-items)
* [Recovery](/worker/session-agent-harness/recovery)
* [Reference](/worker/session-agent-harness/reference)
