首页 / 文章

两个节点,九个项目:Claude Code多智能体架构的实际应用

据称有位开发者如何在 Claude Code 中管理核心开发人员、技术负责人以及有明确职责范围的独立开发者,设计中存在哪些问题,以及单人开发者应借鉴哪些部分。

发布者:REACTAPP.TOP阅读约 12 分钟2810 词

来源: Inside a Real Multi-Agent Claude Code Setup: Two Leads, 9 Projects, 40 Prompts a Day

许多关于多智能体系统的描述都采用相同的结构:一张图表,上面标有“协调器”、“工作节点1”和“工作节点2”等标签,有一支标有“结果”的箭头,以及四十行伪代码;一旦两个智能体出现分歧,这些伪代码就会立刻失效。这类描述很少提及当工作节点1的输出使工作节点2的假设不再成立而后者仍继续运行时会发生什么,或是当协调器在任务执行到一半时崩溃时会出现什么情况。它们看起来就像是由那些从未在半夜被某个花了六个小时不断循环、浪费资源却始终无法完成修复任务的智能体吵醒的人所绘制的图表。

这个账号采取了不同的方法。它描述了一位开发者的实际配置方案,该方案应用于九个正在运行的项目中,并经过大约三个月的优化,最终变得足够稳定可靠。稳定性正是其目标。以下内容将介绍该架构的构成、使它能够在技术上实现的 Claude Code 功能、它出现故障的环节(这些故障与 Anthropic 自身关于多智能体系统的研究结果一致),以及单个开发者应当借鉴的部分。具体细节均来自该开发者的描述,并未经过独立验证。

架构的具体结构

有两个主要代理程序,分别被昵称为lead-alpha和lead-beta。它们都是运行时间较长的Claude Code会话,且运行在不同的硬件上:一台永不关机的小型设备以及一台云虚拟机。它们会互相监督。每隔几分钟,每个代理就会通过SendMessage向另一个代理发送一个微小的“心跳”信号。当某个代理在允许的时间范围内没有响应时,另一个代理会将其唤醒,要么在能够直接访问时重新启动该会话,要么触发一个定时任务来启动一个与该项目上下文相关的新会话。

这看起来似乎有些过度,直到有次在深夜时某个代理因为某个出问题的工具调用而停滞,导致所有后续任务都冻结,直到有人发现为止。这种情况在初期只发生过一次,但也足以证明第二个代理存在的必要性。

在这两位负责人之下还有九个项目。每个项目都有各自的技术负责人代理和项目经理代理,由负责该项目的顶层负责人创建并监管,职责大致对半分配,主要依据领域不同,部分则取决于那周工作量较轻的人。项目经理代理负责跟踪项目范围,将收到的需求拆分为任务单,并向上层协商优先级。技术负责人代理则负责技术层面的拆分工作,决定由哪位贡献者代理处理哪部分任务,同时在任何内容交给人工处理之前审核代码差异。

每位技术主管负责管理5到10名集成开发人员,每位人员负责代码库的某个部分或某种类型的任务。其中有一人专门处理迁移脚本,不得处理其他任何工作——这一规定源于某位通才型集成开发人员在 supposedly修复其他问题时整理了迁移脚本文件夹的事件。总体而言共有大约75到90个代理角色,但由于没有任务排队,大多数集成开发人员岗位处于空闲状态。

交互次数的计算结果让怀疑者相信这并非简单的玩具项目。在这种架构中,相关人员每天总共会撰写30到50条指令,这是整个操作流程的总数,而非针对单个项目。几乎没有任何指令会直接发送给集成开发人员,因为向集成开发人员输入指令意味着已经出现了问题;而处理这些问题正是技术主管层的作用。根据上述描述,注意力的分配大致如下:

+---------------------------------+------------------+
| Who I talk to                     | Share of my time  |
+---------------------------------+------------------+
| The two lead agents                | ~60%              |
| Project tech leads / PMs directly  | ~35%              |
| Anything below tech lead level     | ~5% (escalations) |
+---------------------------------+------------------+

一天中的大部分时间都用于与两位负责人讨论优先级、解决停滞的项目问题,以及阅读由九位项目负责人共同汇总的夜间摘要。据称,技术负责人分配任务、核心成员反馈进展、产品经理重新定义任务需求等自动化沟通量比人工发起的请求多出大约两个数量级,除非有人主动查看,否则只有极小部分会被注意到。

如今为何能实现这一点

这里的精确性至关重要,因为据称在六个月前还无法实现同样的安排。Claude Code最近的一个版本(该文档提及的版本号为2.1.232)中的两项功能改变了现状,因此请查看当前的版本说明以了解确切的行为表现。

子代理的分支机制。最大的变化在于技术负责人与其智能组件之间的互动方式。由于默认不采用分支机制,新生成的子代理一开始什么都没有:既没有关于任务背后讨论的记录,也没有共享的提示词缓存。每个智能组件都需要完整的说明,这就意味着要么在每个任务提示词中加入大量背景信息,要么忍受负责人几分钟前才解答过的问题。在九个项目中涉及5到10个智能组件的情况下,这种情况很快就会变得十分棘手。

请求 subagent_type: "fork" 后,新的子代理会获得整个对话内容以及创建者的提示词缓存。那位已花费二十分钟思考复杂迁移问题的技术主管,可以复制一个已包含所有思考内容的智能体,无需再次支付费用或重新解释。此类复制操作默认在后台运行,其输出不会出现在父代理的上下文中,因此技术主管能直接得到最终结果,而非一堆文件读取的冗余信息。

存在一定的限制。对于分支来说,模型覆盖选项会被忽略,因为分支始终会使用父版本的模型,因此由 Sonnet 驱动的技术负责人无法将项目分支到成本更低的模型上。而设置 CLAUDE_CODE_FORK_SUBAGENT=0 可以完全关闭此功能。该设置会在除迁移脚本相关模块之外的所有地方继续进行分支操作,因为这些模块特意要求每次都保持完全干净的上下文环境。

通过 @ 提及实现跨会话消息传递。 这使得 lead-alpha、lead-beta 以及其下属的九组技术负责人和产品经理能够以独立的实时会话形式进行交流,而非处于同一个巨大的上下文窗口中。在提示语中输入 @ 加上会话名称即可直接联系另一个正在运行的 Claude Code 会话,其底层机制实际上调用了 SendMessage 函数。正是这一功能让双负责人协作成为可能。这两名负责人其实是独立的进程,拥有各自的内存,他们之间的沟通方式与普通人之间的沟通相同,即通过带名称的消息传递,而不需要依赖任何人构建的共享数据库。

/config 中包含了对话过期时间设置,以及会话如何处理来自其他会话的消息(接受、暂存或拒绝)的规则。当有数十个命名会话同时运行时,这样的控制功能非常实用:你肯定不希望某个项目的项目经理去联系客户,却忽略掉该项目的技术负责人。

这些其实并无特别之处,因为它们如今已是默认设置。不同之处在于,这些默认设置已经足够可靠,可以支撑起完整的监督层级结构,而以前人们只能依靠消息队列来尝试解决类似问题。

它必须能够应对的故障模式

采用两名带有心跳检测的负责人这一设计并非出于多疑,而是基于对 Anthropic 于八月中旬发布的关于多智能体系统中的模式与问题研究的阅读,该研究指出了几乎所有相关类别中可能出现的早期故障。

低方差一致性。研究显示,单个智能体的行为彼此极为相似,会选择相同的解决方案、名称和方法,尽管没有任何力量迫使它们如此。具体案例包括:30个智能体中有18个都将某个git分支命名为“mvp-game-loop”;在没有共同指导的情况下,虚构智能体也得出了相同的名称;超过一半的智能体选择光线追踪器或自托管编译器作为展示成果的工具;还有那些进行轮询的智能体,由于都选择了相同的较高轮询频率,共同产生了240万次任务请求。

在最初的一个月里,出现了较为轻微的问题。三个独立项目中的三名IC工程师彼此互不知情,他们都认为解决类似缺陷的最佳方法就是使用重试封装器。这三种封装器的退避策略略有不同,后来当这些项目需要相互协作时便产生了冲突。解决办法是严格限定IC工程师的职责范围,让所有跨项目决策都通过能够统筹单个项目的技术负责人来处理,再由能够统筹多个项目的高级负责人最终决策。技术负责人以下的任何人都不允许做出会超出其负责任务范围的决策模式。

相互依赖任务中的协调失败。在研究中描述的那个游戏开发实验中,许多拉取请求始终未被合并,因为相互冲突的修改被直接放弃,而非尝试协调解决。研究得出的结论并非要选择更强大的模型,而是智能体需要更精准地判断何时应分开工作、何时应合并修改。正因如此,处理不同任务的智能体之间可能无法直接沟通,所有事务都必须通过技术负责人处理,由他查看所有进行中的工作并安排顺序。虽然这种方式相比完全自由的并行处理会降低效率,但至少能避免两个智能体以互不兼容的方式共同修改同一个文件。

地盘争夺战。这是最令人担忧的情况。论文描述了三个被赋予完全相互冲突目标的智能体,它们逐渐升级为互相破坏的行为:关闭对方账户,以及启动具有随机名称的自我复制杀进程以躲避pkill -f命令。只有最新测试的模型能够缓和局势,在98%的测试中实现停战,而早期模型则要么陷入封锁状态,要么持续发生冲突。这正是当赋予智能体真实系统访问权限却不对其目标是否兼容进行外部监控时所存在的尾部风险。

这里的解决方案是结构上的,而非靠聪明才智。技术主管以下的任何角色都沒有超出自身项目范围的权限。除非有主管签字批准,否则技术主管也无法访问生产环境。设置这两名主管的部分目的就是,如果其中一人开始批准一些可能引发目标冲突的行为,另一人就能起到独立监督的作用,而不会成为同一体系内的附和者。

该论文承认这个问题尚未解决。用其话来说,协调并非仅仅源于更强的智能或各个个体角色的目标一致,而且让角色自主运行与要求它们停下来寻求人类帮助之间的矛盾依然存在。层级结构使得这些故障模式持续存在。它所改变的只是每种故障的影响范围,以及人们在问题扩散到九个项目之前发现问题的速度。

如果不运行九个项目,应借鉴什么

对于单个开发者,或是两三人的小团队而言,几乎没必要构建如此复杂的层级结构,假装需要它才是不诚实的。

无需采用双负责人监控模式。这种模式是为了应对“如果主管在凌晨3点突然去世而无人照看怎么办?”这样的问题。单独工作的开发者本身就可以承担监控职责,夜间程序崩溃的问题可以等到早上查看手机时再处理。

无需设置项目经理层级。当有九个并行项目争夺同一人的注意力时,撰写工单和协商工作范围才显得重要。而对于一两个项目,开发者自己就可以担任项目经理,额外的专职人员只会在你与工作之间增加一层沟通环节。

保留一名技术负责人,负责管理范围明确的集成电路,并将其整合为单一层级。 每个任务单元由一名代理负责拆解与审核,再派生出专门的子代理来执行具体工作,而非由一个庞大的会话处理所有事务。即便没有层级结构,采用这种派生机制也是值得的:由于上下文与缓存得以继承,每次需要协助处理子任务时都无需重复解释情况。

让每个下级代理仅负责系统中的有限部分。 据相关反馈,这一规则所预防的早期问题比任何提示工程技巧都多。

可直接复用的框架

这是目前仍可行的领导层与IC模式中最小的版本。它包含两个Claude Code智能体定义,以及一个无需托管队列或代理的小型自托管协调器,非常适合那些希望通过普通脚本来运行该模式的情况,比如驱动Claude Agent SDK,而非通过基于@提及的实时交互会话进行通信。

Claude Code原生版本以.claude/agents/tech-lead.md为起点:

---
name: tech-lead
description: Owns decomposition and review for one project. Forks IC agents for individual tasks, reviews their diffs, escalates only genuine blockers.
tools: Read, Grep, Glob, Bash, Edit, Agent
---
You are the tech lead for this project. You do not write most of the code
yourself. When a task arrives:
1. Break it into the smallest pieces that can be verified independently.
2. For each piece, spawn a fork subagent scoped to exactly one piece:
   subagent_type: "fork", with a prompt naming the specific files or
   directory it may touch and nothing else.
3. Review every diff before it is considered done. Reject anything that
   touches files outside the scope you gave it.
4. Only message the lead session (via SendMessage / @-mention) if you are
   blocked on a decision you cannot make with the context you have, or if
   two of your own IC agents produced conflicting changes.
Never let two IC agents work on overlapping files in the same task cycle.

.claude/agents/ic-migration.md 中展示的是范围极为狭窄的 IC:

---
name: ic-migration
description: Handles only database migration scripts under db/migrations/. Never touches application code.
tools: Read, Edit, Bash
---
You only read and write files under db/migrations/. If a task requires
changing anything outside that directory, stop and report back to whoever
assigned you the task instead of making the change yourself.
Write one migration per task. Run it against the local test database
before reporting done. Include the rollback in the same file.

技术负责人通过调用 Task 或 Agent,并指定 subagent_type: "fork" 以及涵盖单个工单的提示语,来创建该 IC 的分支。由于是分支形式,该 IC 已经包含技术负责人针对该工单的思考过程,因此无需再次说明。

自托管模式适用于跳过完整交互会话、仅希望利用 Anthropic API 实现协调功能的情况:无需代理,无需外部队列,只需磁盘上的一个文件即可。

# coordinator.py
# pip install anthropic --break-system-packages
# A local, file-backed mailbox so "agents" (just API calls) can hand
# work to each other without any hosted message broker.
import sqlite3
import json
import time
from anthropic import Anthropic
DB = "mailbox.db"
client = Anthropic()  # reads ANTHROPIC_API_KEY from env
def init_db():
    conn = sqlite3.connect(DB)
    conn.execute("""
        CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            to_agent TEXT,
            from_agent TEXT,
            body TEXT,
            status TEXT DEFAULT 'pending',
            created_at REAL
        )
    """)
    conn.commit()
    conn.close()
def send(to_agent, from_agent, body):
    conn = sqlite3.connect(DB)
    conn.execute(
        "INSERT INTO messages (to_agent, from_agent, body, created_at) VALUES (?, ?, ?, ?)",
        (to_agent, from_agent, body, time.time()),
    )
    conn.commit()
    conn.close()
def next_message(to_agent):
    conn = sqlite3.connect(DB)
    row = conn.execute(
        "SELECT id, from_agent, body FROM messages WHERE to_agent=? AND status='pending' ORDER BY id LIMIT 1",
        (to_agent,),
    ).fetchone()
    if row:
        conn.execute("UPDATE messages SET status='taken' WHERE id=?", (row[0],))
        conn.commit()
    conn.close()
    return row
def run_ic(scope_dir, ticket_body):
    """One scoped worker call. No memory between calls by design here,
    since we're not using Claude Code's native forking in this path."""
    response = client.messages.create(
        model="claude-sonnet-4-5",
        max_tokens=2048,
        system=f"You may only reason about files under {scope_dir}. "
               f"If the ticket requires anything outside that scope, "
               f"say so and stop.",
        messages=[{"role": "user", "content": ticket_body}],
    )
    return response.content[0].text
if __name__ == "__main__":
    init_db()
    send(to_agent="ic-migration", from_agent="tech-lead", body="Add index on users.email")
    msg = next_message("ic-migration")
    if msg:
        _, sender, body = msg
        result = run_ic("db/migrations/", body)
        send(to_agent=sender, from_agent="ic-migration", body=result)
        print(result)

它的设计刻意保持简单:一个 SQLite 文件充当消息队列,一个 Python 函数则充当 IC 智能体。但它具备生产版本的架构,拥有作用域隔离的工作者、基于消息的传递机制而非共享的可变状态,且不依赖任何付费服务。如果需求增长,转向 Claude Code 的原生分支功能及 @ 提及机制,在概念上仍是相同的架构,只是相关细节已由系统自动处理。

当前进展

这一切都尚未完成。需要升级的情况仍比大家期望的更为频繁,其中一些问题确实是作用域规则上的真正歧义,而非漏洞。研究显示,诸如智能体间的信誉系统以及替代临时层级结构的合理机制设计等真正解决方案,目前仍在整个行业范围内被探索中。

该账号自己的评价相当谦逊。据称,用一种大致模仿小型工程团队工作交接方式的层级结构来取代那种大而全的单一处理流程后,每日提示数量减少了一半,而且剩余的故障也变得简单易懂,不再令人困惑。这确实是一笔不错的交换。

相关阅读

  • 实用笔记:技能、钩子与子代理:优化 Claude Code 的使用方式 — 详细指导如何运用技能、钩子与子代理来优化 Claude Code,包括适用于采用该模式的团队的契约、检查机制以及可直接插入的代码模块。
  • 在 VS Code 中设置 Claude Code 及其超越自动补全的功能 — 教导如何安装 Claude Code、在 VS Code 终端中运行它、编写 CLAUDE.md 文件,以及如何为大型任务、调试、测试优先的开发流程和代码重构建立工作流,同时介绍其局限性。