---
title: 'Human input'
description: 'Approvals, questions and forms that suspend a run until a person answers'
---
[View as Markdown](https://camelai.com/docs/camelrun/human-input.md) · [Docs index for coding agents](https://run.camelai.com/llms.txt)

A run can stop and wait for a person: the model asks a question, a tool needs
approval before it runs, or a tool asks for a form or a setup step. The run
**suspends**. It ends with `status: "input_required"` and the `inputs` it waits
on, and the agent sleeps (nothing is billed while it waits). Once the last input
is answered, minutes or days later and from any process, the turn resumes.

```ts TypeScript
let run = await agent.run("Delete the staging database", { user: "alice" });
while (run.status === "input_required") {
  const input = run.inputs[0];
  console.log(input.kind, input.message);            // "approval", "Allow db__drop to run?"
  run = await input.answer(true, { from: "alice" }); // resolves with the resumed run
}
console.log(run.text);
```

```python Python
run = await agent.run("Delete the staging database", user="alice")
while run.status == "input_required":
    run = await run.inputs[0].answer(True, from_="alice")
print(run.text)
```

## Where inputs come from

* **Approvals.** A tool with `needsApproval: true` (`@tool(needs_approval=True)`)
  is approved before each call. A definition's MCP and OpenAPI sources take an
  approval policy:
  `"approval": {"default": "never" | "always" | "destructive", "tools": {"delete_repo": "always"}}`.
  `destructive` means MCP tools annotated `destructiveHint`, and OpenAPI
  operations other than GET, HEAD and OPTIONS. The person sees the real call,
  and an approved call runs with exactly those arguments.
* **Questions.** The `ask_user` built-in lets the model ask 1 to 4
  multiple-choice questions, with free text where it allows.
* **The tool itself.** `context.confirm(message)`, `context.ask(message, schema)`
  (a flat form) and `context.requireUrl(url, message)` (a step on an https page,
  such as connecting an account).

```ts
const deleteApp = tool({
  description: "Delete an app",
  input: schema.Object({ app: schema.String() }),
  execute: async ({ app }, context) => {
    if (!await context.confirm(`Delete ${app}? Its URL stops working.`)) return { cancelled: true };
    return apps.delete(app, { idempotencyKey: context.idempotencyKey });
  },
});
```

> **Warning:**
>
> An ask ends the call. Once the person answers, camelRun calls the tool again
> with the same arguments and the ask returns the answer, so **everything before
> an ask runs again**. Ask first and act after, and key side effects by
> `context.idempotencyKey`, which is the same on both calls.

## Answering

`input.answer(value, { from })` (Python `answer(value, from_=)`) takes a plain value by the input's `kind`:

| Kind       | Value                                                                                                                |
| ---------- | -------------------------------------------------------------------------------------------------------------------- |
| `approval` | `true` to approve, `false` to decline                                                                                |
| `question` | The chosen option's label (or labels, or free text where allowed). For several questions, `{ "<question>": answer }` |
| `form`     | The fields, as an object. A confirmation (`context.confirm`, a form without fields) takes `true` or `false`          |
| `url`      | `true` once the person has done what the page asks                                                                   |

`input.decline()` declines any input. Answering the same way twice is safe; a
different answer to an input that already settled is a 409 that says how it
settled.

Inputs outlive your process, so you can answer later from anywhere:

* `agent.pendingInputs()` (Python `pending_inputs()`) lists an agent's waiting
  inputs.
* `agents.runtime.inbox("pending")` lists inputs across all your agents, for an
  approvals queue.
* Over REST: `GET /v1/agents/{id}/inputs?state=pending`, then
  `POST /v1/agents/{id}/inputs/{inputId}` with
  `{"action": "accept" | "decline" | "cancel", "content": ..., "from": ...}`.
* `onInput` on `upsert` hears each input as it's asked. Return an answer to give
  it right away, or nothing to answer later.
* Events `input_required` and `input_resolved`, and the webhooks
  `input.requested` and `input.resolved`.

## Who may answer

By default, the person whose message started the turn, plus the definition's
`humanInput.approvers`. An answer must say who is answering (`from`) whenever
the input names who may. A `from` who may not answer gets a 403. The model has no
way to answer.

## Waiting and giving up

* A new message to the agent supersedes its waiting inputs. Their calls close
  with "Not answered: the user sent a new message instead", then the message
  runs.
* `agent.abort()` cancels them.
* Inputs expire after `humanInput.expiresInSeconds`: 7 days by default, at most
  30\.

```json
{"name": "Ops", "builtins": ["ask_user"],
 "humanInput": {"expiresInSeconds": 86400, "approvers": ["slack:U0123"]},
 "mcpServers": [{"name": "github", "url": "...", "approval": {"tools": {"delete_repo": "always"}}}]}
```

In Slack, Telegram or Discord, a suspended turn's reply ends with its first
input as text. The next message from someone allowed to answer, if it fits (an
option's number, `approve` or `deny`, `done`), is the answer. See
[Channels](/docs/camelrun/channels).
