# Human in the loop

Source: https://pushary.com/docs/agents/human-in-the-loop
Last updated: 2026-10-07

> Add human in the loop approvals to your AI agent with Pushary's ask_user MCP tool. Ask for a yes or no, a choice or text, and handle unanswered questions.

Human in the loop means your AI agent stops and asks you before it continues. With Pushary, the agent calls the `ask_user` MCP tool, you answer on your phone, and the agent reads your answer from the same call. This page covers an agent asking **you**, the workspace owner. If your product needs approval from **your customers**, use the [Partner integration](/docs/agents/embed) and its durable Decisions API.

Connect your agent and your phone first:

```bash
npx pushary@latest setup
```

Setup installs the MCP connection and agent instructions, plus hooks where the agent supports them. MCP gives the agent tools it can call. Approval before other tool calls is enforced only through the agent's hook integration and your [permission policies](/docs/agents/policies).

## Ask a question and read the answer [#ask-a-question-and-read-the-answer]

`ask_user` **waits by default**. It can return the answer in the first call, with no second call needed.

```json
{
  "tool": "ask_user",
  "arguments": {
    "question": "Deploy this release to staging?",
    "type": "confirm",
    "agentName": "Codex - my-project",
    "toolName": "Bash",
    "toolTarget": "deploy staging"
  }
}
```

An answered question returns:

```json
{
  "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "question": "Deploy this release to staging?",
  "type": "confirm",
  "status": "answered",
  "answered": true,
  "value": "yes"
}
```

For a confirm question, proceed only on `answered: true` with `value: "yes"`. `answered: true` with `value: "no"` is a refusal. An unanswered question never becomes an approval.

| Question type | You send                                 | The answer               |
| ------------- | ---------------------------------------- | ------------------------ |
| `confirm`     | A yes or no question                     | `"yes"` or `"no"`        |
| `select`      | A question and 2 to 6 `options`          | The chosen option string |
| `input`       | A question and an optional `placeholder` | The text the user typed  |

Use `context` for background, `agentName` to name the project, and `toolName` and `toolTarget` for a tool approval. Ask only when you need the user's input. A notification on its own does not approve an action.

## Handle a question nobody answered [#handle-a-question-nobody-answered]

Read `handoffAction` first when it is present. Otherwise follow `nextAction`. An unanswered result can mean a live timeout, a delivery mode handoff, or a final state.

1. If the result has `nextAction: "wait_for_answer"`, keep polling with the same
   `correlationId` and `timeoutMs: 55000`.
2. Only for an explicit handoff, cancel the phone question before asking in the
   current chat.
3. If cancellation returns `handoffAction: "stop"`, stop. Otherwise, if
   cancellation returns false, poll once for 1 second and honor any answer that
   won the race.
4. Ask in the current client only after the phone question is safely closed and
   no answer won the race.

```json
{
  "tool": "wait_for_answer",
  "arguments": {
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timeoutMs": 55000
  }
}
```

Only `pending` is a live unanswered state. `cancelled`, `expired`, `missing` and `unavailable` are not timeouts. Never turn a `stop` directive into another approval request.

While the agent waits, show the returned `answerUrl` so the user can open the question in the dashboard. For the full handoff contract, see [When you don't answer](/docs/agents/unanswered).

## Where you answer: delivery modes [#where-you-answer-delivery-modes]

| Mode                       | What happens                                                                                 |
| -------------------------- | -------------------------------------------------------------------------------------------- |
| When away (`push_first`)   | Uses presence to decide whether to send the phone push, and waits for the push-first window. |
| Always (`push_only`)       | Sends to the phone and waits for the policy timeout.                                         |
| Alert only (`notify_only`) | Sends an alert only. The approval belongs in the current client.                             |
| In agent (`terminal_only`) | Keeps the decision in the current client, with no phone push.                                |

In Claude Code, a command your own Claude Code allow rules already cover is left to Claude Code in every mode. The exceptions are a command that a Pushary rule or preset you chose names, and one that looks risky. Pushary records no decision for it.

Always read the returned directives. One call waits at most 55 seconds. Ordinary full-access MCP questions without a host deadline stay answerable until answered or cancelled, with one phone reminder after ten minutes when delivery settings allow it. Restricted connectors and native permission hooks retain their existing deadlines. The end of a wait is not consent.

Set `wait: false` only if your code will collect the answer later. The [tools reference](/docs/agents/reference/tools#ask_user) lists every parameter.

## Cancel a question you no longer need [#cancel-a-question-you-no-longer-need]

Cancel when the action is abandoned or replaced, or the user answers in chat:

```json
{
  "tool": "cancel_question",
  "arguments": {
    "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "handoff": true
  }
}
```

Use `handoff: true` when you move the same question into the current client. Apply the race handling above before you act on a chat answer.

## Agree on a run's scope [#agree-on-a-runs-scope]

For a run that touches several files, `propose_scope` records the allowed paths, the off-limits paths and the definition of done under a session ID. Only `ratified: true` creates the contract on the server.

Scope **narrows** the file boundary. It does not approve risky actions inside it. Hook enforcement needs a supported agent with `@pushary/agent-hooks` 0.59.0 or later. Shell commands stay governed by your permission policy. See [propose\_scope](/docs/agents/reference/tools#propose_scope).

## Next steps [#next-steps]

<Cards>
  <Card title="See what happens when you don't answer" description="The wait ladder for each approval mode, and how to tune the window." href="/docs/agents/unanswered" />

  <Card title="Set your permission policies" description="Choose which tools ask you, how long they wait, and what happens next." href="/docs/agents/policies" />

  <Card title="Read the tools reference" description="Every argument and return field for ask_user, wait_for_answer and cancel_question." href="/docs/agents/reference/tools" />
</Cards>