Agent approval
%%{init: {'look': 'handDrawn', 'theme': 'base', 'themeVariables': {'primaryColor': '#eef2ff', 'primaryBorderColor': '#1e40af', 'primaryTextColor': '#1e293b', 'lineColor': '#1e3a8a', 'edgeLabelBackground': '#ffffff', 'clusterBkg': '#fbfcff', 'clusterBorder': '#2563eb', 'fontFamily': '-apple-system, system-ui, Segoe UI, Roboto, Helvetica, Arial, sans-serif', 'fontSize': '15px'}, 'flowchart': {'nodeSpacing': 50, 'rankSpacing': 58, 'padding': 14, 'htmlLabels': true, 'curve': 'basis'}}}%%
flowchart LR
R(["Action request"]) --> A("Agent decides it wants<br/>to use a guarded tool")
A --> H[/"A human approves"/]
H --> A2("The same agent run<br/>picks up where it paused")
A2 --> O(["Result"])Outcome: pause a deployed agent at an explicit tool-approval boundary, collect the human decision, then resume the same agent execution.
Prerequisites and contract
Start the local MCP Testkit server and deploy the cookbook agents. The input is prompt; the first AGENT task returns waiting: true when the native request_notification tool needs approval. The local demo tool records only a notification request—it does not prove an external write. Never use a secret in workflow input.
Runnable definition
Save this as human-approved-action.json:
{
"name": "human_approved_external_action",
"version": 1,
"schemaVersion": 2,
"description": "Derived from ai/examples/32-conductor-agent-human-in-loop.json. An AGENT task pauses for a guarded tool approval, a HUMAN task collects the decision, and a second AGENT task resumes the same run.",
"tasks": [
{
"name": "run_agent",
"taskReferenceName": "run_agent_ref",
"type": "AGENT",
"inputParameters": {
"agentType": "conductor",
"name": "guarded-incident-planner",
"prompt": "${workflow.input.prompt}"
}
},
{
"name": "check_waiting",
"taskReferenceName": "check_waiting_ref",
"type": "SWITCH",
"evaluatorType": "value-param",
"expression": "waiting",
"inputParameters": {
"waiting": "${run_agent_ref.output.waiting}"
},
"decisionCases": {
"true": [
{
"name": "collect_answer",
"taskReferenceName": "collect_answer_ref",
"type": "HUMAN",
"asyncComplete": true
},
{
"name": "resume_agent",
"taskReferenceName": "resume_agent_ref",
"type": "AGENT",
"inputParameters": {
"agentType": "conductor",
"name": "guarded-incident-planner",
"executionId": "${run_agent_ref.output.executionId}",
"prompt": "${collect_answer_ref.output.answer}"
}
}
]
},
"defaultCase": []
}
]
}
Register and run
conductor workflow create human-approved-action.json
conductor workflow start -w human_approved_external_action --sync -u collect_answer_ref -i '{"prompt":"Request a notification to oncall@example.com that incident INC-1001 needs attention."}'
Complete collect_answer_ref only after reviewing the pending tool request. On OSS Conductor, use the task-by-reference endpoint and provide the answer the agent should receive:
curl -X POST '<YOUR-CLUSTER-URL>/api/tasks/WORKFLOW_ID/collect_answer_ref/COMPLETED/sync' \
-H 'Content-Type: application/json' \
-d '{"answer":"approved"}'
Production notes
- The agent resumes by
executionId, so it can't re-plan a different action after approval. - Record who approved, the policy version, and what they saw.
- Before swapping in a write-capable tool, add an idempotency key and a check-before-retry.
- The Testkit tool only records a request. It is not proof that a real write works.