|$ curl https://forge-ai.dev/api/markdown?path=docs/ai/orchestration
$cat docs/ai-engineering-—-agent-orchestration.md
updated Recently·35 min read·published

AI Engineering — Agent Orchestration

AI EngineeringAgentsAdvanced
Introduction

Agent orchestration is the discipline of coordinating multiple AI agents or agent steps to achieve complex goals. As agent systems grow beyond single-turn tool use, you need patterns for sequencing work, managing state, handling failures, and incorporating human oversight.

Orchestration determines how agents communicate, share context, and hand off work. The right pattern depends on task dependencies, error tolerance, latency requirements, and cost constraints.

Sequential Workflow

The simplest pattern: agents execute one after another, passing output as input to the next. This works when tasks have clear linear dependencies (A must complete before B can start).

sequential-pipeline.ts
TypeScript
1// Sequential agent pipeline
2interface PipelineStep {
3 name: string;
4 agent: Agent;
5 transform: (input: any) => string;
6}
7
8class SequentialPipeline {
9 constructor(private steps: PipelineStep[]) {}
10
11 async run(initialInput: string) {
12 let currentInput = initialInput;
13
14 for (const step of this.steps) {
15 console.log(`Running step: ${step.name}`);
16
17 const agentInput = step.transform(currentInput);
18 const result = await step.agent.run(agentInput);
19
20 currentInput = result;
21 }
22
23 return currentInput;
24 }
25}
26
27// Example: Content generation pipeline
28const pipeline = new SequentialPipeline([
29 {
30 name: "outline",
31 agent: outlineAgent,
32 transform: (topic) =>
33 `Create an outline for: "${topic}"`
34 },
35 {
36 name: "draft",
37 agent: draftAgent,
38 transform: (outline) =>
39 `Write a draft based on this outline: ${outline}`
40 },
41 {
42 name: "review",
43 agent: reviewAgent,
44 transform: (draft) =>
45 `Review and improve this draft: ${draft}`
46 }
47]);
48
49const article = await pipeline.run("AI Orchestration");

info

Sequential workflows are easy to debug and test since each step has a clear input and output. Add logging between steps to capture intermediate results for troubleshooting.
DAG Workflows

Directed Acyclic Graphs (DAGs) model workflows where some steps can run in parallel and others have dependencies. This maximizes throughput while preserving ordering constraints.

dag-orchestrator.ts
TypeScript
1// DAG-based orchestration
2interface DAGNode {
3 id: string;
4 execute: (inputs: Map<string, any>) => Promise<any>;
5 dependencies: string[];
6}
7
8class DAGExecutor {
9 constructor(private nodes: Map<string, DAGNode>) {}
10
11 async execute(): Promise<Map<string, any>> {
12 const results = new Map<string, any>();
13 const completed = new Set<string>();
14 const inProgress = new Set<string>();
15
16 while (completed.size < this.nodes.size) {
17 const ready = this.getReadyNodes(completed, inProgress);
18
19 if (ready.length === 0 && inProgress.size > 0) {
20 // Wait for in-progress tasks
21 await new Promise(r => setTimeout(r, 100));
22 continue;
23 }
24
25 const batch = ready.map(async (node) => {
26 inProgress.add(node.id);
27 const deps = new Map(
28 node.dependencies.map(d => [d, results.get(d)])
29 );
30 const result = await node.execute(deps);
31 results.set(node.id, result);
32 completed.add(node.id);
33 inProgress.delete(node.id);
34 });
35
36 await Promise.all(batch);
37 }
38
39 return results;
40 }
41
42 private getReadyNodes(
43 completed: Set<string>,
44 inProgress: Set<string>
45 ): DAGNode[] {
46 return Array.from(this.nodes.values()).filter(node =>
47 !completed.has(node.id) &&
48 !inProgress.has(node.id) &&
49 node.dependencies.every(d => completed.has(d))
50 );
51 }
52}

best practice

Use DAGs when tasks have mixed dependencies — some parallel, some sequential. DAG execution maximizes resource utilization. Tools like LangGraph, Temporal, and Prefect provide production-grade DAG orchestration with retries and observability.
Hierarchical Orchestration

A manager agent decomposes tasks and delegates to specialized sub-agents. The manager tracks progress, handles exceptions, and synthesizes results. This mirrors human organizational structures.

hierarchical-orchestrator.ts
TypeScript
1// Hierarchical manager-worker pattern
2class ManagerAgent {
3 private workers = new Map<string, Agent>();
4 private taskQueue: Task[] = [];
5
6 constructor(
7 private orchestrator: Agent,
8 workers: Record<string, Agent>
9 ) {
10 Object.entries(workers).forEach(
11 ([name, agent]) => this.workers.set(name, agent)
12 );
13 }
14
15 async run(goal: string): Promise<string> {
16 // Step 1: Decompose goal into subtasks
17 const plan = await this.orchestrator.run(
18 `Decompose this goal into subtasks and assign workers.
19Goal: ${goal}
20Available workers: ${[...this.workers.keys()].join(", ")}
21Return a JSON array of {worker, task}`
22 );
23
24 const subtasks = JSON.parse(plan);
25
26 // Step 2: Execute subtasks in parallel
27 const results = await Promise.all(
28 subtasks.map(async (subtask: any) => {
29 const worker = this.workers.get(subtask.worker);
30 if (!worker) {
31 return { error: `Unknown worker: ${subtask.worker}` };
32 }
33 return {
34 worker: subtask.worker,
35 result: await worker.run(subtask.task)
36 };
37 })
38 );
39
40 // Step 3: Synthesize results
41 return await this.orchestrator.run(
42 `Synthesize these results into a final answer:
43${JSON.stringify(results)}`
44 );
45 }
46}
Supervisor Pattern

A supervisor agent monitors worker agents, approves or rejects their outputs, and handles failures. This is critical for production systems where quality control and error recovery are essential.

supervisor-pattern.ts
TypeScript
1// Supervisor with approval gates
2class Supervisor {
3 private maxRetries = 3;
4
5 constructor(
6 private supervisor: Agent,
7 private worker: Agent
8 ) {}
9
10 async run(task: string): Promise<string> {
11 let attempt = 0;
12
13 while (attempt < this.maxRetries) {
14 attempt++;
15 const result = await this.worker.run(task);
16
17 const review = await this.supervisor.run(
18 `Review this result for quality and correctness.
19Task: ${task}
20Result: ${result}
21Respond with APPROVED or REJECTED plus reason.`
22 );
23
24 if (review.startsWith("APPROVED")) {
25 return result;
26 }
27
28 console.log(`Attempt ${attempt} rejected: ${review}`);
29 }
30
31 throw new Error(`Task failed after ${this.maxRetries} attempts`);
32 }
33}
34
35// Human-in-the-loop supervisor
36class HumanSupervisor {
37 async run(result: string, task: string): Promise<boolean> {
38 console.log(`\n=== HUMAN APPROVAL REQUIRED ===`);
39 console.log(`Task: ${task}`);
40 console.log(`Result: ${result}\n`);
41
42 // In production, this would notify via Slack/email/UI
43 const approved = await promptUser("Approve? (y/n): ");
44 return approved;
45 }
46}

warning

Human-in-the-loop supervision adds latency but is essential for high-stakes operations like financial transactions, medical advice, content moderation, and any action that modifies production data. Always implement approval gates for destructive operations.
State Management

Orchestrated agents share state. Common approaches include a shared context object passed through the chain, a global state store, or event-driven message passing. The choice affects latency, consistency, and fault tolerance.

state-management.ts
TypeScript
1// Shared context state
2interface WorkflowState {
3 workflowId: string;
4 status: "running" | "completed" | "failed";
5 currentStep: string;
6 data: Record<string, any>;
7 errors: Error[];
8 startTime: number;
9}
10
11class WorkflowContext {
12 private state: WorkflowState;
13
14 constructor(workflowId: string) {
15 this.state = {
16 workflowId,
17 status: "running",
18 currentStep: "init",
19 data: {},
20 errors: [],
21 startTime: Date.now()
22 };
23 }
24
25 get(key: string): any {
26 return this.state.data[key];
27 }
28
29 set(key: string, value: any): void {
30 this.state.data[key] = value;
31 }
32
33 addError(error: Error): void {
34 this.state.errors.push(error);
35 }
36
37 snapshot(): WorkflowState {
38 return { ...this.state };
39 }
40
41 // Persist state for resumability
42 async persist(): Promise<void> {
43 await db.collection("workflows").updateOne(
44 { workflowId: this.state.workflowId },
45 { $set: this.state },
46 { upsert: true }
47 );
48 }
49
50 static async resume(workflowId: string): Promise<WorkflowContext> {
51 const doc = await db.collection("workflows").findOne({ workflowId });
52 const ctx = new WorkflowContext(workflowId);
53 ctx.state = doc!;
54 return ctx;
55 }
56}

best practice

Persist workflow state at every step for resumability. If a worker crashes mid-workflow, you can resume from the last persisted state instead of starting over. This is especially important for long-running workflows that may take minutes or hours.
Orchestration Patterns Reference

Different orchestration patterns suit different workflow shapes. Choose based on task dependencies, error tolerance, and operational requirements.

PatternStructureParallelismFault ToleranceUse Case
SequentialChainNoneLowLinear pipelines
DAGGraphPartialMediumETL, data processing
HierarchicalTreeAt leavesMediumManager-worker teams
SupervisorStarNoneHighQuality-critical tasks
SwarmPeer-to-peerFullHighExploratory tasks
RoutingClassifierN/AMediumIntent-based dispatch
routing-pattern.ts
TypeScript
1// Routing pattern — classify then route
2class Router {
3 constructor(
4 private classifier: Agent,
5 private routes: Map<string, Agent>
6 ) {}
7
8 async route(input: string): Promise<string> {
9 const intent = await this.classifier.run(
10 `Classify this request into one of:
11 ${[...this.routes.keys()].join(", ")}
12 Request: "${input}"
13 Respond with only the category name.`
14 );
15
16 const handler = this.routes.get(intent.trim());
17 if (!handler) {
18 return `No handler for: ${intent}`;
19 }
20
21 return await handler.run(input);
22 }
23}
$Blueprint — Engineering Documentation·Section ID: AI-ORCH-01·Revision: 1.0