Background
By 2025, AI Agents are nothing new. The real competition has shifted to Multi-Agent systems — letting multiple agents coordinate, divide work, and collaborate autonomously.
No matter how capable a single agent is, it hits a wall on complex tasks: the context window overflows, and trying to pack search + writing + editing + image generation + speech synthesis into one prompt makes quality unpredictable. Breaking the task into multiple specialized agents, each doing one thing, actually gives you more control.
But that raises the question: how do multiple agents coordinate? This post compares the two dominant architectural patterns and explains why I ended up choosing the Supervisor model for my podcast auto-generation pipeline.
Two Dominant Architectural Patterns
When designing a Multi-Agent system, you typically encounter two design patterns:
1. Network / Peer-to-Peer
Agents communicate directly with each other — no central controller. Each agent can decide on its own who to talk to and what to pass along.
┌─────────┐ ┌─────────┐
│ Agent A │◄───►│ Agent B │
└────┬────┘ └────┬────┘
│ │
▼ ▼
┌─────────┐ ┌─────────┐
│ Agent C │◄───►│ Agent D │
└─────────┘ └─────────┘
(每個 Agent 都可以跟任意 Agent 溝通)
Best for: open-ended discussions, brainstorming, multi-perspective debates. Risks: unpredictable flow, easy to fall into infinite loops, difficult to debug.
2. Supervisor
A central Supervisor Agent evaluates the current state and decides which agent to dispatch next. Worker agents execute their task and report back; the Supervisor decides what happens next.
┌────────────┐
│ Supervisor │
│ (協調者) │
└─────┬──────┘
┌──────────┼──────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Worker A │ │Worker B │ │Worker C │
│(搜尋) │ │(撰寫) │ │(校稿) │
└─────────┘ └─────────┘ └─────────┘
(Worker 只跟 Supervisor 溝通,彼此不直接對話)
Best for: task pipelines with clear steps, quality gates, and trackable progress. Advantages: predictable flow, clear separation of concerns, straightforward fault tolerance.
Comparison
Dimension | Network (P2P) | Supervisor |
|---|---|---|
Control | Decentralized, agents negotiate | Centralized, Supervisor dispatches |
Predictability | ❌ Low, paths are dynamic | ✅ High, explicit state machine |
Debug difficulty | 😰 High, complex message flows | 😌 Low, every step is logged |
Best task type | Open-ended discussion, creative work | Production pipelines with defined steps |
Extensibility | Adding agents requires rethinking topology | Just register a new Worker with the Supervisor |
Fault tolerance | Each agent handles its own errors | Supervisor owns the degradation strategy |
Why I Chose the Supervisor Pattern
The core reason: the Supervisor only needs to know "who can do what," and Workers only need to know "how to do this one thing well."
That separation is clean:
- Supervisor handles "orchestration" — who goes first, who goes next, what happens on failure.
- Workers handle "specialized execution" — search is search, writing is writing, editing is editing.
This mirrors the Single Responsibility Principle (SRP) in software engineering — each agent does exactly one thing and reports back when done.
In practice, most AI automation tasks have clear sequential dependencies (you need the search results before you can write, and the draft before you can edit). The Supervisor pattern is a natural fit for this kind of pipeline workflow.
How Agents Coordinate: Shared State Is the Only Communication Channel
In the Supervisor pattern, agents never communicate directly with each other. All coordination flows through a shared State object:
┌─────────────────────────────────────────────┐
│ 共享狀態(Shared State) │
│ │
│ rawSources ← Research 寫入 │
│ podcastMarkdown ← Writer 寫入 │
│ editorFeedback ← Editor 寫入 │
│ coverImagePath ← CoverArt 寫入 │
│ audioPath ← TTS 寫入 │
│ errors ← 所有 Agent 可寫入(append 模式) │
│ │
│ Supervisor 讀取 State → 決定下一步 │
└─────────────────────────────────────────────┘
Each agent's interaction pattern is simple:
- Read: pull its required input from State (e.g., Writer reads
rawSources) - Write: push its output back to State (e.g., Writer writes
podcastMarkdown) - Doesn't need to know: who ran before it, or who runs next
This means agents are fully decoupled. You can swap any agent's implementation at any time (e.g., replace a Gemini Writer with a Claude Writer) — as long as the State field contracts remain the same, no other agent is affected.
LangGraph State Management: Annotations and Reducers
LangGraph uses Annotation to define shared state — this is the foundation of all Multi-Agent coordination.
Core concept: each field can have its own reducer
Ordinary state management is "last write wins," but that breaks down in Multi-Agent scenarios. For example, if Agent A records an error and Agent B records an error, without a reducer B's error would overwrite A's.
LangGraph's solution is to let each field define its own reducer (merge strategy):
export const PodcastAnnotation = Annotation.Root({
// 普通欄位:後寫覆蓋(預設行為)
date: Annotation<string>,
podcastMarkdown: Annotation<string>,
// append reducer:新值追加到陣列,不覆蓋舊值
errors: Annotation<AgentError[], AgentError[]>({
reducer: (existing, update) => [...existing, ...update],
default: () => [],
}),
// merge reducer:合併物件的 key-value
retryCount: Annotation<Record<string, number>>({
reducer: (existing, update) => ({ ...existing, ...update }),
default: () => ({}),
}),
})
How reducers work
Reducer type | Behavior | Use case |
|---|---|---|
None (default) | Last write wins | Fields written by a single agent ( |
append | New value appended to array | Fields multiple agents may write ( |
merge | Merge object keys | Counters, state tracking ( |
custom | Any custom logic | Special requirements |
Why does this matter?
The most common bug in Multi-Agent systems without reducers is state being silently overwritten. Consider this scenario:
- CoverArt Agent and TTS Agent run in parallel.
- CoverArt hits an error and writes
errors: [{ agent: 'coverArt', message: '...' }]. - TTS also hits an error and writes
errors: [{ agent: 'tts', message: '...' }]. - Without an append reducer, TTS's error overwrites CoverArt's — you'll never know the cover generation failed.
With an append reducer, both errors are preserved. This is table-stakes for Multi-Agent state management.
Design Concept: Implementing Supervisor with LangGraph StateGraph
I use LangGraph's StateGraph to implement the Supervisor. The core ideas are:
- Shared State: all agents read from and write to the same State object (using
Annotationas described above). - Nodes: each agent is a node.
- Edges: define execution order between nodes.
- Conditional Edges: the Supervisor reads State to decide which path to take next.
START → Research → Writer → Editor ─┬─→ Publisher → [CoverArt + TTS] → END
▲ │
│ (score < 60, retry ≤ 2)
└───────────────┘
回饋迴路:帶著問題清單重寫
Implementation: Podcast Auto-Generation Pipeline
I built a "Tech News Podcast Auto-Generator" using this architecture. Six agents, each with a dedicated role:
Agent | Responsibility | Technology |
|---|---|---|
🔍 ResearchAgent | Search for the latest AI news (8-12 items) | Claude CLI + web_search |
✍️ WriterAgent | Turn news sources into a podcast article | Gemini 3.1 Pro |
📝 EditorAgent | Quality scoring + removing AI artifacts | Gemini 3.1 Pro + regex |
📦 PublisherAgent | Assemble frontmatter and write MDX file | Filesystem |
🎨 CoverArtAgent | Generate cover image | Gemini Image + sharp |
🎙️ TTSAgent | Generate multi-voice dialogue audio | Gemini + VoAI TTS |
Full Architecture Diagram
┌─────────────────────────────────────────────────────────────────────┐
│ LangGraph StateGraph │
│ (Supervisor 協調者) │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Research │───▶│ Writer │───▶│ Editor │───▶│ Publisher │ │
│ │ Agent │ │ Agent │◀───│ Agent │ │ Agent │ │
│ │ │ │ │回饋 │ │ │ │ │
│ │ Claude │ │ Gemini │迴路 │ Gemini │ │ Filesystem │ │
│ │ CLI + │ │ 3.1 Pro │(≤2) │ 3.1 Pro │ │ MDX 寫入 │ │
│ │web_search│ │ │ │ │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────┬───────┘ │
│ │ │
│ ┌──────────┴────┐ │
│ │ 平行執行 │ │
│ ┌────▼────┐ ┌──────▼──┐ │
│ │CoverArt │ │ TTS │ │
│ │ Agent │ │ Agent │ │
│ │ Gemini │ │ Gemini │ │
│ │ Image + │ │ + VoAI │ │
│ │ sharp │ │ │ │
│ └────┬────┘ └────┬────┘ │
│ ▼ ▼ │
│ END END │
└─────────────────────────────────────────────────────────────────────┘
Key Code Walkthrough
1. Shared State Definition (LangGraph Annotation)
All agents read and write the same State. Annotation defines the data structure and reducers:
import { Annotation } from '@langchain/langgraph'
export const PodcastAnnotation = Annotation.Root({
// ─── 輸入 ───
date: Annotation<string>,
config: Annotation<PodcastConfig>,
// ─── 各階段產出 ───
rawSources: Annotation<NewsSource[]>, // Research 寫入
podcastMarkdown: Annotation<string>, // Writer 寫入
editorFeedback: Annotation<EditorFeedback>, // Editor 寫入
cleanedContent: Annotation<string>, // Editor 寫入
coverImagePath: Annotation<string | null>, // CoverArt 寫入
audioPath: Annotation<string | null>, // TTS 寫入
// ─── 控制欄位 ───
errors: Annotation<AgentError[]>({
reducer: (existing, update) => [...existing, ...update],
default: () => [],
}),
retryCount: Annotation<Record<string, number>>({
reducer: (existing, update) => ({ ...existing, ...update }),
default: () => ({}),
}),
})
Key point: errors uses an append reducer so later agents can't overwrite earlier error records.
2. StateGraph Assembly: Nodes + Edges + Conditional Routing
import { StateGraph, END } from '@langchain/langgraph'
export function buildGraph() {
const graph = new StateGraph(PodcastAnnotation)
// 註冊節點(每個 Agent 都是一個 function)
.addNode('research', researchAgent)
.addNode('writer', writerAgent)
.addNode('editor', editorAgent)
.addNode('publisher', publisherAgent)
.addNode('coverArt', coverArtAgent)
.addNode('tts', ttsAgent)
// 順序執行
.addEdge('__start__', 'research')
.addEdge('research', 'writer')
.addEdge('writer', 'editor')
// 條件路由:Editor 決定要重寫還是放行
.addConditionalEdges('editor', routeAfterEditor, {
publisher: 'publisher',
writerRetry: 'writerRetry',
})
// Publisher 後:平行執行 CoverArt + TTS
.addConditionalEdges('publisher', routeAfterPublisher, {
coverArt: 'coverArt',
tts: 'tts',
end: 'end',
})
.addEdge('coverArt', END)
.addEdge('tts', END)
return graph.compile()
}
Key point: addConditionalEdges is the Supervisor's core mechanism — it reads the current State and decides which path to take next.
3. Worker Agent Implementation (WriterAgent as an Example)
Each Worker does exactly one thing: receive State, execute the task, write back the result:
export async function writerAgent(
state: PodcastState
): Promise<Partial<PodcastState>> {
const model = await createVertexModel({
model: state.config.writerModel,
temperature: 0.8,
})
// 如果是 retry,帶入 Editor 的回饋
let feedbackContext = ''
if (state.editorFeedback && !state.editorFeedback.passed) {
feedbackContext = state.editorFeedback.issues
.map(i => `- ${i}`)
.join('\n')
}
const prompt = buildWriterPrompt(state.date, newsContext) + feedbackContext
const response = await model.invoke([{ role: 'user', content: prompt }])
return {
podcastMarkdown: response.content.trim(),
currentStep: 'writing_done',
}
}
The Worker doesn't need to know which iteration it's on, who ran before it, or who runs next. It just focuses on producing a good draft.
Feedback Loop: The Quality Gatekeeper Pattern
This is the most powerful aspect of the Supervisor pattern — you can design feedback loops.
Writer ──draft──▶ Editor ──score──┬──▶ ✅ Pass → Publisher
│
❌ Fail (score < 60)
│
Rewrite with feedback (max 2 retries)
│
└──▶ Writer
function routeAfterEditor(state: PodcastState): string {
// 通過 → 下一步
if (state.editorFeedback?.passed) return 'publisher'
// 沒通過但還有重試次數 → 帶回饋重寫
const retries = state.retryCount?.['writer'] ?? 0
if (retries < MAX_WRITER_RETRIES) return 'writerRetry'
// 超過重試次數 → 帶警告繼續
return 'publisher'
}
Analogy: Coding Agent Scenario
The same pattern applies to code review:
Coding Agent ──submit code──▶ Supervisor Agent ──review──┬──▶ ✅ Pass → Merge
│
❌ Security issue / violation
│
Fix with feedback (max N retries)
│
└──▶ Coding Agent
The Supervisor only evaluates "does this code have problems," and the Coding Agent only "fixes based on feedback." Responsibilities are clear and don't bleed into each other.
Fault Tolerance and Degradation Strategies
In a Multi-Agent system, not every agent is equally critical. A well-designed Supervisor needs tiered fault tolerance:
Failure scenario | Handling | Severity |
|---|---|---|
Research search fails | Writer generates from LLM's own knowledge | ⚠️ Degrade |
Editor fails repeatedly | Publish with warning | ⚠️ Degrade |
CoverArt generation fails | Article publishes without cover | 💡 Acceptable |
TTS synthesis fails | Article publishes without audio | 💡 Acceptable |
Publisher write fails | Abort the pipeline | 🔴 Hard failure |
Design principle: failures in the core pipeline (Research → Write → Edit → Publish) need graceful degradation; failures in supplementary features (Cover, TTS) should not impact the main flow.
Notes and Gotchas
Practical pitfalls
- Use reducers for shared state: if multiple agents write to
errorssimultaneously without an append reducer, they'll overwrite each other. LangGraph'sAnnotationreducer is a lifesaver. - Parallel agents must be truly independent: CoverArt and TTS can run in parallel because they have no data dependency on each other. If there is a dependency, sequential execution is required.
- Always cap feedback loops: a feedback loop without
MAX_RETRIESis an infinite loop. I set mine to 2; after that, the pipeline continues with a warning. - Different agents can use different models: Research uses Claude (strong search reasoning), Writer/Editor use Gemini (cheap + long context), TTS uses a dedicated voice API. Mixing models often yields better results than using one model for everything.
Performance reference
Mode | Time | Description |
|---|---|---|
Text only (skip-tts + skip-cover) | ~2 min | Research → Writer → Editor → Publisher |
With cover | ~3 min | Adds CoverArt generation |
Full pipeline | ~4 min | CoverArt + TTS running in parallel |
When not to use Supervisor
- If the task is inherently open-ended (multi-agent debates, creative brainstorming), Supervisor actually limits agent autonomy.
- If you only have 2 agents and the flow is linear, just chain them directly — no need to bring in the full Supervisor architecture.
Conclusion
The core of Multi-Agent isn't "more agents = better." It's clear division of labor + predictable flow.
Three design principles for the Supervisor pattern:
- Supervisor owns orchestration, Workers own execution — single responsibility, each does its job.
- Feedback loops ensure quality — the Editor/Reviewer acts as gatekeeper, sending work back when it doesn't pass.
- Tiered fault tolerance — core pipeline degrades gracefully; supplementary features fail gracefully.
If you're planning a Multi-Agent system, I'd suggest first asking yourself: "does this task have a clear sequential order?" If yes, the Supervisor pattern is the most stable starting point.





























Comments