Human-in-the-Loop
Ask for approval, choices, or text through MCP, and handle unanswered questions safely.
This guide covers an agent asking you, the workspace operator, for a decision. If your product needs approval from your customers, use the Partner integration and its durable Decisions API.
Set up once
Run the quickstart to connect your agent and an approval channel. The setup installs the MCP connection and agent instructions, plus hooks where supported.
MCP supplies tools the agent can call. Enforced approval before other tool calls depends on the client's hook integration and your permission policies.
Ask and read the result
ask_user waits by default. It can return the answer in the first call;
there is no mandatory second wait.
{
"tool": "ask_user",
"arguments": {
"question": "Deploy this release to staging?",
"type": "confirm",
"agentName": "Codex - my-project",
"toolName": "Bash",
"toolTarget": "deploy staging"
}
}A successful answer returns:
{
"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.
| Question type | Input | Answer |
|---|---|---|
confirm | A yes/no question | "yes" or "no" |
select | A question and 2–6 options | The selected option string |
input | A question and optional placeholder | The user's text |
Use context for background, agentName to identify the project, and
toolName / toolTarget for a tool approval. Ask only when the user's input
is needed. A notification by itself does not approve an action.
Handle an unanswered question
Read handoffAction first when present; otherwise follow nextAction.
An unanswered result can mean a live timeout, a delivery-mode handoff, or a
terminal state.
- If
nextAction: "wait_for_answer"is returned, poll once with the samecorrelationIdandtimeoutMs: 55000. - If that poll is still pending, cancel the phone question before asking in the current chat.
- If cancellation returns
handoffAction: "stop", stop. Otherwise, if cancellation returns false, poll once for 1 second and honor any answer that won the race. - Ask in the current client only after the phone question is safely closed and no answer won the race.
{
"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. A stop directive must not be turned into
another approval request.
When showing a waiting state, include the returned answerUrl so the user can
open the dashboard question. For the full handoff contract, see
Unanswered questions.
Delivery mode controls where you answer
| Mode | Behavior |
|---|---|
When I'm out (push_first) | Uses presence to decide whether to send the phone push, and waits for the push-first window. |
Every time (push_only) | Sends to the phone and waits for the policy timeout. |
Updates (notify_only) | Sends awareness only; the approval belongs in the current client. |
Terminal (terminal_only) | Keeps the decision in the current client without a phone push. |
Always inspect the returned directives. A call waits at most 55 seconds; a question can remain answerable for 10 minutes. The wait ending is not consent.
Set wait: false only if your caller will collect the answer later.
Tool reference lists all parameters.
Cancel a stale question
Cancel if the action is abandoned, superseded, or the user responds in chat:
{
"tool": "cancel_question",
"arguments": {
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"handoff": true
}
}handoff: true is for moving the same question into the current client.
Use the cancellation race handling above before acting on a chat response.
Agree on a run's scope
For a run touching several files, propose_scope records the allowed paths,
off-limits paths, and definition of done under a session ID.
Only ratified: true establishes the server-recorded contract.
Scope narrows the file boundary; it does not approve risky actions inside it.
Hook enforcement requires a supported client with @pushary/agent-hooks
0.59.0 or later. Shell commands remain governed by the permission policy.
See propose_scope.