作为一名深耕业务的前端开发者,在近期深度探索 AI 辅助编程(AI Coding)的过程中,我萌生并独立主导了这个开源项目。
【起因与痛点】 在使用大模型开发项目时,我发现像 Codex / Claude-3.5-Sonnet 这样的顶级模型价格昂贵。如果手里同时推进多个项目,完全用昂贵模型写基础业务代码非常不划算。 于是我产生了一个构想:能不能让“昂贵且聪明”的模型充当架构师与监工,去驱动和审查“便宜且量大”的模型(作为基层码农)干活? 并且,我希望这一切是全程 100% 自动化、零人工干预的。
【灵感与破局:借鉴 Vue 的 Pinia 思想】 在实验初期,我试图让两款不同的 AI 编程软件(比如 Codex 和另一款终端大模型)建立直接的 WebSocket 或 RPC 连接。但由于不同工具的环境黑盒,多次实验均以失败告终。 在苦思冥想时,我想起了前端老本行——Vue 生态中的 Pinia 全局状态管理。 既然不同组件(AI 软件)无法直接通信,为什么不把整个项目根目录作为唯一的全局 Store 呢? 只要我们在本地规定好一套严密的“读写锁”与“状态机制”,不同的编程软件共用同一套项目目录:
- Watchdog(看门狗) 作为 Store 的响应式监听器,实时监控状态变更。
- Supervisor(监工方/贵模型) 充当架构侧的 Action,负责心跳响应与结果校验。
- Builder(施工方/便宜模型) 充当视图/业务更新层,自动进行底层逻辑的编写。
昂贵的模型只需负责补充好 docs/prd.md 需求文档与编写严苛的测试用例,便宜的模型就能依靠这套“状态管理”机制,彼此接替,直到把整个项目开发完毕。
本项目实现了一套**零依赖(Zero-Dependency)**的三权分立状态机。
- Builder (施工方):纯粹的代码生产者。执行完任务后,主动调用本地阻塞脚本挂起,交出控制流。
- Supervisor (监工方):冷酷的代码审查者。不写业务代码,只跑测试和审查差异。若不合规,则将状态打回
REJECTED强制重写。 - Watchdog (看门狗):无情的流水线传送带。纯原生 Node.js 进程,只盯防根目录的
bridge.md状态机锁文件,负责调度和唤醒。
实现真正的“无人值守”,远比画架构图要艰难。在这个项目中,我遇到并解决了三个极度棘手的跨环境工程 Bug,这也是本项目最核心的技术壁垒:
- 痛点:最初监工方尝试使用 WMI (
Get-CimInstance) 嗅探系统级进程状态,但在 Windows 下即使是管理员权限也频频遭遇“拒绝访问”。 - 解决:坚决摒弃全局扫描。改为依赖轻量级的文件级互斥机制——通过
fs.openSync(path, 'wx')的排他创建标志实现锁(该系统调用由操作系统保证原子性,不存在「先检查再创建」的竞态窗口),配合 PID 存活探测实现陈旧锁自动回收。将跨进程通信降维到文件系统层面,完美绕过了 UAC 权限限制。 - 工程验证:实测 8 进程 × 25 次并发自增,无锁时丢 58%(83/200),加锁后零丢失(200/200)。详见
tests/lock-primitives.test.cjs。
- 痛点:施工方在拉起前端 Vite 开发服务器时,由于是阻塞式前台进程,导致大模型的终端执行(Tool Call)一直挂起,无法拿回控制权推进状态。
- 解决:强制将所有长运行服务剥离。利用
Start-Process ... -WindowStyle Hidden及 Node 的detached模式将服务丢入后台守护,确保命令秒级返回,将控制流交还给大模型。
- 痛点:这是最难排查的一个 Bug。施工方执行挂起脚本等待任务,但在第 120 秒时,大模型客户端因为“单次工具执行超时限制”自动退出了挂起状态。而监工方的深度审查往往需要 150 秒。双方在时间轴上产生致命脱节。
- 解决:引入了**“自我续期挂起” (Self-Renewal Wait Pattern)** 韧性机制。我在传给大模型的 Prompt 协议中下达死命令:如果脚本因超时返回,绝对禁止停止思考,必须立即发起新的挂起脚本调用,用无限接力的方式填平时间差,确保链路永不断裂。
- 痛点:崩溃后残留的锁需要被自动接管,否则一次崩溃就永久锁死。但最初的实现是「读锁 → 判 PID 已死 → 覆盖写锁」三步——这不是原子的。实测 6 个进程并发接管同一个陈旧锁,5 个进程全部返回“接管成功”,锁文件最后只剩 1 个 PID,可另外 5 个都以为自己持着锁。也就是说,在最需要互斥的时刻(看门狗崩溃重启后),互斥恰好失效,两个 Watchdog 会同时驱动状态机。
- 解决:改为三段式接管,每段都有一次内核级原子判定:① 抢占凭证(
${lockPath}.reclaim.${token},名字含唯一 token,openSync('wx')保证只有一个候选者);② 用rename把陈旧锁原子搬到隔离区,并校验搬走的字节与判定时完全一致(防 ABA);③ 锁路径此刻为空,再对它做一次openSync('wx')—— 这次原子判定才是归属的唯一权威。任何一段失败都明确判负退出,绝不进入临界区。 - 工程验证:实测 6 进程 × 5 轮并发接管,每轮有且仅有一个赢家,临界区重叠观测 0 次。见
tests/concurrency-fixes.test.cjs的 D1-1 / D1-6b。
- 痛点:锁竞争时需要退避重试。最初用
execSync("ping -n N 127.0.0.1")实现 sleep——这是同步阻塞调用,会连事件循环一起钉死。实测请求 200ms 退避,实际阻塞 1365ms(近 7 倍);阻塞 2368ms 期间,setInterval(50ms)触发了 0 次(理论约 47 次),fs.watch回调同样无法触发。也就是说:一旦锁被竞争,看门狗在最需要工作的时候彻底失聪。还发现一个更隐蔽的问题:ping -n N的语义是「发 N 个包、间隔 1 秒」,总耗时约 N-1 秒,导致所有 ≤1s 的退避塌缩成同一个 ~1.3s,指数退避策略在 Windows 上退化成常数退避。 - 解决:改为非阻塞的
await new Promise(r => setTimeout(r, ms))。这要求把withLock及其调用链(writeBridge、tick、assembleReviewRequest、consumeVerdict)系统性改为 async,共 10 处调用点补上await,并保证「状态落盘」先于shutdown()——否则进程可能在状态写入前就退出,状态机停在旧状态。 - 工程验证:实测退避期间事件循环心跳从 0 次 → 22 次;退避耗时从 1365ms 降到毫秒级。见 D2-2 / D2-3。
- 痛点:这是本轮审计中最危险的缺陷——因为它不报错。
bridge.md的状态解析正则硬编码\n行尾,而仓库core.autocrlf=true且没有.gitattributes。任何 Windows 用户 clone 后,bridge.md就是 CRLF 行尾,parseBridge()返回null,tick()首行直接 return。结果是状态机彻底停摆、零日志、零报错——用户只会看到「什么都没发生」,且没有任何线索指向行尾。实测:CRLF 文件的正则匹配结果就是null。 - 解决:三重防线。① 仓库层:新增
.gitattributes强制* text=auto eol=lf,从根源归一化;② 解析层:parseBridge()在做任何正则匹配前统一调用normalizeEol(),把 CRLF/CR 一律转成 LF;③ 兜底层:所有派生正则加\r?,逐行取值时再replace(/\r$/, '')——否则status会变成"PENDING_DEV\r",导致所有状态判断失配。 - 工程验证:CRLF 与 LF 文件的解析结果完全一致,且含反证测试(证明不做归一化时 CRLF 确实解析失败)。见
tests/crlf-regression.test.cjs12 项。
- 痛点:看门狗要审查用户项目的 git 历史,但进程 cwd 取决于用户从哪个目录启动。若 cwd 完全不在 git 仓库内,
git rev-parse抛fatal: not a git repository,而旧代码catch { return null }把异常吞掉——结果是 diff 审查静默降级:不报错,只是审查内容为空。用户看到的是「Supervisor 说没问题」,而不是「审查根本没跑」。 - 解决:启动期用
git rev-parse --show-toplevel探测一次仓库根并缓存,后续所有 git 命令与用户自定义 checks 都以该根为cwd(不依赖用户从哪启动);探测失败则明确 warn 到控制台。同时统一走execInProject()入口,并约定返回值契约:探测失败返回严格null而非空字符串,让调用方能区分「项目不可用」(配置问题)与「命令成功但无输出」(正常状态)。 - 一个被实测推翻的直觉:最初以为「给 execSync 加
cwd: process.cwd()」就能修。实测发现 git 会自动向上查找.git,cwd 在仓库内任意层级都正常,所以那本来就是默认值、加了毫无作用。真正的问题是「完全不在仓库内」的场景。 - 工程验证:见
tests/git-root-detection.test.cjs12 项,覆盖仓库内(含深层子目录)、非仓库、返回值契约三种场景。
以下是项目在真实环境下,完全由大模型自行流转、互相协作与审阅的运行实况:
左上:监工方 (Supervisor) 正在异步轮询。右上:施工方 (Builder) 完成代码编写并触发重审。下方:Watchdog 实时调度。
要体验本项目,只需在一个空文件夹下执行:
npx agent-bridge init如果您想深入了解底层协议细节、CLI 指南或想参与贡献,请查阅以下核心文档:
- 🧠 架构解析 (Architecture)
- 📜 协议规范 (Protocol Spec)
- 💻 命令行指南 (CLI Usage)
- 💡 实战案例 (Examples)
- 🤝 贡献指南 (CONTRIBUTING)
npm test # 运行全部 4 个套件(共 90 项断言,含真实多进程压测)
npm run test:concurrency # 仅跑并发缺陷修复验证(45 项,最核心)| 套件 | 断言数 | 验证内容 |
|---|---|---|
lock-primitives |
21 | 锁原语基础行为、无锁对照组(证明锁确实生效) |
crlf-regression |
12 | CRLF 行尾回归(高危,防状态机静默停摆) |
git-root-detection |
12 | git 仓库根探测(防 diff 审查静默降级) |
concurrency-fixes |
45 | 四个并发缺陷的修复验证 + 端到端联调 |
测试设计原则:所有并发断言都必须保留对照组数据(例如无锁时 8 进程丢 87%、 加锁后 200/200)——没有对照就无法证明锁是否真的生效。判定互斥的正确判据是 「任意时刻只有一个进程在临界区内」(观测临界区重叠次数),不能用「赢家数 > 1」 ——串行接管下多个进程先后合法持锁,赢家数必然大于 1。
tests/_archived/下是审计过程中的历史对照实验,因对照样本丢失已归档,不参与npm test。
“真正的自动化不是赋予大模型无限的自由,而是为其戴上最严酷的工程枷锁。” —— 感谢您的阅读。


