Mark Ku's Blog
Open in ChatGPTOpen in Claude

歡迎收聽 Mark 的 Tech Insights,我是主持人璦廷。當單一的人工智慧無法滿足複雜任務時,多個人工智慧代理協作的 Multi-Agent 架構就成了關鍵。 今天讓我們來看看,為什麼在眾多架構中,我們特別推薦 Supervisor 主管模式。相較於讓代理自由對話、容易失控的點對點網路,Supervisor 模式由中央主管負責流程編排,各個 Worker 代理只需專注於專業執行。 這個重點值得注意,在實作 Podcast 自動生成管線時,代理間不直接溝通,而是透過共享的 State 物件傳遞資訊。搭配 LangGraph 的 Reducer 機制,能確保代理平行運作時的錯誤紀錄不被覆蓋。這不僅讓系統完全解耦,更能輕鬆建立品質把關的回饋迴路與容錯策略。 總結來說,Supervisor 模式讓複雜的人工智慧工作流變得清晰可控。思考一下,您手邊有哪些任務,也適合拆解並交給專屬的數位員工來代勞呢?

Podcast ConversationAI dialogue version of this article · Mandarin audio
Audio for this article is powered by VoAIVoAI

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 (podcastMarkdown)

append

New value appended to array

Fields multiple agents may write (errors)

merge

Merge object keys

Counters, state tracking (retryCount)

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:

  1. CoverArt Agent and TTS Agent run in parallel.
  2. CoverArt hits an error and writes errors: [{ agent: 'coverArt', message: '...' }].
  3. TTS also hits an error and writes errors: [{ agent: 'tts', message: '...' }].
  4. 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:

  1. Shared State: all agents read from and write to the same State object (using Annotation as described above).
  2. Nodes: each agent is a node.
  3. Edges: define execution order between nodes.
  4. 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 errors simultaneously without an append reducer, they'll overwrite each other. LangGraph's Annotation reducer 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_RETRIES is 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:

  1. Supervisor owns orchestration, Workers own execution — single responsibility, each does its job.
  2. Feedback loops ensure quality — the Editor/Reviewer acts as gatekeeper, sending work back when it doesn't pass.
  3. 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.

Dark mode admin panel displaying an AI agent workflow with multiple stepsDark UI showing AI content generation form and multi-agent workflow

Author

Mark Ku

擁有 10+ 年經驗的資深軟體工程師,現為 AI 應用 Builder,專注於大型平台架構與簡化複雜系統設計,從電商系統到訂閱與收費平台,結合 AI Agent、AI 整合與自動化開發,打造高效率且可持續演進的產品技術基礎。Read More

Found this useful?

The author's free tools, daily podcasts and newsletter are all here.

Mark Ku · This article is licensed under CC BY 4.0. Credit the author and link back to the original when reusing it.

Comments

Subscribe to Newsletter

Subscribe to get new posts delivered instantly — never miss a tech share.

By submitting, you agree to receive emails. You can anytime.

Popular Posts

View all
Mark Ku
··602

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution

Oracle Cloud Always Free Tier: Linux Host and Static IP for a $0 Cloud Solution
Mark Ku
··490

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.

Say Goodbye to Postman's Fee Trap! A Hands-on Guide to Bruno, the Open-Source Git-Native API Testing Powerhouse.
Mark Ku
··333

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki

A Free, Open-Source, Notion-like Knowledge Base — A Complete Guide to Deploying and Backing Up Outline Wiki
Mark Ku
··264

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning

Training Your Own AI Voice: Hardware Requirements, Open-Source Model Comparison, and LoRA Fine-Tuning
Mark Ku
··221

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1

Building an Efficient API Management Platform: Deploying Kong Gateway from Scratch - Part 1
Mark Ku
··215

Setting Up Samba on Ubuntu to Share Folders with Windows 11

Setting Up Samba on Ubuntu to Share Folders with Windows 11