# Ask a person mid-run

Agent Mission Control developer docs > Bring your own agent

> Pause a run on a question, have AMC route it to the right person, and resume when their answer arrives.

**Before you start:** An endpoint that serves `/status`. See [Run status and steps](/developers/byo-run-status/).

Your agent asks a person a question by parking a step and saying so in its status. AMC opens an **intervention**, routes it, tracks its SLA, and posts the answer back to your endpoint.

## The flow

1. Your agent parks a step: step `status` becomes `waiting`, the question goes in that step's `delivered`, and the run `status` becomes `human_intervention`.
2. On its next poll AMC finds the waiting step and opens an intervention for it, with your question as the request. It appears in the Review queue, the bell, and email.
3. A person replies in AMC. AMC calls `POST /runs/{id}/messages` with the answer.
4. Your agent resumes from the parked step, sets the run back to `running`, and returns the new status.

`GET /runs/{id}/status while waiting`
```json
{
  "orchestratorRunId": "run_5f2c0e8a41b94c55",
  "status": "human_intervention",
  "currentStep": 3,
  "totalSteps": 3,
  "steps": [
    { "stepId": "fetch", "name": "Fetch invoice", "status": "completed", "tokenCount": 0 },
    { "stepId": "summarize", "name": "Summarize", "status": "completed", "tokenCount": 4810 },
    {
      "stepId": "approve",
      "name": "Approve summary",
      "status": "waiting",
      "startedAt": "2026-10-17T09:31:00.000000+00:00",
      "tokenCount": 0,
      "delivered": "The total is above the EUR 5,000 limit. Approve the summary for INV-1042?"
    }
  ],
  "assigneeRef": "sam@contoso.com"
}
```

> **Important:** The run status must be `human_intervention`. AMC opens an intervention only for that value. A run reported as `waiting` shows as **Intervention** but gives nobody anything to answer.

- Put the question in `delivered` on the waiting step. Without it the intervention opens with only the step name.
- Park one step at a time. If several report `waiting`, AMC picks the one that started first.
- Waiting holds the run, not your service: keep serving other runs.
- Add a `step.paused` event to the live stream if you have one, so a watching browser shows the pause at once.

## The answer

`POST /runs/{id}/messages`
```json
{
  "orchestratorRunId": "run_5f2c0e8a41b94c55",
  "stepId": "approve",
  "message": "Approved. Use the March exchange rate.\n\n[Attached file: rates.pdf (file, 82 KB)]",
  "attachments": [
    { "kind": "file", "filename": "rates.pdf", "mediaType": "application/pdf",
      "sizeBytes": 84213, "foundryFileId": null }
  ]
}
```

- `message` is the person's reply. When they attach files, AMC appends one `[Attached file: name (kind, size)]` line per file and lists them in `attachments`. Without files, `attachments` is left out.
- Your agent never receives a file or a link to one. Attachments stay inside AMC, scanned for malware; people download them there.
- Return the run status after the answer, as JSON (`200`). The run should read `running`, or terminal if the answer finished it.
- Return `409` when the run isn't waiting, or `stepId` isn't the waiting step. Return `404` for an unknown run.

AMC calls your endpoint before it records the reply. If you return an error, nothing is recorded, the intervention stays open, and the person can send again.

`Park and resume (FastAPI, in-memory run store)`
```python
import asyncio

from fastapi.responses import JSONResponse
from pydantic import BaseModel


class SendMessage(BaseModel):
    orchestratorRunId: str | None = None
    stepId: str | None = None
    message: str = ""


async def ask_human(run: dict, step_id: str, question: str) -> str:
    run["answer"] = asyncio.get_running_loop().create_future()
    run["waitingStep"] = step_id
    set_step(run, step_id, "waiting", startedAt=now(), delivered=question)
    run["status"] = "human_intervention"
    emit(run, "step.paused", step_id)
    try:
        return await run["answer"]
    finally:
        run["answer"] = None
        run["waitingStep"] = None
        run["status"] = "running"
        set_step(run, step_id, "running")


@app.post("/runs/{run_id}/messages")
async def send_message(run_id: str, body: SendMessage):
    run = get_run(run_id)
    answer = run.get("answer")
    if answer is None or answer.done():
        return JSONResponse(status_code=409, content={"error": "run_not_waiting"})
    if body.stepId and body.stepId != run["waitingStep"]:
        return JSONResponse(status_code=409, content={"error": "step_not_waiting"})
    answer.set_result(body.message)
    await asyncio.sleep(0)  # let the run resume before reporting its status
    return await run_status(run_id)
```

The helpers (`set_step`, `emit`, `get_run`, `run_status`, `now`) are the ones in the [minimal endpoint](https://www.agentmissioncontrol.ai/developers/byo-test-locally/). In the run, call `answer = await ask_human(run, "approve", "Approve the summary?")`, then complete the step.

## Who gets the question

An intervention goes to the run's assignee. The run gets one from:

- the **Assignee** picked in **Run now**, or `assigneeId` in a webhook body;
- an input whose parameter is marked `"role": "assignee"` (see [Declare run parameters](https://www.agentmissioncontrol.ai/developers/byo-input-contract/));
- your agent, by reporting `assigneeRef` (an email) on `/status`. Use this when a workflow runs once per person and asks each about their own data.

With no run assignee, the question goes to the workflow's **Default assignee**, and failing that to any manager of its department. AMC resolves `assigneeRef` to a user in the tenant, and drops it (with an audit entry) when that person isn't in the workflow's department or can't see a sensitive workflow. An omitted `assigneeRef` never unassigns a run.

## When nobody answers

AMC re-notifies and escalates on the intervention's SLA (see [Set intervention SLAs and upload limits](https://www.agentmissioncontrol.ai/guide/intervention-settings/)). Your run keeps waiting; give it your own deadline if it must not wait forever. If the run ends while waiting, for example because someone cancels it, AMC withdraws the intervention. The person's side is in [Respond to a human intervention](https://www.agentmissioncontrol.ai/guide/respond-to-interventions/).

## Related

- [Run status and steps](https://www.agentmissioncontrol.ai/developers/byo-run-status/)
- [Declare run parameters](https://www.agentmissioncontrol.ai/developers/byo-input-contract/)
- [The amc_sdk Python package](https://www.agentmissioncontrol.ai/developers/byo-amc-sdk/)

---
Source: https://www.agentmissioncontrol.ai/developers/byo-human-in-the-loop/ · Last updated 2026-10-21 · Full index: https://www.agentmissioncontrol.ai/llms.txt
