---
title: "Asking people"
description: "Let an agent ask a person a question in the middle of a job, and wait durably for the answer."
canonical_url: "https://docs.oz0.ai/agents/asking-people"
---
# Asking people

> Let an agent ask a person a question in the middle of a job, and wait durably for the answer.

For: people running and reviewing agents.

Give an agent `oz.ask_user` when its work needs a person's answer, such as which customer an order belongs to, whether to refund it or which branch to release. The built-in tool asks the question and waits for a reply.

```yaml [agents/acme-support.yaml]
name: acme-support
runtime: pydantic-ai
model:
  primary: anthropic/claude-sonnet-5-5
tools: [acme_crm.find_order, acme_crm.refund, oz.ask_user]
ask_user:
  timeout: 4h            # how long a question waits; 24h when unset
  on_timeout: continue   # continue (the default) or fail
```

The model sees a tool `oz__ask_user` with two arguments: the `question`, written so that a person who has not seen the conversation can answer it, and optional `choices`, the answers to pick from. A call that does not fit, such as one with an argument it does not take or `choices` that is not a list of strings, goes back to the model with the reason, and it tries again; it counts against the tool's retries like any other refused call.

## Typed questions

To ask for a date, a number or a pick from a list rather than words, the model passes `fields` instead of `choices`: at most 20 fields in [the standard field set](https://docs.oz0.ai/agents/approvals/edits-and-repeat-calls#the-field-set), each with a `name` (lower case letters, digits and `_`), a `kind` (`text`, `number`, `choice`, `multi-choice`, `date`, `toggle`), and optionally a `label`, a `description`, `required`, `options` for a choice, `min`, `max` and `integer` for a number, `max_length` and `multiline` for text, and `date_time` for a date that holds a time.

The fields take at most 16 KiB in all, as JSON; the model is told why when it asks with more:

```json
{"question": "When should I send the report, and how many copies?",
 "fields": [{"name": "on", "kind": "date", "label": "Send on", "required": true},
            {"name": "copies", "kind": "number", "integer": true, "min": 1, "max": 9}]}
```

The answer is one JSON object of the fields' values, such as `{"on": "2026-10-20", "copies": 2}`, which the agent reads as `The answer: {...}`. The job checks it before it takes it: a missing required field, a number out of its bounds (or not a finite number), a choice that is not one of the options or a date that does not read is refused with the field's words, and the question keeps waiting.

The console draws the fields as a form; Slack and Teams draw them in their own forms; Discord offers **Open in OZ**, and the post says to answer with the form where the chat offers one, or in OZ. A question takes `choices` or `fields`, not both.

## What happens

1. The agent calls `oz.ask_user`. The job records the question and waits. The question shows up on the console's **Approvals** page, on the job's page, in `oz0 approval list`, and in the job's live stream.
2. Someone answers it, or declines to.
3. Answered: the agent reads `The answer: ...` as the tool's result and goes on. **Declined:** the agent reads who declined and why, for example `user:kevin declined to answer: ask Ana.`
4. Nobody answers in time: with `on_timeout: continue`, the agent reads `Nobody answered within 4:00:00.` and decides itself whether to go on or stop. With `on_timeout: fail`, the job fails with `QuestionTimedOut`.

A question is a durable wait in the job's Temporal workflow, like an [approval](https://docs.oz0.ai/agents/approvals). It costs nothing while it waits and survives restarts of servers and nodes. The question, the answer and who gave it are in the job's history, and the job's [trace](https://docs.oz0.ai/agents/history) shows the wait as a step.

## The escalation ladder

A question climbs the same [escalation ladder](https://docs.oz0.ai/agents/approvals/configure#the-escalation-ladder) as an approval, counted from its own `timeout`: a reminder to the people who may answer at a quarter, the agent's owner at half and the tenant's admins at three quarters, each by email, webhook or chat when the tenant has a [notification rule](https://docs.oz0.ai/admin/notifications) for `question.waiting` or `approval.escalated`. The agent's `approvals: {escalation: ...}` reshapes it or turns it off (with `escalation_reason`). At the timeout the question ends as before: `on_timeout` decides, and nobody's silence is ever read as an answer. Questions cannot be edited; answer, or decline with a comment.

## Answer from the console

**Approvals** lists questions next to approvals, with the kind `question`. Open one to see the question. When it offers choices, each is a button; when it asks fields, fill in the form (it says what is missing before it sends); otherwise type the answer and press **Answer**. **Decline** tells the agent you will not answer; write why in the field next to it first if you want the agent to know.

Operators, admins and owners of the tenant answer. Readers see that a question waits, but not the question, unless they may see the tenant's content. An operator without the content permission does not see the question either, so they cannot answer it; they can only decline.

## Answer from the CLI

```bash [Terminal]
oz0 approval list
oz0 approval answer job-0a9dc2d1-... a-75eaf712473f blue
oz0 approval deny job-0a9dc2d1-... a-75eaf712473f --comment "ask Ana"
```

When the question has choices, the answer must be one of them; a refusal does not list them, because they are the job's content. A typed question takes one JSON object: `oz0 approval answer <job> <id> '{"on": "2026-10-20", "copies": 2}'` (`approval list` shows its fields as `{on: date, copies: number}`). `oz0 run` and `job watch` show the question as it is asked, with the command that answers it:

```text
asker → oz.ask_user {"choices":["red","blue"],"question":"Which colour should the report use?"}
asker ? asks a-75eaf712473f: Which colour should the report use? [red / blue]
  oz0 approval answer job-0a9dc2d1-... a-75eaf712473f "..."   (or deny)
asker ▶ operator:admin answered: blue
asker ← oz.ask_user The answer: blue
```

## From your own app

`ListApprovals` in the [runtime API](https://docs.oz0.ai/build/apps/runtime-api) returns questions with `kind: "question"`, the question in `details`, the answers to pick from in `choices`, and a typed question's `fields` (`FormField`; the feed's `FeedQuestion` and the chat model's `ChatRequest` carry them too, all only with the content permission). Answer with `DecideApproval`, `approve: true` and the `answer`; `approve: false` with an optional `comment` declines.

Only a question takes an answer: an answer sent to any other kind of approval, or together with `approve: false`, is refused (`FailedPrecondition`), so `approval answer` can never approve a tool call by mistake, and a decline never shows as an answer.

The server checks this against the job's pending approvals before the answer reaches the job, so it also holds for jobs still running on an older runtime version. An answer from a caller without the content permission is refused with `PermissionDenied`: they cannot see what they would answer.

## Harness agents

A [harness agent](https://docs.oz0.ai/build/parts/harnesses) asks the same way. List `oz.ask_user` in its `tools:`, and the Claude harness gives the session the SDK's `AskUserQuestion` tool: each of its questions waits in the inbox, and the answers go back to the session.

An [ACP agent](https://docs.oz0.ai/build/parts/harnesses/acp#what-a-session-gets-1) asks with ACP's form elicitation: a form of one field is one question (with its choices), and a form of two to 20 fields is one typed question, each property in the field set where it maps and as text where it does not (an object property is answered as JSON). Only a harness that asks before each tool call (`permissions: true`) can ask people.

A client that started the job over [MCP](https://docs.oz0.ai/build/apps/mcp-server#calls-that-wait-and-calls-that-are-tasks) sees a question as the task's `input_required`, and can answer it there.

A session's question waits no longer than the session may run: its agent's `limits.timeout`, or two hours when that is unset, counted from when the session started. The inbox shows that deadline. When the session ends, its open questions leave the inbox, and an answer after that has no one to go to.

<note>

`oz.ask_human`, the name lint accepted before the tool was built, still works and is the same tool; lint suggests the new name.

</note>

<span id="answer-from-the-web-ui">

answer from the web ui

</span>

: [Read the section](https://docs.oz0.ai/agents/asking-people#answer-from-the-console).


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
