Skip to content
Go back

我给 Claude 设计了一个自动驾驶系统

Updated:

项目地址:github.com/BassShang/autopilot

20 倍算力的瓶颈

我买了 Claude Max plan,最贵的那个档位。官方宣称有 20 倍的 Opus 模型用量。

用下来我发现,瓶颈根本不在算力。

Claude Code 的使用过程是这样的:它写一段代码,你 review 一下,觉得没问题就让它继续。它再写一段,你再看一下。每一步都需要人手动确认。这意味着不管 Opus 用量给你多少倍,你的产出上限取决于你能多快地做 code review。

还有一个更恼人的问题:context 太长之后,session 的质量会明显下降。命名开始随意,逻辑开始混乱,有时候写出来的东西跟前面的代码互相矛盾。这时候你只能开一个新的 session,但新 session 对之前的上下文一无所知,又得重新建立理解。

说白了,20 倍的算力被两个东西卡死了:人的交互带宽,和 context 窗口的物理限制。

所以我需要一个系统,把人从循环里摘出来。

我想解决什么问题

两个。

上下文膨胀导致代码质量下降

用 Claude Code 写过项目的人应该都有体感:session 刚开始的时候,代码质量很好,逻辑清晰,命名规范。但随着对话越来越长,上下文窗口塞满了几十轮的对话历史,质量开始肉眼可见地下降。命名变随意,边界情况被忽略,有时候甚至重写之前写过的逻辑。

这不是 Claude 变笨了。是上下文窗口的物理限制。当窗口里塞了太多东西,模型的注意力被稀释了,就像你在一个吵得要死的会议室里试图写代码。

每次都需要人验收

Claude Code 写完一段代码,你得自己 review,确认没问题才能让它继续。如果你一直盯着倒也行。但我想要的是:把需求丢给它,出去吃个饭回来看结果。中间不需要我参与。

这意味着系统需要有自我验证的能力。不是 Claude 自己说”我觉得写得不错”,而是有一个独立的、看不到实现过程的 agent 来做 code review,给出客观的判断。

面临的挑战

这两个问题看起来各自独立,但解法其实指向同一个方向:多 agent 编排

用多个短 session 替代一个长 session 解决上下文膨胀,用独立的 Reviewer agent 替代人工验收。但引入多 agent 之后,新的问题来了:

谁来管理这些 agent? 任务怎么拆,按什么顺序执行,前一个任务的产出怎么传给后一个?需要一个调度者。

调度者自己的上下文会不会膨胀? 如果调度者是一个长驻的 Claude session,跑几十轮循环之后,它自己的上下文也炸了。问题又回来了。

状态存在哪? 如果每个 agent 都是独立的短 session,它们之间怎么传递信息?不可能靠”记忆”,因为每个 session 启动时是干净的。

子 agent 怎么调用? 最初我想的是轮询:分发任务后每隔几秒检测子 agent 是否完成。但很快发现轮询引入了定时器管理、状态竞争、延迟浪费等一堆复杂度。最终选择了同步阻塞调用,通过 claude -p 启动子 agent,调用本身就是阻塞的,子 agent 完成后命令返回结果,Orchestrator 直接拿到产出进入下一步。简洁、可靠、无需定时器。

用户怎么介入? 系统在自动跑,但遇到需要人做决定的情况(比如一个任务反复失败),怎么通知用户、等待回复、拿到回复后继续?

解决方案

四个角色

我设计了四个角色,每个都有明确的职责边界:

角色类比职责边界
Architect架构师只规划,不写代码。输出任务列表和技术决策文档
Orchestrator项目经理只调度,不创造。读状态 → 分派 → 更新状态 → 退出
Implementer程序员只执行分配的任务,不扩大范围
Reviewer代码审查员独立审查,看不到 Implementer 的推理过程,只看产出

Architect(架构师) 只在项目启动和异常升级时出场。把需求拆成带依赖关系和验收标准的任务列表。关键约束:绝对不写代码。一旦它开始写代码,注意力就浪费在细节上了,规划质量反而下降。写完计划就退场。

Orchestrator(调度者) 是整个系统的心跳。但它不是一个持续运行的 agent,而是一个被定时唤醒的无状态函数(这个设计后面会解释)。每次醒来做一件事:读状态 → 派活 → 等结果 → 审核 → 更新状态 → 退出。它越笨越好,越机械越稳定。

Implementer(执行者) 是干活的。每次启动只看到当前任务的描述、验收标准、和精确筛选过的相关文件。不知道项目全貌,也不需要知道。有一个重要的约束:不扩展范围。任务说”实现用户登录”,它不应该顺手把”忘记密码”也做了。越界 = 引入调度者不知道的变更 = 状态管理炸掉。

Reviewer(审核者) 做独立的 code review。它拿到任务需求和 Implementer 的代码产出,但看不到 Implementer 的推理过程。这很重要:如果 Reviewer 能看到 Implementer 是怎么想的,它会被带着走,失去独立判断。就像真实团队中,code review 只看 PR diff,不看开发者的内心独白。返回三种结论:通过、需修改(附具体意见)、需重做。

无状态 Orchestrator

调度者的上下文膨胀问题,我的解法是:让它根本不存在于内存中。

核心哲学四个字:召之即来、完事就走

Orchestrator 是一个 Cowork scheduled task,每 5 分钟唤醒一次。每次唤醒都是一个全新的 Claude session,干净的上下文。它的全部”记忆”在磁盘上的 state.json 里。读文件、做决策、调子 agent、写文件、退出。下次唤醒再来。

这意味着:

  • 每次执行都有干净的上下文窗口,不存在质量退化
  • 所有记忆都在文件系统中,而非 Claude 的对话历史里
  • Orchestrator 崩溃不会丢失状态,state.json 就是恢复点
  • 多个项目可以并行运行,互不干扰

但有一个问题:每次唤醒都是全新的 Claude 实例,上一个周期发生了什么?如果不做处理,新的 Orchestrator 只能从任务状态中推断,很多”软信息”就丢了。比如”上一轮 Reviewer 差点就通过了,只是测试覆盖率不够”或者”task-04 的依赖刚完成,下一轮应该优先处理”。

所以我加了一个 orchestrator_notes 字段。每个周期结束时,Orchestrator 写一两句话的摘要,告诉”下一个自己”发生了什么、接下来该关注什么。本质上是一个跨实例的短期记忆。不是完整的上下文传递(那会重蹈上下文爆炸的覆辙),而是一张精炼的便签纸。

状态文件是唯一的 truth

所有 agent 之间的信息传递只通过文件系统。state.json 里记录了一切:任务列表和每个任务的状态、Reviewer 的历次反馈、Orchestrator 留给下次唤醒的备忘、并发锁(orchestrator_running + orchestrator_last_run)、用户交互队列。

我特意把 status(项目在任务生命周期中的位置)和 should_run(自动执行的开关)设计成正交的。这个区分很重要:用户想”暂停”的时候,不应该丢失任务进度。如果只有一个 status 字段,“暂停”只能用一个额外的状态值(比如 paused),会和任务生命周期纠缠在一起。

分成两个维度之后就清晰了:

statusshould_run含义
runningtrue正常运行
runningfalse用户暂停(kill),任务保留但不执行
completedfalse项目完成
waiting_for_usertrue等待用户输入,Orchestrator 仍会唤醒但跳过工作

kill 只改 should_run,不碰 statusresume 反之。两个维度互不干扰。

.ap/ 在项目目录内

最初我把所有 AP 项目的状态集中存放在 ~/Projects/autopilot/ 下。但很快发现问题:项目和 autopilot 系统耦合太紧,无法灵活地把一个已有项目纳入管理,而且项目在磁盘上分散各处,集中目录不自然。

所以改成了项目本地化:状态文件住在项目根目录的 .ap/ 下面,和代码在一起,就像 .git/ 属于项目本身一样。

my-project/
├── .ap/
│   ├── state.json
│   ├── context/
│   │   ├── architecture.md
│   │   └── role-prompts.md
│   └── logs/
├── src/
└── package.json

一个全局注册表 ~/.ap/registry.json 记录所有 AP 项目的路径,给 ap status 命令用。这个决策直接催生了 ap adopt 命令,让已有项目可以直接纳入 AP 管理。

显式激活

一个重要的产品决策:AP 绝不自动启动

即使项目目录下有 .ap/,即使用户描述的改动很大,AP 也不会自作主张地接管。原因很简单:AP 是重量级工作流,误触发的代价很高。用户可能只是想快速修个 bug,不需要四角色编排。

触发规则:

  • 用户明确说 ap setupap status 等命令,立即触发
  • 用户描述大型改动但没提 AP,建议使用 AP,等待确认,绝不自启
  • 用户做小改动,直接帮忙,不提 AP

异步用户交互

Orchestrator 是短命的,等不了用户。所以当它需要人做决定时(比如一个任务被 Reviewer 打回三次),它把问题写进 state.json,设状态为 waiting_for_user,然后正常退出。后续每次唤醒检查有没有回复,没有就跳过,有了就继续。用户通过 ap interact 回答问题。

工作原理

把上面的东西串起来,完整的流程分三个阶段。

启动

确认

修改

用户: ap setup

面试用户

(项目背景、技术栈、成功标准)

创建 .ap/ 目录

启动 Architect

拆解任务 + 输出架构文档

用户确认计划?

注册定时任务

Orchestrator 每 5 分钟唤醒

执行循环

false

true

已锁

未锁

waiting_for_user

completed

running

通过

需修改

需重做

失败 > 3 次

定时唤醒 Orchestrator

(全新 Claude session)

读取 state.json

should_run?

退出

检查并发锁

检查 status

找到下一个可执行任务

(依赖已完成的)

启动 Implementer

(同步阻塞)

启动 Reviewer

(同步阻塞)

Reviewer verdict?

标记任务完成

保存 feedback

下轮带 feedback 重试

重置任务产物

下轮从头来

升级给用户

waiting_for_user

写 orchestrator_notes

释放锁,退出

收尾

所有任务完成后,跑一次全量测试 + lint + 类型检查。全过就写 summary,标记 completed。有失败就生成修复任务,继续循环。

已有项目

除了从零 setup,还有 ap adopt,把一个已有代码库纳入 AP。区别在于 Architect 规划前会先并行启动几个子 agent 深度阅读代码库,理解现有架构和 convention,然后基于这些理解来规划新工作。所有历史上下文(架构决策、模块边界、接口约定)都保留着,新的 Implementer 能直接读到,不需要重新发现。

关键设计总结

  1. Orchestrator 无状态。每次唤醒都是全新的 Claude session,上下文永远干净。彻底解决了 context 爆炸导致代码质量下降的问题。
  2. Reviewer 按需唤醒。针对每个任务临时启动一个 Reviewer,只给它任务需求和代码产出,不给多余的上下文。简洁的上下文确保了代码审核的准确度。
  3. 可打断、可交互。执行过程中随时可以 ap kill 暂停,用 ap interact 回答系统的提问,用 ap resume 继续。人始终在回路中,但不需要一直盯着。
  4. 可接入老项目ap adopt 命令让已有代码库直接纳入 AP 管理,Architect 会先深度阅读现有代码再规划,不是从零开始。
  5. 多项目并行。每个项目的状态独立存在于自己的 .ap/ 目录,一台电脑上可以同时跑多个 AP 项目,互不干涉。

限制

  1. 太 heavy 了。四角色编排、定时唤醒、状态文件管理,对于”改个 bug”或者”加个按钮”这种小事来说是杀鸡用牛刀。AP 适合的是需要拆成多个任务的中大型改动。
  2. token 消耗巨大。每个任务至少要跑一轮 Implementer + Reviewer,失败重试还要翻倍。这个项目存在的意义就是把我的 Claude Max 20 倍 plan 压榨到极致。如果你用的是按量付费,账单会很刺激。

未来计划

  1. Eco 模式。不是所有任务都需要 Opus。计划加一个 eco 模式,对简单任务用更轻量的模型,复杂任务才上 Opus,在质量和 token 消耗之间找平衡。
  2. Web 看板。现在看项目状态只能用 ap status 命令行查看。计划做一个 web 看板,在浏览器里实时查看各项目状态、任务进度、审查历史,手机上也能看。