Ask a person mid-run
Pause a run on a question, have AMC route it to the right person, and resume when their answer arrives.
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
- Your agent parks a step: step
statusbecomeswaiting, the question goes in that step'sdelivered, and the runstatusbecomeshuman_intervention. - 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.
- A person replies in AMC. AMC calls
POST /runs/{id}/messageswith the answer. - Your agent resumes from the parked step, sets the run back to
running, and returns the new status.
{
"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"
}- Put the question in
deliveredon 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.pausedevent to the live stream if you have one, so a watching browser shows the pause at once.
The answer
{
"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 }
]
}messageis the person's reply. When they attach files, AMC appends one[Attached file: name (kind, size)]line per file and lists them inattachments. Without files,attachmentsis 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 readrunning, or terminal if the answer finished it. - Return
409when the run isn't waiting, orstepIdisn't the waiting step. Return404for 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.
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. 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
assigneeIdin a webhook body; - an input whose parameter is marked
"role": "assignee"(see Declare run parameters); - 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). 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.
Related
Last updated 2026-10-21