Skip to content

OrchestratorAgent

Multi-step task planner and executor. Uses a Gemini model to decompose a user request into a structured plan of steps, then executes each step by delegating to atomic agents (DocumentAnalysisAgent, QAChatAgent) or registered tools. Supports dependency ordering, parallel execution, retries, and automatic result aggregation.

Import

import { OrchestratorAgent } from "@modernpath/agent-framework";
import type { OrchestratorAgentConfig } from "@modernpath/agent-framework";

Constructor

class OrchestratorAgent extends BaseAgent {
  constructor(
    toolRegistry: ToolRegistry,
    gemini: GeminiClient,
    prompts: PromptTemplate,
    atomicAgents: {
      documentAnalysis: BaseAgent;
      qaChat: BaseAgent;
    },
    cfg?: OrchestratorAgentConfig,
  )
}
Parameter Type Required Description
toolRegistry ToolRegistry Yes Tool registry. When allowToolSteps is enabled, registered tools can be invoked as plan steps.
gemini GeminiClient Yes Gemini model client for plan generation.
prompts PromptTemplate Yes Prompt template engine. Expects orchestrator.planning.* templates.
atomicAgents object Yes The atomic agents available for plan steps.
atomicAgents.documentAnalysis BaseAgent Yes Agent for document operations (typically DocumentAnalysisAgent).
atomicAgents.qaChat BaseAgent Yes Agent for knowledge-base Q&A (typically QAChatAgent).
cfg OrchestratorAgentConfig No Optional configuration.

Configuration

OrchestratorAgentConfig

interface OrchestratorAgentConfig {
  parallel?: boolean;
  maxRetries?: number;
  defaultSiteId?: string;
  allowToolSteps?: boolean;
  exposeToolsToPlanner?: boolean;
  toolCatalogMode?: "externalOnly" | "all";
}
Name Type Required Default Description
parallel boolean No false If true, executes dependency-free steps in parallel using Promise.allSettled. Disable for safety in serverless environments with rate limits.
maxRetries number No 1 Maximum number of retries for a retryable step after the first failure (so a step runs at most maxRetries + 1 times).
defaultSiteId string No undefined Default SharePoint site ID for documentAnalysis steps when the planner omits it. Also tries context.parameters.siteId, context.parameters.sharepoint.siteId, and context.metadata.siteId.
allowToolSteps boolean No true When true, plan steps can invoke ToolRegistry tools directly (in addition to atomic agents). The step's agent field is set to the tool name.
exposeToolsToPlanner boolean No false When true, includes registered tool names and parameter schemas in the planning prompt so the LLM can generate tool steps.
toolCatalogMode "externalOnly" \| "all" No "externalOnly" Controls which tools appear in the planner's tool catalog. "externalOnly" shows only tools with category "external" or tags "external" / "client". "all" shows every registered tool.

Return Value

On success, AgentResult.data contains:

{
  plan: TaskPlan;
  results: StepResult[];
  aggregated: AggregatedResult;
}

TaskPlan

interface TaskPlan {
  taskType: TaskType;
  steps: PlanStep[];
  estimatedTime: string;
  requiredDocuments: string[];
  metadata?: Record<string, any>;
}
Field Type Description
taskType TaskType Category of the task. One of: reconciliation, substantive_testing, analytical_procedures, checklist, report_generation, company_info.
steps PlanStep[] Ordered list of steps to execute.
estimatedTime string Human-readable time estimate (e.g. "5-10 minutes").
requiredDocuments string[] Document names the plan expects to be available.

PlanStep

interface PlanStep {
  stepNumber: number;
  description: string;
  agent: AgentName;
  inputs: Record<string, any>;
  expectedOutput: string;
  dependencies: number[];
  timeout?: number;
  retryable?: boolean;
  critical?: boolean;
}
Field Type Description
stepNumber number Unique step identifier within the plan.
description string Human-readable description of what this step does.
agent string Name of the agent or tool to execute. For atomic agents: "documentAnalysis" or "qaChat". For tools: the tool's registered name.
inputs Record<string, any> Parameters passed to the agent or tool. For agents, these become context.parameters.
expectedOutput string Description of the expected result.
dependencies number[] Step numbers that must complete successfully before this step runs.
timeout number Optional step-level timeout in milliseconds.
retryable boolean Whether the step can be retried on failure. Default: true.
critical boolean If true and the step fails, the entire plan execution is aborted with an error. Default: false.

StepResult

interface StepResult {
  stepNumber: number;
  success: boolean;
  data?: any;
  error?: string;
  executionTime: number;
  agent: string;
  timestamp: Date;
}

AggregatedResult

interface AggregatedResult {
  summary: string;
  data: {
    resultsByStep: Record<string, StepResult>;
  };
  executionSummary: {
    totalSteps: number;
    successfulSteps: number;
    failedSteps: number;
    totalExecutionTime: number;
  };
}

Execution Flow

sequenceDiagram
    participant Caller
    participant Orchestrator
    participant Gemini
    participant AtomicAgent
    participant ToolRegistry

    Caller->>Orchestrator: execute(context)
    Orchestrator->>Gemini: Generate task plan (JSON)
    Gemini-->>Orchestrator: TaskPlan
    Orchestrator->>Orchestrator: Validate & normalize plan

    loop For each ready step
        alt Atomic agent step
            Orchestrator->>AtomicAgent: execute(stepContext)
            AtomicAgent-->>Orchestrator: AgentResult
        else Tool step
            Orchestrator->>ToolRegistry: execute(toolName, params)
            ToolRegistry-->>Orchestrator: Tool result
        end
    end

    Orchestrator->>Orchestrator: Aggregate results
    Orchestrator-->>Caller: { plan, results, aggregated }

Planning

How the Plan is Generated

  1. The orchestrator renders the planning prompt using orchestrator.planning.system and orchestrator.planning.user_template templates
  2. Available agents and (optionally) tool names are included in the prompt
  3. Gemini generates a JSON plan at temperature 0.2
  4. The plan is parsed, validated, and normalized:
    • Agent names are normalized (e.g., "qa-chat" becomes "qaChat")
    • Missing fields get defaults (stepNumber, dependencies, retryable)
    • Tool parameters are validated against their metadata schemas
  5. If parsing/validation fails, a second attempt is made with the error message included
  6. If both attempts fail, a fallback plan with a single documentAnalysis step is used

Plan Validation

The orchestrator validates:

  • The plan has a steps array
  • Each step has a valid agent (matching an atomic agent or registered tool)
  • Step numbers are unique
  • Tool step inputs pass parameter validation

Agent name normalization

The planner may produce agent names in different formats ("qa_chat", "QAChatAgent", "document-analysis"). The orchestrator normalizes these to the canonical names "documentAnalysis" and "qaChat".

Fallback Plan

If the LLM fails to produce a valid plan after two attempts, the orchestrator creates a minimal fallback plan:

{
  "taskType": "analytical_procedures",
  "steps": [{
    "stepNumber": 1,
    "description": "Analyze documents based on: <user prompt>",
    "agent": "documentAnalysis",
    "inputs": { "prompt": "<user prompt>" },
    "dependencies": [],
    "retryable": true,
    "critical": false
  }],
  "estimatedTime": "5-10 minutes",
  "requiredDocuments": []
}

Step Execution

Dependency Resolution

Steps are executed in dependency order. A step runs only when all steps in its dependencies array have completed successfully. If a dependency fails, the dependent step is skipped (unless critical causes an abort).

Parallel Execution

When parallel: true, all ready steps (those with satisfied dependencies) in the same "wave" run concurrently via Promise.allSettled. This can reduce total execution time but may increase API rate limit pressure.

Retry Logic

If a step fails and retryable is true, the orchestrator retries it up to maxRetries times. If all retries fail:

  • If critical: true, the entire plan execution throws an error
  • If critical: false, the step is marked as failed and execution continues

Document ID Auto-resolution

For documentAnalysis steps that require a documentId but do not have one in their inputs, the orchestrator automatically:

  1. Resolves the siteId from step inputs, context parameters, or defaultSiteId
  2. Calls the documentAnalysis agent with action: "select" to pick the most relevant documents
  3. Injects the selected document IDs into the step inputs

Tool Step Execution

When a step's agent matches a registered tool (not an atomic agent), the orchestrator:

  1. Injects userId and auditingId from the context
  2. Passes step inputs as tool parameters
  3. Passes orchestrator metadata via the __metadata key (for dynamic tools)

Prompt Templates

Template Key Required Description
orchestrator.planning.system No System prompt for plan generation.
orchestrator.planning.user_template No User prompt template. Variables: task (the user prompt), agents (available agent/tool names). Falls back to a built-in prompt if not defined.

Code Example

Basic Usage

import {
  OrchestratorAgent,
  DocumentAnalysisAgent,
  QAChatAgent,
  ToolRegistry,
} from "@modernpath/agent-framework";

const registry = new ToolRegistry();
// Register tools...

const docAgent = new DocumentAnalysisAgent(registry, gemini, prompts);
const qaAgent = new QAChatAgent(registry, retrieval, gemini, prompts);

const orchestrator = new OrchestratorAgent(
  registry,
  gemini,
  prompts,
  { documentAnalysis: docAgent, qaChat: qaAgent },
  { defaultSiteId: "site-abc-123" },
);

const result = await orchestrator.execute({
  userId: 42,
  auditingId: 1001,
  prompt: "Analyze the Q3 financial report and answer: what were the main cost drivers?",
  parameters: {},
});

if (result.success) {
  const { plan, results, aggregated } = result.data;

  console.log(`Plan type: ${plan.taskType}`);
  console.log(`Steps: ${plan.steps.length}`);
  console.log(`Summary: ${aggregated.summary}`);
  console.log(`Success rate: ${aggregated.executionSummary.successfulSteps}/${aggregated.executionSummary.totalSteps}`);

  // Access individual step results
  for (const step of results) {
    console.log(`Step ${step.stepNumber} (${step.agent}): ${step.success ? "OK" : step.error}`);
  }
}

With Tool Steps

import { registerDynamicTool } from "@modernpath/agent-framework";

// Register an external tool
registerDynamicTool(registry, {
  name: "send-report",
  description: "Sends a formatted report via email",
  category: "external",
  tags: ["external", "email"],
  parameters: {
    recipient: { type: "string", description: "Email address" },
    subject: { type: "string", description: "Email subject" },
    body: { type: "string", description: "Email body (HTML)" },
  },
  execute: async (params, ctx) => {
    await emailService.send(params);
    return { sent: true };
  },
});

const orchestrator = new OrchestratorAgent(
  registry,
  gemini,
  prompts,
  { documentAnalysis: docAgent, qaChat: qaAgent },
  {
    defaultSiteId: "site-abc-123",
    exposeToolsToPlanner: true,     // Let the LLM see available tools
    toolCatalogMode: "externalOnly", // Only external tools
    parallel: false,                 // Sequential for safety
    maxRetries: 2,                   // Retry failed steps twice
  },
);

// The orchestrator can now generate plans that include "send-report" steps
const result = await orchestrator.execute({
  userId: 42,
  auditingId: 1001,
  prompt: "Analyze Q3 report and email the summary to finance@example.com",
  parameters: {},
});

Parallel Execution

const orchestrator = new OrchestratorAgent(
  registry,
  gemini,
  prompts,
  { documentAnalysis: docAgent, qaChat: qaAgent },
  { parallel: true },
);
// Steps with no dependencies on each other will run concurrently