Skip to content

Production agent architecture

For a runnable implementation of the pattern, see Durable Adaptive Graphs, which builds a governed PR-review agent with bounded fan-out and human approval before its single side effect.

The parent workflow reference path

Every path starts and ends in the parent workflow: validate the request, choose an execution boundary, validate the returned result, then apply approval, writes, or compensation. The parent owns the business process; each agent path owns only the work behind its boundary.

Production agent parent workflow A parent workflow validates a request, chooses native tasks, a deployed Conductor Agent, or a remote A2A agent, validates the returned result, and then obtains approval before writing or compensates on failure. Native and deployed-agent paths are observable in Conductor; an A2A handoff is observable at the parent boundary while its internals remain remote. Parent workflow — durable business-process boundary Validate request shape, policy, IDs Choose boundary Native tasks LLM, MCP, control flow execution + observability: Conductor Conductor Agent AGENT: agentType conductor execution + observability: Conductor Remote A2A agent AGENT: agentType a2a handoff observable; internals remote Validate result schema, policy, artifacts Approve then write or compensate on failure

Choose the execution boundary

The parent workflow can use one or more of these execution paths. Choose the path based on where the agent behavior belongs; all three participate in the same durable business process.

  • Native AI tasks run directly in the workflow graph. Use LLM_CHAT_COMPLETE, MCP tasks, HUMAN, and control-flow tasks when the workflow definition is the agent implementation.
  • Deployed Conductor Agents run through an AGENT task with agentType: "conductor". They include agents authored with a Conductor SDK or framework bridges for OpenAI Agents, Google ADK, LangChain, LangGraph, and Vercel AI SDK. Conductor compiles these agents into deployed workflow graphs.
  • Remote A2A agents run through an AGENT task with agentType: "a2a". This is a durable handoff to an independently deployed Agent2Agent service: Conductor manages the parent-workflow lifecycle, while the remote service keeps its own implementation and internals.

agentType selects the execution mode; it does not name an authoring framework. Use SUB_WORKFLOW or START_WORKFLOW to compose child workflows, and use AGENT when the parent invokes an agent runtime.

Boundary Use it when Execution and observability
Native tasks The workflow graph owns the orchestration and agent behavior. Native system tasks execute and are observable in Conductor.
AGENT / agentType: "conductor" The agent is authored in a Conductor SDK or a supported framework bridge: OpenAI Agents, Google ADK, LangChain, LangGraph, or Vercel AI SDK. Conductor compiles and runs the deployed agent graph, so its execution is observable in Conductor.
AGENT / agentType: "a2a" A specialist is independently deployed as a remote A2A service. Conductor observes the durable handoff, lifecycle, and returned artifacts; the remote agent owns its private internals.
SUB_WORKFLOW / START_WORKFLOW You are composing another Conductor workflow, synchronously or fire-and-forget. These compose workflow definitions; they do not invoke either AGENT runtime mode.

Production contract at every agent boundary

Decision Default production contract
Input and output Define and validate input before the boundary and output after it; do not let an unvalidated model or remote response decide a consequential action.
Identity and side effects Carry a correlation ID and idempotency key into external effects and remote handoffs. Treat every tool and remote-agent side effect as at-least-once; use idempotency or an explicit reconciliation marker.
State owner Keep orchestration state in workflow variables, resumable deployed-agent state behind its execution ID, and remote continuation state in A2A context and task IDs.
Durable payload Return small durable artifacts and references, not raw histories or large payloads.

Production readiness

  • Resolve credentials server-side. Never put secrets in prompts or workflow input.
  • Use least-privileged tools, validate outputs, and require human approval before consequential writes.
  • Bound turns, parallelism, time, tokens or cost, retries, cancellation, and compensation behavior.
  • Name an owner and define one correlation-ID convention. Monitor terminal state, duration, retries, timeout or cancellation, tool failures, budget exhaustion, and approval age.
  • Run one recovery drill: interrupt a safe execution, locate it by correlation ID, retry, resume, or terminate as appropriate, and verify the audit trail.
  • Keep releases KISS: test the changed path against sandbox tools, deploy it, and retain a known-good definition for rollback.

For implementation details, see Conductor Agents, Framework Agent Bridges, A2A Integration, Guardrails, Evals, Failure Semantics, and Durable Adaptive Graphs.

Native-task implementation: architecture diagram

DO_WHILE — Agent Loop (checkpointed per iteration) Start Discover Tools LIST_MCP_TOOLS Initialize Memory SET_VARIABLE Plan Next Action LLM_CHAT_COMPLETE SWITCH done? done = true needs_approval Human Approval HUMAN (durable pause) execute Execute Tool CALL_MCP_TOOL ! auto-retry Update Memory SET_VARIABLE Budget check next iteration budget exceeded End On failure: failureWorkflow runs compensation Every step persisted Prompt, response, tokens, timing

The canonical agent pattern

A production agent has these concerns. Each one maps to a specific Conductor primitive:

Agent concern Conductor primitive How it works
Plan next action LLM_CHAT_COMPLETE LLM receives goal + context + tool list, returns structured plan
Select an approved tool at runtime SWITCH + guarded CALL_MCP_TOOL The LLM proposes a route; the graph revalidates capability selection before execution.
Execute tool CALL_MCP_TOOL, HTTP, or SIMPLE worker Tool runs with retry policy, timeout, and full I/O recording
Retry with backoff Task definition retryLogic FIXED, EXPONENTIAL_BACKOFF, or LINEAR_BACKOFF — no code needed
Parallel tool calls FORK/JOIN or FORK_JOIN_DYNAMIC Fan out to a bounded set of tools in parallel, then join their results
Memory / context handoff SET_VARIABLE + workflow variables Accumulate results across loop iterations; pass to next LLM call
Human approval gate HUMAN task Durable pause. Survives restarts and deploys. Resumes on API signal.
Long wait (hours/days) WAIT task Timer-based durable pause. Survives server restarts.
Resume from external event HUMAN task + webhook/API External system calls Task Update API. Workflow resumes with payload.
Reflection / evaluation loop DO_WHILE with LLM-as-judge Second LLM evaluates output quality; loop continues if below threshold
Budget / iteration cap DO_WHILE loopCondition iteration < maxIterations or token/cost check in loop condition
Termination criteria DO_WHILE exit + SWITCH LLM sets done: true, or evaluator decides goal is met
Invoke a deployed specialist agent AGENT with agentType: "conductor" Run a deployed Conductor Agent by name; its compiled graph is visible in Conductor.
Hand off to a remote specialist agent AGENT with agentType: "a2a" Call a remote A2A service; Conductor persists the handoff, lifecycle, and returned artifacts at the parent boundary.
Compose a child workflow SUB_WORKFLOW or START_WORKFLOW Use SUB_WORKFLOW when the parent waits, or START_WORKFLOW for fire-and-forget workflow composition.
Compensation on failure failureWorkflow Undo side effects: revoke API calls, send notifications, release resources
Audit trail Automatic Every task's input, output, timing, retry count, and worker ID is persisted

Native-task implementation: end-to-end workflow

The runnable source of truth for the native-task path is ai/examples/35-governed-adaptive-agent.json in this repository's AI examples directory. Every step is a native system task or operator — no custom code or external framework. The compact JSON below is a conceptual baseline for that path; use the governed PR reviewer when deploying it because it adds the production guardrails described above.

{
  "name": "production_agent",
  "description": "Reference architecture: durable production agent",
  "version": 1,
  "schemaVersion": 2,
  "inputParameters": ["goal", "mcpServerUrl", "maxIterations"],
  "tasks": [
    {
      "name": "discover_tools",
      "taskReferenceName": "discover",
      "type": "LIST_MCP_TOOLS",
      "inputParameters": {
        "mcpServer": "${workflow.input.mcpServerUrl}"
      }
    },
    {
      "name": "initialize_memory",
      "taskReferenceName": "init_memory",
      "type": "SET_VARIABLE",
      "inputParameters": {
        "last_action": "",
        "last_result": "",
        "final_answer": ""
      }
    },
    {
      "name": "agent_loop",
      "taskReferenceName": "loop",
      "type": "DO_WHILE",
      "loopCondition": "$.plan['route'] != 'done' && $.loop['iteration'] < $.maxIterations",
      "inputParameters": {
        "maxIterations": "${workflow.input.maxIterations}"
      },
      "loopOver": [
        {
          "name": "plan_next_action",
          "taskReferenceName": "plan",
          "type": "LLM_CHAT_COMPLETE",
          "inputParameters": {
            "llmProvider": "anthropic",
            "model": "claude-sonnet-4-20250514",
            "messages": [
              {
                "role": "system",
                "message": "You are a production AI agent. Goal: ${workflow.input.goal}\n\nAvailable tools: ${discover.output.tools}\n\nMost recent action: ${workflow.variables.last_action}\nMost recent result: ${workflow.variables.last_result}\n\nRespond with JSON only. Use {\"route\": \"execute\", \"action\": \"tool_name\", \"arguments\": {}, \"reasoning\": \"why\"} for a safe tool call, {\"route\": \"needs_approval\", \"action\": \"tool_name\", \"arguments\": {}, \"reasoning\": \"why\"} for a reviewable tool call, or {\"route\": \"done\", \"answer\": \"final answer\"} when complete."
              }
            ],
            "temperature": 0.1,
            "maxTokens": 1000,
            "jsonOutput": true
          }
        },
        {
          "name": "check_if_done",
          "taskReferenceName": "done_check",
          "type": "SWITCH",
          "evaluatorType": "value-param",
          "expression": "route",
          "inputParameters": {
            "route": "${plan.output.result.route}"
          },
          "decisionCases": {
            "needs_approval": [
              {
                "name": "human_approval",
                "taskReferenceName": "approval",
                "type": "HUMAN",
                "inputParameters": {
                  "plannedAction": "${plan.output.result.action}",
                  "arguments": "${plan.output.result.arguments}",
                  "reasoning": "${plan.output.result.reasoning}",
                  "goal": "${workflow.input.goal}"
                }
              },
              {
                "name": "execute_approved_tool",
                "taskReferenceName": "approved_tool_call",
                "type": "CALL_MCP_TOOL",
                "inputParameters": {
                  "mcpServer": "${workflow.input.mcpServerUrl}",
                  "method": "${plan.output.result.action}",
                  "arguments": "${plan.output.result.arguments}"
                }
              },
              {
                "name": "update_memory_approved",
                "taskReferenceName": "mem_update_approved",
                "type": "SET_VARIABLE",
                "inputParameters": {
                  "last_action": "${plan.output.result.action}",
                  "last_result": "${approved_tool_call.output.content}"
                }
              }
            ],
            "execute": [
              {
                "name": "execute_tool",
                "taskReferenceName": "tool_call",
                "type": "CALL_MCP_TOOL",
                "inputParameters": {
                  "mcpServer": "${workflow.input.mcpServerUrl}",
                  "method": "${plan.output.result.action}",
                  "arguments": "${plan.output.result.arguments}"
                }
              },
              {
                "name": "update_memory",
                "taskReferenceName": "mem_update",
                "type": "SET_VARIABLE",
                "inputParameters": {
                  "last_action": "${plan.output.result.action}",
                  "last_result": "${tool_call.output.content}"
                }
              }
            ],
            "done": [
              {
                "name": "save_answer",
                "taskReferenceName": "save_answer",
                "type": "SET_VARIABLE",
                "inputParameters": {
                  "final_answer": "${plan.output.result.answer}"
                }
              }
            ]
          },
          "defaultCase": []
        }
      ]
    }
  ],
  "outputParameters": {
    "answer": "${workflow.variables.final_answer}",
    "iterations": "${loop.output.iteration}",
    "last_action": "${workflow.variables.last_action}",
    "last_result": "${workflow.variables.last_result}"
  },
  "failureWorkflow": "agent_compensation_workflow"
}

What makes this production-ready

Every step is a durable checkpoint

In the native-task path, each iteration of DO_WHILE is persisted before the next begins. If the agent crashes at iteration 15 of 20, it resumes from iteration 15 — not from scratch. Every LLM prompt, response, tool call, and human decision is recorded. Deployed Conductor Agents provide the same internal Conductor visibility because their graphs are compiled into Conductor workflows.

For an A2A path, the durable checkpoint is the AGENT handoff: Conductor records its status, retry and cancellation lifecycle, and returned artifacts. The remote agent's private internal steps remain owned and observed by that remote service.

Human approval is a durable gate

The HUMAN task pauses the workflow indefinitely. The pause survives server restarts, deploys, and infrastructure changes. When a reviewer approves via the API or UI, the workflow resumes with the approval payload as task output. No polling, no timeouts (unless you configure one), no lost approvals.

Retry is automatic and configurable

Every tool call (CALL_MCP_TOOL, HTTP, SIMPLE) inherits retry behavior from its task definition:

{
  "name": "execute_tool",
  "retryCount": 3,
  "retryLogic": "EXPONENTIAL_BACKOFF",
  "retryDelaySeconds": 2,
  "responseTimeoutSeconds": 30
}

If the MCP server is down, Conductor retries with exponential backoff. The LLM is not re-called — only the failed tool call retries.

Memory persists across iterations

SET_VARIABLE stores accumulated context in workflow variables. These variables are persisted to durable storage and available to every subsequent task. The LLM receives the full history of actions and results on each iteration.

Budget cap prevents runaway agents

The loopCondition checks both the agent's done flag and an iteration cap. You can also check token usage or cost in the condition. The agent terminates cleanly when the budget is exhausted.

Compensation handles side effects

If the agent fails after taking real-world actions (sent an email, created a record, charged a payment), the failureWorkflow runs compensating tasks automatically. The compensation workflow receives the full execution context: which actions succeeded, which failed, and why.

Observability is automatic

For native tasks and compiled Conductor Agent graphs, open the Conductor UI to see:

  • The exact task graph for this execution
  • Every LLM prompt and response (click any LLM_CHAT_COMPLETE task)
  • Every tool call with input, output, and timing
  • Every human approval with who approved and when
  • The iteration count and loop state
  • Retry history for any failed task
  • The full workflow input, output, and variables

For a remote A2A agent, the parent workflow exposes the durable AGENT task — handoff state, retry and cancellation lifecycle, and returned text or artifacts. The remote agent's internal graph stays private to its operator, which is what keeps the boundary clean.

Extending the pattern

Add parallel research

Replace a single tool call with FORK_JOIN_DYNAMIC to fan out to multiple tools in parallel. Validate and cap the LLM-produced inputs before this task; an unbounded plan is not a safe production fan-out.

{
  "name": "parallel_research",
  "taskReferenceName": "research",
  "type": "FORK_JOIN_DYNAMIC",
  "inputParameters": {
    "dynamicTasks": "${plan.output.result.parallel_tasks}",
    "dynamicTasksInput": "${plan.output.result.task_inputs}"
  },
  "dynamicForkTasksParam": "dynamicTasks",
  "dynamicForkTasksInputParamName": "dynamicTasksInput"
}

The LLM decides how many tools to call in parallel and with what inputs. Conductor creates the branches at runtime.

Add a reflection / evaluation step

Insert an LLM-as-judge after tool execution to evaluate output quality:

{
  "name": "evaluate_result",
  "taskReferenceName": "evaluator",
  "type": "LLM_CHAT_COMPLETE",
  "inputParameters": {
    "llmProvider": "anthropic",
    "model": "claude-sonnet-4-20250514",
    "messages": [
      {
        "role": "system",
        "message": "Evaluate this result against the goal. Is it sufficient? Respond with JSON: {\"quality\": \"good\" or \"insufficient\", \"feedback\": \"...\"}"
      },
      {
        "role": "user",
        "message": "Goal: ${workflow.input.goal}\nResult: ${tool_call.output.content}"
      }
    ]
  }
}

If the evaluator returns insufficient, the loop continues with the feedback as context for the next planning step.

Add long waits

Insert a WAIT task for time-based pauses (rate limiting, cooldown periods, scheduled actions):

{
  "name": "wait_before_retry",
  "taskReferenceName": "cooldown",
  "type": "WAIT",
  "inputParameters": {
    "duration": "1 hour"
  }
}

The wait is durable. The workflow does not consume resources while waiting. After 1 hour — even if the server restarted during that time — the workflow resumes.

Delegate to specialist agents

Use AGENT when the specialist is an agent runtime. A deployed Conductor Agent is invoked by name:

{
  "name": "delegate_to_planner",
  "taskReferenceName": "planner_agent",
  "type": "AGENT",
  "inputParameters": {
    "agentType": "conductor",
    "name": "specialist_planner",
    "prompt": "${workflow.input.goal}"
  }
}

Use agentType: "a2a" when the specialist is an independently deployed A2A service:

{
  "name": "delegate_to_researcher",
  "taskReferenceName": "research_agent",
  "type": "AGENT",
  "inputParameters": {
    "agentType": "a2a",
    "agentUrl": "${workflow.input.researchAgentUrl}",
    "text": "${plan.output.result.research_topic}"
  }
}

Use SUB_WORKFLOW when the specialist is a child workflow rather than an agent runtime:

{
  "name": "delegate_to_researcher",
  "taskReferenceName": "research_agent",
  "type": "SUB_WORKFLOW",
  "inputParameters": {
    "name": "research_agent_workflow",
    "version": 1,
    "input": {
      "topic": "${plan.output.result.research_topic}",
      "mcpServerUrl": "${workflow.input.mcpServerUrl}"
    }
  }
}

The parent waits for the child workflow to complete. If it fails, the parent's failure handling kicks in. Its workflow tree is observable in the UI. START_WORKFLOW is the corresponding fire-and-forget option; neither task is a substitute for invoking a deployed or remote agent runtime.

The primitives, mapped

"I need my agent to..." Use this Why
Wait for a tool callback HUMAN task or async completion Durable pause. Resumes on API signal with payload.
Sleep until a retry window WAIT task Timer-based durable pause. Zero resource consumption.
Pick the next tool at runtime DYNAMIC task LLM output determines task type. Resolved at execution time.
Call multiple tools in parallel FORK/JOIN or FORK_JOIN_DYNAMIC Static or runtime-determined parallelism. Join waits for all.
Loop until goal is met DO_WHILE Checkpointed loop. Each iteration persisted.
Invoke a deployed specialist agent AGENT with agentType: "conductor" Runs a named Conductor Agent; its compiled workflow graph is inspectable in Conductor.
Hand off to a remote specialist agent AGENT with agentType: "a2a" Durable remote handoff with parent-boundary status, lifecycle, and artifacts.
Compose a child workflow SUB_WORKFLOW or START_WORKFLOW Waiting or fire-and-forget child-workflow composition; distinct from invoking an agent runtime.
Accumulate context across steps SET_VARIABLE Workflow variables persisted to durable storage.
Evaluate output quality LLM_CHAT_COMPLETE as evaluator LLM-as-judge pattern inside the loop.
Cap iterations or cost DO_WHILE loopCondition Check iteration count, token usage, or cost.
Undo side effects on failure failureWorkflow Compensation tasks run automatically on workflow failure.
Pause for human review HUMAN task Indefinite durable pause. Survives restarts and deploys.
Resume on external event HUMAN task + API/webhook External system calls Task Update API with payload.
Post-process structured output INLINE (JavaScript) or JSON_JQ_TRANSFORM Server-side transforms without a worker.

Next steps