CC 源码系列 #3|CC 没用 AutoGen,自己写了 8000 行调度代码
上一篇《CC 记忆凭啥不用向量数据库》发出后,有读者留言问:Agent Teams 怎么实现的?有没有用 AutoGen 或者 CrewAI?这篇带你扒开 Claude Code 多智能体团队系统的源码,看看它底层到底是怎么调度的。
目录
- 一行框架代码都没用
- 目录结构:就是一堆 JSON 文件
- 核心机制:文件 inbox
- Agent 生命周期:Idle ≠ Dead
- 任务系统:无锁竞争设计
- 这套设计的 7 条代价
- 与主流框架对比
- 从源码反推的 5 条使用技巧
- 我的实际运行场景
- 一句话总结
一行框架代码都没用
我本来以为 Claude Code 的多智能体系统会包一层 AutoGen 或 CrewAI——毕竟现成的轮子摆在那里,不用白不用。
翻完源码之后,发现完全不是这回事。
CC 的 Agent Teams 实现在 packages/cli/src/services/teams/ 下,加上相关类型定义和工具层,合计接近 8000 行代码,一行现成框架都没有。
整个系统基于文件系统构建,没有数据库,没有消息队列,没有 Redis,没有 RPC。
读完的第一感受是:很多框架党想多了。
目录结构:就是一堆 JSON 文件

CC Teams 的运行时目录长这样:
~/.claude/teams/<team-name>/
members.json # 团队成员注册表
tasks.json # 任务列表(全局共享)
inboxes/
team-lead.json # team-lead 的收件箱
agent-1.json # agent-1 的收件箱
agent-2.json # agent-2 的收件箱
...
state/
<agent-name>.json # 各成员运行时状态
这就是整个多智能体系统的"基础设施"——一个目录,几个 JSON 文件。
没有消息队列服务要启动,没有数据库要初始化,没有网络端口要监听。文件系统即协议。
核心机制:文件 inbox

每个团队成员的"收件箱"是一个独立的 JSON 文件,路径为 inboxes/<name>.json。
消息格式如下:
[
{
"id": "msg_abc123",
"from": "team-lead",
"to": "agent-1",
"text": "开始处理任务 #3",
"timestamp": "2026-04-07T10:00:00Z",
"read": false
},
{
"id": "msg_def456",
"from": "agent-1",
"to": "team-lead",
"text": "{\"type\": \"idle_notification\", \"agent\": \"agent-1\"}",
"timestamp": "2026-04-07T10:05:00Z",
"read": true
}
]
SendMessage = append 一条消息到目标 inbox 文件。
收消息 = 读文件,筛选 read: false 的条目。
协议消息(shutdown_request / idle_notification / plan_approval_request)也走同一个 inbox,靠 text 字段里的 JSON type 字段区分。没有单独的控制通道,没有优先级队列。
这个设计意味着:如果某个 Agent 卡住了,你可以直接 cat inboxes/agent-name.json 看消息有没有送到、有没有被 read。调试体验极其直接。
Agent 生命周期:Idle ≠ Dead

这是整个系统最反直觉的一点。
处理完一个 turn 之后,Agent 进程不会退出。
它会:
- 向 inbox 发一条
idle_notification - 进入轮询状态,持续读取自己的 inbox
- 检测到新消息后立即唤醒,开始处理下一个 turn
轮询间隔很短,唤醒是毫秒级的。
为什么不重启?重启的成本是重新加载所有上下文。 进程保活意味着 context window 里的内容还在,下一个任务可以直接衔接。这对于有状态的长期任务尤其重要。
所以当你看到一个 Agent 长时间没有输出时,不要以为它挂了——它可能只是在 idle,等消息。
正确的排查方式:直接 cat inboxes/<name>.json,看最后几条消息的 read 状态。
任务系统:无锁竞争设计

任务存储在 tasks.json 里,格式大致如下:
[
{
"id": "task_001",
"subject": "修复登录页面 Bug",
"description": "用户反馈登录后跳转失败,复现路径:...",
"status": "in_progress",
"owner": "agent-1",
"blockedBy": [],
"blocks": ["task_002"]
},
{
"id": "task_002",
"subject": "更新登录相关文档",
"status": "pending",
"owner": null,
"blockedBy": ["task_001"],
"blocks": []
}
]
没有分布式锁。
CC 的任务竞争靠的是应用层规范:team-lead 主动派活,不依赖 Agent 自主抢占。这规避了多 Agent 同时 claim 同一个任务的并发问题——因为 team-lead 是单点分派的。
blocker 依赖(blockedBy / blocks)由 Agent 手动维护。清理不及时会导致任务链卡住。依赖链越长,出问题的概率越高。这是这套设计里明确的 trade-off。
这套设计的 7 条代价

这套方案听起来很美("就是几个 JSON 文件"),但不是没有代价:
1. 并发写冲突
多个 Agent 同时向同一个 inbox 文件 append 消息,存在并发写丢消息的风险。CC 没有加锁,靠的是写入频率较低(每次写入有 LLM 处理间隔)来规避。
2. 文件只增不减
inbox 文件会持续增长,没有自动 GC。长时间运行后需要手动清理,否则读取成本上升。
3. 崩溃恢复弱
进程崩溃后,read: false 的消息会被重新处理(at-least-once 语义)。没有持久化的消费位点,幂等性由上层业务保证。
4. 无横向扩展
整个系统假设单机运行。不支持跨机器的 Agent,不支持动态扩容。这是设计目标决定的,不是遗漏。
5. 依赖链手动维护
blockedBy / blocks 需要 Agent 自己更新。如果 Agent 忘记清理,或者清理时机不对,任务链会卡死。
6. 消息无优先级
所有消息 FIFO 处理,没有优先级队列。紧急任务没有插队机制,只能靠 team-lead 提前发送。
7. 状态可观测性差
没有统一的 dashboard,观察整个团队状态需要手动读多个 JSON 文件。这在调试复杂场景时比较费力。
CC 在源码注释里对这些问题都有所承认。这是典型的 "worse is better":牺牲严格正确性换简洁,对目标场景(单机、小团队、可审计)来说是合理的取舍。
与主流框架对比

| 维度 | CC Teams | AutoGen | CrewAI |
|---|---|---|---|
| 依赖 | 零(文件系统) | Python 生态 | Python 生态 |
| 消息机制 | 文件 inbox | 内存 / Redis | 内存 |
| 持久化 | 自动(文件即状态) | 需要配置 | 需要配置 |
| 可观测 | cat JSON 即可 | 需要日志系统 | 需要日志系统 |
| 横向扩展 | 不支持 | 支持 | 支持 |
| 学习成本 | 极低 | 中等 | 中等 |
| 目标场景 | 单机、小团队、可审计 | 通用、分布式 | 通用、角色化 |
CC Teams 明显是为单机、小团队、可审计优化的。横向扩展不是它的目标,也不应该是你对它的期待。
从源码反推的 5 条使用技巧

1. 卡住直接 cat inbox
cat ~/.claude/teams/<team-name>/inboxes/<agent-name>.json | jq '.[] | select(.read == false)'
这一条比任何日志都直接。消息在不在、有没有被读,一目了然。
2. 别频繁重启 Agent
重启意味着 context window 清空。除非你确认当前上下文已经失效,否则 idle 状态的 Agent 不需要重启。
3. shutdown 也是消息
要关闭某个 Agent,发一条 shutdown_request 消息到它的 inbox,不要直接 kill 进程。优雅关闭能保证当前 turn 处理完成后再退出。
{
"type": "shutdown_request",
"request_id": "req_xxx"
}
4. team-lead 主动派活,不要等 Agent 自主抢占
任务分配收敛更快,也避免并发 claim 冲突。
5. 依赖链扁平化
blockedBy 链越长,出问题越多。能并行的任务尽量不加 blocker,单链深度不超过 3 层是个经验值。
我的实际运行场景
我自己跑的是一个 commander 团队:1 个 lead + 7 个成员。
成员分工:
xhs-ops:每天巡查小红书笔记评论,识别用户需求,上报 commanderjuejin-ops:同步发布技术文章到掘金(就是正在做这件事)github-ops:巡查开源项目的 Issue,自动派发给开发成员cc-stats-dev:负责 cc-statistics 的功能开发和 Bug 修复stock-dev:AI 投研分析服务,接收用户的股票分析请求album-dev:KMP 相册应用开发
典型流程:
用户在小红书评论"能不能支持 CSV 导出?"
→ xhs-ops 识别 → 上报 commander
→ commander 评估 P2(体验改善)
→ 派发给 cc-stats-dev
→ cc-stats-dev 进 ~/Claude/cc-statistics 仓库实现
→ 完成后通知 commander
→ commander 通知 xhs-ops
→ xhs-ops 回复用户 + 发布更新帖
整套流程全靠 inbox + task list 驱动,没有额外的协调层。
有一次团队成员 idle 半小时,我以为挂了,直接 cat inboxes/xxx.json 一看——消息根本没送到,是 SendMessage 时写错了目标名。这种调试体验在看日志的框架里是复现不了的。
一句话总结
CC Teams 用文件 inbox 替代消息队列,用应用层规范替代分布式锁,用进程保活替代冷启动,用 JSON 文件替代状态数据库。Idle ≠ Dead,cat inbox 即调试,worse is better 是它的设计哲学。
如果你之前因为 AutoGen / CrewAI 太复杂没敢入坑,CC Teams 是个很好的起点——理解成本几乎为零,跑起来就是几个 JSON 文件。
延伸:用 cc-statistics 观察多 Agent 的 token 消耗
如果你在跑 CC Teams,cc-statistics 可以帮你统计各 Agent 的 token 消耗分布。
多 Agent 并行时,token 消耗会比单 Agent 高出好几倍。cc-statistics 支持按 session 拆分,能看到每个 Agent 的 input/output token 占比,方便评估成本。
下一篇预告
CC 源码系列 #4:Tool 系统——为什么一个 LLM 工具调用框架要写 1 万多行?
从 Bash、Read、Write、Edit、Glob 这些基础工具,到权限沙箱、超时控制、hook 系统,下一篇扒开看。