Building a Multi-Agent System in TypeScript A developer detailed the construction of a multi-agent system in TypeScript, highlighting two patterns: Orchestrator/Subagent and Pipeline. The system decomposes tasks into parallel subtasks to overcome context window limits and improve efficiency, as demonstrated with a competitor pricing analysis example. Single agents hit real limits in production. Long tasks exceed context windows. Complex goals need different tools at different stages. Sequential reasoning is slow when subtasks are independent. Multi-agent systems solve these problems by decomposing work across specialized agents that can run in parallel. This article walks through two patterns — Orchestrator/Subagent and Pipeline — implemented entirely in TypeScript. The case for multi-agent systems isn't abstract. Consider a research task: "Analyze our competitors' pricing pages and summarize the key differences." A single agent working sequentially has to: fetch page 1, process it, fetch page 2, process it, fetch page 3, process it, then write the analysis. Each step burns context window. Total time is the sum of all steps. An orchestrator-based approach: spawn three agents in parallel, one per competitor. Each fetches and processes its page independently. Total time is roughly the time of the slowest agent, not the sum. Single agent serial : Task → fetch A → fetch B → fetch C → analyze → Result Time: T A + T B + T C + T analyze Multi-agent parallel : ┌→ Agent A: fetch + process ─┐ Task → Orchestrator → Agent B: fetch + process → Synthesize → Result └→ Agent C: fetch + process ─┘ Time: max T A , T B , T C + T synthesize The orchestrator pattern has three phases: decompose the goal into subtasks, execute subtasks in parallel where possible , then synthesize results. First, define the data structures that flow between orchestrator and subagents: // src/lib/agent/multi/types.ts export interface SubTask { id: string; title: string; description: string; toolSet: 'browser' | 'file' | 'code' | 'rag' | 'general'; dependsOn?: string ; // IDs of tasks that must complete first priority: 'high' | 'medium' | 'low'; timeoutMs?: number; } export interface SubTaskResult { taskId: string; status: 'success' | 'failed' | 'timeout' | 'skipped'; output: string; durationMs: number; } export interface OrchestratorPlan { goal: string; tasks: SubTask ; estimatedParallelGroups: string ; // Which tasks can run concurrently } dependsOn is what makes the dependency graph work — a task that needs another task's output lists its ID here. estimatedParallelGroups is the LLM's suggestion for which tasks can run at the same time. The orchestrator calls the LLM once to convert a natural-language goal into a structured plan: async decompose goal: string : Promise