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

AI Engineering — Multi-Agent Systems

AI EngineeringAgentsAdvanced
Introduction

Multi-agent systems coordinate multiple AI agents — each with specialized roles, goals, and tools — to solve complex problems that exceed the capability of any single agent. Agents communicate, compete, collaborate, and critique each other, producing results that emerge from their interaction.

Research shows that multi-agent systems outperform single agents on complex reasoning, code generation, creative tasks, and factuality by introducing diverse perspectives and structured debate.

Debate Pattern

Two or more agents with opposing viewpoints debate a topic. Each agent argues its position, and a judge agent synthesizes the best answer. This improves factuality and reduces hallucination by surfacing counterarguments.

debate-pattern.ts
TypeScript
1// Multi-agent debate
2interface DebateAgent {
3 name: string;
4 stance: string;
5 agent: Agent;
6}
7
8class Debate {
9 private rounds = 3;
10
11 constructor(
12 private participants: DebateAgent[],
13 private judge: Agent
14 ) {}
15
16 async run(topic: string): Promise<string> {
17 const history: string[] = [];
18
19 // Initial positions
20 for (const p of this.participants) {
21 const pos = await p.agent.run(
22 `You argue that ${p.stance} regarding: ${topic}
23 Present your opening argument.`
24 );
25 history.push(`${p.name}: ${pos}`);
26 }
27
28 // Debate rounds
29 for (let round = 0; round < this.rounds; round++) {
30 for (const p of this.participants) {
31 const rebuttal = await p.agent.run(
32 `The topic is: ${topic}
33 Previous arguments:
34 ${history.join("\n")}
35
36 Rebut the opposing arguments and strengthen your case
37 that ${p.stance}.`
38 );
39 history.push(`${p.name} (Round ${round + 1}): ${rebuttal}`);
40 }
41 }
42
43 // Judge's final verdict
44 return await this.judge.run(
45 `Topic: ${topic}
46 Debate transcript:
47 ${history.join("\n")}
48
49 Synthesize the strongest arguments and provide a
50 balanced, well-reasoned final answer.`
51 );
52 }
53}
🔥

pro tip

The debate pattern excels at factual tasks. Two agents with opposing stances produce higher quality answers than a single agent. Use 3 rounds of debate — fewer rounds may not surface all perspectives, while more rounds lead to diminishing returns.
Reflection Pattern

An actor agent generates output, then a critic agent reviews and provides feedback. The actor refines based on feedback, iterating until quality thresholds are met. This pattern is widely used for code generation, writing, and content creation.

reflection-loop.ts
TypeScript
1// Actor-Critic reflection loop
2class ReflectionLoop {
3 private maxIterations = 5;
4 private qualityThreshold = 0.8;
5
6 constructor(
7 private actor: Agent,
8 private critic: Agent
9 ) {}
10
11 async generate(prompt: string): Promise<string> {
12 let output = await this.actor.run(prompt);
13
14 for (let i = 0; i < this.maxIterations; i++) {
15 const review = await this.critic.run(
16 `Review this output for quality, accuracy, and completeness.
17Original prompt: "${prompt}"
18Output: "${output}"
19
20Rate from 0.0 to 1.0.
21If below ${this.qualityThreshold}, provide specific
22actionable feedback for improvement.
23Respond with JSON: { score, feedback, approved }`
24 );
25
26 const { score, feedback, approved } = JSON.parse(review);
27
28 if (approved || score >= this.qualityThreshold) {
29 return output;
30 }
31
32 output = await this.actor.run(
33 `Original prompt: "${prompt}"
34Your previous output: "${output}"
35Feedback to address: "${feedback}"
36Please revise.`
37 );
38 }
39
40 return output;
41 }
42}

best practice

The reflection pattern is particularly effective for code generation. Use a separate model (e.g., a cheaper model like GPT-4o-mini) as the critic to reduce costs. The critic should focus on specific criteria: correctness, style, security, and performance.
Communication Topologies

Agents need structured communication patterns. The topology defines how messages flow between agents and affects system complexity, fault tolerance, and scalability.

TopologyCommunicationScalabilityFault Tolerance
Round-RobinSequential turn-takingLowLow
BroadcastAll-to-allLowMedium
NetworkPeer-to-peerMediumHigh
Hub-and-SpokeCentral coordinatorMediumLow (hub is SPOF)
Message BusEvent-drivenHighHigh
message-bus.ts
TypeScript
1// Message bus topology
2interface AgentMessage {
3 from: string;
4 to: string | "broadcast";
5 type: string;
6 content: any;
7 timestamp: number;
8 threadId: string;
9}
10
11class MessageBus {
12 private subscribers = new Map<string, Agent[]>();
13 private messageLog: AgentMessage[] = [];
14
15 subscribe(topic: string, agent: Agent) {
16 if (!this.subscribers.has(topic)) {
17 this.subscribers.set(topic, []);
18 }
19 this.subscribers.get(topic)!.push(agent);
20 }
21
22 async publish(message: AgentMessage) {
23 this.messageLog.push(message);
24
25 const target = message.to === "broadcast"
26 ? Array.from(this.subscribers.values()).flat()
27 : this.subscribers.get(message.to) || [];
28
29 await Promise.all(
30 target.map(agent =>
31 agent.handleMessage(message).catch(err =>
32 console.error(`Agent failed: ${err}`)
33 )
34 )
35 );
36 }
37
38 getThread(threadId: string): AgentMessage[] {
39 return this.messageLog.filter(m => m.threadId === threadId);
40 }
41}

info

Start with round-robin for simplicity. Graduate to a message bus when you need asynchronous communication, multiple conversation threads, or independent agent lifecycles. Message buses are more complex but enable truly scalable multi-agent systems.
Tool Sharing & Resource Management

Agents in a multi-agent system may share access to tools. Managing concurrent access, rate limits, and resource contention is essential. Implement a tool registry with access control and quota management.

tool-registry.ts
TypeScript
1// Tool registry with access control
2interface ToolBinding {
3 tool: Tool;
4 allowedAgents: string[];
5 rateLimit: { maxCalls: number; windowMs: number };
6}
7
8class ToolRegistry {
9 private bindings = new Map<string, ToolBinding>();
10 private usageLog = new Map<string, number[]>();
11
12 register(name: string, binding: ToolBinding) {
13 this.bindings.set(name, binding);
14 }
15
16 async callTool(
17 toolName: string,
18 agentName: string,
19 args: any
20 ): Promise<any> {
21 const binding = this.bindings.get(toolName);
22 if (!binding) throw new Error(`Unknown tool: ${toolName}`);
23
24 if (!binding.allowedAgents.includes(agentName)) {
25 throw new Error(
26 `Agent ${agentName} not authorized for ${toolName}`
27 );
28 }
29
30 // Rate limiting
31 const now = Date.now();
32 const window = binding.rateLimit.windowMs;
33 const calls = (this.usageLog.get(toolName) || [])
34 .filter(t => now - t < window);
35
36 if (calls.length >= binding.rateLimit.maxCalls) {
37 throw new Error(`Rate limit exceeded for ${toolName}`);
38 }
39
40 this.usageLog.set(toolName, [...calls, now]);
41
42 return await binding.tool.execute(args);
43 }
44}
Coordination Protocols

Agents need protocols to coordinate their work. Common protocols include voting (agents vote on the best answer), consensus (agents negotiate agreement), bidding (agents bid on tasks), and blackboard (shared workspace where agents post and consume messages).

coordination-protocols.ts
TypeScript
1// Voting-based consensus
2class VotingConsensus {
3 constructor(
4 private voters: Agent[],
5 private minVotes: number = 3
6 ) {}
7
8 async reachConsensus(
9 question: string,
10 options: string[]
11 ): Promise<{ winner: string; votes: Record<string, number> }> {
12 const votes: Record<string, number> = {};
13
14 const results = await Promise.all(
15 this.voters.map(voter =>
16 voter.run(
17 `Question: ${question}
18Options: ${options.join(", ")}
19Vote for the best option and respond with ONLY the option name.`
20 )
21 )
22 );
23
24 results.forEach(vote => {
25 const option = vote.trim();
26 if (options.includes(option)) {
27 votes[option] = (votes[option] || 0) + 1;
28 }
29 });
30
31 const winner = Object.entries(votes)
32 .sort((a, b) => b[1] - a[1])[0][0];
33
34 return { winner, votes };
35 }
36}
37
38// Blackboard pattern
39class Blackboard {
40 private board: Map<string, any> = new Map();
41 private agents: Map<string, Agent> = new Map();
42
43 write(key: string, value: any, author: string) {
44 this.board.set(key, { value, author, timestamp: Date.now() });
45 }
46
47 read(key: string): any {
48 return this.board.get(key);
49 }
50
51 subscribe(pattern: RegExp, callback: (key: string, value: any) => void) {
52 // Notify when matching keys are written
53 }
54}

warning

Voting-based consensus can converge on incorrect answers if all agents share the same training data biases. Use agents with different model backbones (e.g., GPT-4o, Claude 3.5, Gemini) to introduce genuine diversity of perspective. Homogeneous agent populations amplify blind spots.
Role-Playing Patterns

Assigning distinct roles to agents (e.g., CEO, Engineer, Designer, Critic) creates productive specialization. Each role has different goals, tools, and behavioral constraints. Role-playing improves output quality by forcing diverse perspectives on the same problem.

role-playing-team.ts
TypeScript
1// Role-based software development team
2const roles = {
3 productManager: new Agent({
4 name: "Product Manager",
5 systemPrompt: `You are a product manager. Define requirements,
6 prioritize features, and ensure the team builds the right thing.
7 Focus on user value and business goals.`
8 }),
9 engineer: new Agent({
10 name: "Engineer",
11 systemPrompt: `You are a senior engineer. Write clean,
12 maintainable, and well-tested code. Consider architecture,
13 performance, and security in your decisions.`
14 }),
15 designer: new Agent({
16 name: "Designer",
17 systemPrompt: `You are a UI/UX designer. Focus on user
18 experience, accessibility, visual design, and interaction
19 patterns. Always consider edge cases in user flows.`
20 }),
21 qa: new Agent({
22 name: "QA Engineer",
23 systemPrompt: `You are a QA engineer. Find bugs, edge cases,
24 and usability issues. Test assumptions and identify gaps.
25 Be thorough and specific in your reports.`
26 })
27};
28
29async function buildFeature(spec: string) {
30 // Phase 1: Requirements
31 const requirements = await roles.productManager.run(
32 `Define requirements for: ${spec}`
33 );
34
35 // Phase 2: Design
36 const design = await roles.designer.run(
37 `Create a design plan for these requirements:
38 ${requirements}`
39 );
40
41 // Phase 3: Implementation
42 const code = await roles.engineer.run(
43 `Implement this design:
44 Requirements: ${requirements}
45 Design: ${design}`
46 );
47
48 // Phase 4: Review
49 const review = await roles.qa.run(
50 `Review this implementation:
51 Requirements: ${requirements}
52 Code: ${code}`
53 );
54
55 return { requirements, design, code, review };
56}

best practice

Assign each role a distinct system prompt that defines goals, constraints, and personality. Avoid overlapping responsibilities — clear role boundaries prevent contradictory instructions. Consider using different model temperatures per role: lower (0-0.3) for analytical roles, higher (0.7-1.0) for creative roles.
$Blueprint — Engineering Documentation·Section ID: AI-MA-01·Revision: 1.0