Skip to content

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.