- 人工智能
- AI Agent
- Agent 工作流
- CLI
- 研发协作
- AI 技能
- MCP 服务
【免费下载链接】loop-engineering
Practical patterns, starters & CLI tools for loop engineering with AI coding agents. Design systems that prompt and orchestrate agents (inspired by Addy Osmani and Boris Cherny). Includes loop-audit, loop-init, loop-cost.
本文基于 loop-engineering 仓库中的真实生产事故记录 stories/multi-loop-collision.md 展开。它记录了两个独立调度的 AI 行动循环(CI Sweeper 与 PR Babysitter)在同一分支上各自生成互相冲突的修复提交,最终造成约 5 倍 token 消耗和 45 分钟人工解耦的完整经过。读完本文,你将掌握:行动循环为什么需要"分支锁"(branch lock)、acting_on状态约定如何在多个循环之间实现碰撞检测、以及 loop-worktree 的lock/unlock命令如何把这一约定从"靠人自觉"升级为"机械化的咨询式锁"。
事故背景:两个循环盯上了同一条分支
事故场景非常简单,却极具代表性。团队同时在同一个仓库里运行着两个具备"自动修复"能力的行动循环(L2):
| 循环 | 配置 | 职责 |
|---|---|---|
| CI Sweeper | /loop 15m,最多重试 3 次 | 监控失败 CI,诊断并提交最小修复 |
| PR Babysitter | /loop 10m,同一仓库 | 盯守活跃 PR 的 CI、review、rebase 与合并 |
两者共享的目标是fix/auth-token-refresh分支,对应 PR #318。按照 patterns/ci-sweeper.md 的定位,CI Sweeper 负责"对 main 或活跃分支上的失败 CI 快速响应";而按照 patterns/pr-babysitter.md 的定位,PR Babysitter 负责"把 PR 从开题推到可合并状态"。当一条 PR 的分支上同时出现失败的 CI 测试时,两个循环的职责范围天然重叠——这正是事故的温床。
什么在正常工作:两个循环的判断都没错
复盘首先要诚实记录做得对的部分。根据 stories/multi-loop-collision.md 的记录:
- 两个循环都独立且正确地定位到了失败的
auth测试(test_refresh_token_expiry一类的鉴权令牌刷新用例),说明各自的 triage 分类逻辑工作正常; - 周二早上审计时,两个循环的状态文件都如实记录了这次重叠(
ci-sweeper-state.md与pr-babysitter-state.md),说明状态文件作为可观测性载体是有效的。
问题从来不是"循环不会发现问题",而是"两个循环没有互相感知"。
什么坏了:两套互相冲突的修复提交
事故链条如下,时间戳来自原文档:
- 14:02CI Sweeper 在
fix/auth-token-refresh分支上拉起一个 worktree,开始实施它认为的最小修复; - 14:07PR Babysitter 在同一个 PR上拉起了另一个不同的最小修复方案;
- 结果是两个提交、两种互斥的修复思路同时出现在 PR 上,reviewer 完全被搞糊涂了,不知道哪个是"真正的修复";
- 更糟的是成本失控:该 PR 的合并 token 消耗约 40 万 token,而正常水平约 8 万 token——一个 PR 花了平时 5 倍的预算。
这两个循环各自遵循了自己的模式文档:CI Sweeper 打开 worktree、起草修复、等待 verifier 与人工确认(见 patterns/ci-sweeper.md 的"Typical Cycle");PR Babysitter 对失败检查项拉起minimal-fix子代理(见 patterns/pr-babysitter.md 的典型循环)。单个循环看,每一步都是规范动作;整体看,缺了一道"互斥"检查。
量化指标
原文档给出了完整的事故度量表:
| 指标 | 数值 |
|---|---|
| 重复修复尝试 | 2 次 |
| 人工解耦耗时 | 45 分钟 |
| 根因 | 缺少acting_on碰撞检查 |
结合 patterns/ci-sweeper.md 的成本画像可以进一步理解"为什么这么贵":一次典型的 L2 修复尝试(worktree + implementer + verifier)本身就接近 20 万 token,两次独立修复叠加后,token 消耗落到 40 万量级完全符合成本模型的推算;而 45 分钟的人工解耦时间,本质上是把两个 AI 循环各自产生的错误上下文重新对齐的代价。
教训:行动循环需要状态层面的"分支锁"
原文档给出的教训只有一句话,但它是整套 multi-loop 协调机制的核心:
Action loops need abranch lockin state.
行动循环(会实际改动分支、提交代码的循环,L2 及以上)必须在状态文件中声明自己"正在处理哪条分支/哪个 PR",即写入acting_on: branch-or-pr-id。随后这一约定被固化为 docs/multi-loop.md 的碰撞检测流程:
- 每个行动循环在状态文件中写入
acting_on: 分支名或 PR id; - 在拉起 worktree 修复之前,先读取所有其他模式的状态文件;
- 如果发现另一个循环的
acting_on与自己的目标匹配,则跳过本次执行,并把跳过原因记入 loop-run-log.md。
落到这次事故的具体职责划分是:CI Sweeper 拥有"红色 CI"的处理权;PR Babysitter 在读到ci-sweeper-state.md显示某个 PR 已被acting_on标记时,跳过对该 PR 的修复动作。
多循环协调的五条原则
docs/multi-loop.md 把上述事故上升为通用原则。在一个仓库里跑多个循环是常态,但没有边界的多循环就是"循环互殴"。协调原则如下:
- 每条分支一个属主(One owner per branch)——最多只有一个循环在一小时内改动某条分支;
- 状态文件相互分离——
STATE.md用于 triage(优先级与人工收件箱),行动循环使用各自专属的状态文件; - Triage 只报告,行动循环才执行——Daily Triage 在 L1 阶段永不与 CI Sweeper 的修复竞争;
- 共享 denylist——把同一份路径 denylist 复制进每个
LOOP.md; - 聚合 token 预算——参见 templates/loop-budget.md.template。
推荐的仓库根目录状态布局如下(来自原文档):
STATE.md # Daily Triage(优先级、人工收件箱) pr-babysitter-state.md # PR 观察者 ci-sweeper-state.md # 活跃 CI 失败 + 尝试次数 dependency-sweeper-state.md # 进行中的依赖更新 post-merge-state.md # 合入后清理积压 loop-run-log.md # 只追加的可观测性日志循环冲突时的优先级栈
当多个循环目标重叠时,docs/multi-loop.md 定义了明确的优先级栈,冲突时由高优先级循环先执行:
| 优先级 | 循环 | 理由 |
|---|---|---|
| 1 | CI Sweeper | 红色 main 阻塞一切 |
| 2 | PR Babysitter | 活跃 PR 对时间敏感 |
| 3 | Dependency Sweeper | CI 红色时暂停 |
| 4 | Post-Merge Cleanup | 非高峰、紧急度最低 |
| 5 | Daily Triage | L1 只出报告,负责调度其他循环 |
调度协调应在根目录LOOP.md中显式文档化。原文档给出了可直接复制的示例:
## Multi-loop schedule - CI Sweeper: /loop 15m (active hours) - PR Babysitter: /loop 10m (active hours, skip if CI Sweeper acting on same PR) - Daily Triage: /loop 1d 08:00 - Dependency Sweeper: /loop 6h (skip if main CI red) - Post-Merge: /loop 1d 22:00注意 PR Babysitter 一行的括号注释"skip if CI Sweeper acting on same PR"——这就是本次事故后补上的调度契约,对应本仓库实际运行的多循环调度表见 LOOP.md 的 "Multi-loop coordination" 一节。
从状态约定到机械化锁:loop-worktree lock/unlock
纯靠"状态文件 + 人工阅读"的碰撞检测有一个致命弱点:它依赖循环的控制脚本自觉去读别人的状态文件。仓库给出的更强方案是用工具把约定机械化——tools/loop-worktree 的lock/unlock命令把docs/multi-loop.md的acting_on约定实现为一个"咨询式锁"(advisory lock):
loop-worktree lock --paths package.json,package-lock.json --owner dependency-sweeper --ttl 6h \ || exit 2 # 另一个属主持有重叠路径的锁 —— 跳过本次运行 loop-worktree create --run-id "$RUN_ID" --pattern dependency-sweeper # ... 执行修复工作 ... loop-worktree unlock --owner dependency-sweeper控制脚本在拉起 worktree之前执行loop-worktree lock,工作完成后执行loop-worktree unlock。关键设计是:loop-worktree create本身不检查锁,两者靠控制脚本中的配对约定来保持同步——这正与loop-context --check和loop-worktree mark --status escalated的配对方式一致(见 docs/multi-loop.md 与 tools/loop-worktree/README.md)。同样的约定也通过--lock-paths选项存在于 tools/loop-sandbox(一次性沙箱 agent 运行也属于可能撞车的控制脚本),但它是可选开启的——不带--lock-paths的loop-sandbox run不受锁保护。
锁的源码级原理:路径重叠、TTL、等待与死锁检测
在 tools/loop-worktree/src/lock.ts 中可以读到这个锁的完整实现,几个关键设计值得注意:
- 锁文件即状态:每个属主一个 JSON 文件,存放在
.loop-worktrees/locks/<owner>.json,字段包括owner、paths、lockedAt与可选的expiresAt(不传--ttl则永不过期,必须显式unlock,见 lock.ts)。这个文件的物理存在形式,就是acting_on约定的机械化版本——只不过它锁定的是路径 glob而非分支名,因此还能捕捉跨模式的冲突(比如 CI Sweeper 与 Dependency Sweeper 同时要改package.json)。 - 按路径段比较的重叠判断:
pathsOverlap把两个 glob 按/切分成段,逐段比较;通配段(*/**)与该位置任意内容兼容,因此src/**与src/foo.ts重叠,而docs/api与docs/apidocs.md不重叠(不同字面段,不是简单的前缀匹配)。实现注释明确说明这是"故意简单的咨询式锁,而非完整 glob 引擎"(lock.ts)。 - 跨进程互斥:
lock的"检查-再写入"临界区通过一个独占创建的 mutex 文件(.loop-worktrees/locks/.mutex)串行化,5 秒超时,避免两个同时发起的lock调用都在对方写入前通过重叠检查——这正是本功能要防的竞态本身(lock.ts)。 - 等待与死锁检测:带
--wait 15m时,锁被占用会进入等待队列并写入.wait.json;等待图一旦成环立即抛出Deadlock detected: A -> B -> A(lock.ts),不会无限挂起。 - 过期清理:
loop-worktree locks --sweep只报告过期锁,加--force才删除;孤立锁(属主崩溃未解锁)会被显式暴露而非静默忽略,与gc命令"默认只报告"的仓库惯例一致(lock.ts)。
这些行为都有测试用例背书。tools/loop-worktree/test/lock.test.mjs 覆盖了:路径段边界(docs/apivsdocs/apidocs.md不重叠)、相同属主重复加锁会替换而非叠加、过期锁不再阻塞新锁、非法--owner(含路径分隔符、可逃逸锁目录)被拒绝、并发lock竞争时恰好只有一个获胜,以及--wait排队与死锁环检测(如Deadlock detected: B -> A -> B)。
落地示例:把碰撞检测写进两个循环的状态文件
对于不引入锁工具的场景,原文档与模式文档给出了纯状态文件的做法。
CI Sweeper(patterns/ci-sweeper.md)在ci-sweeper-state.md中跟踪:commit SHA、失败 job、尝试次数、worktree/PR 链接、结果。将acting_on写入后,形如:
## CI Sweeper — Active Failures Last run: 2026-06-09 14:30 UTC ### fix/auth-token-refresh (PR #318) — acting_on - Job: test-auth - Failure: AssertionError in test_refresh_token_expiry - Attempts: 1/3 - Last action: Minimal fix proposed in worktree fix/ci-auth-refresh - Status: Waiting for verifier + humanPR Babysitter(patterns/pr-babysitter.md)在pr-babysitter-state.md中记录每个被盯守 PR 的状态,并在读到ci-sweeper-state.md中对应acting_on标记后跳过修复:
- #318 (fix/auth-token-refresh) Checks: test-auth FAILING (owned by CI Sweeper — skipping fix) Last action: no-op, logged skip to loop-run-log.md每次跳过都要写入 loop-run-log.md(只追加、按 run 一条 JSON,字段含pattern、outcome、tokens_estimate),保证"这次为什么没动手"对人工完全透明。对于无法自动判定属主的歧义情况,docs/multi-loop.md 建议在STATE.md中开一个共享的"Human Inbox":
## Human Inbox (ambiguous / cross-loop) - [ ] PR #42: CI Sweeper and PR Babysitter both flagged — human pick owner同类事故佐证:优先级锁与预检
PR #318 并非孤例。仓库里的姊妹篇事故 stories/dependency-vs-ci-sweeper-collision.md 记录了一次跨模式碰撞:CI Sweeper 正在 main 上修复回归时,Dependency Sweeper 把一次"安全"的 minor 依赖升级直接合入红色 main,引入传递性回归,CI Sweeper 在环境已经变化的情况下继续修原 bug,一小时内烧掉约 150 万 token,且两个循环同时写loop-run-log.md导致 git push 冲突。它的教训与 PR #318 互补:高优先级修复活跃时,低优先级变更循环不得执行。据此 docs/multi-loop.md 补上了优先级栈,Dependency Sweeper 现在运行预检——先读ci-sweeper-state.md确认 main CI 是否绿色,非绿则跳过并在 2 小时后重试;同时用错峰的 cron 调度避免状态文件写冲突。
两起事故合起来给出完整的防碰撞策略:状态层的acting_on声明 + 工具层的咨询式锁 + 优先级栈 + 预检(pre-flight)+ 错峰调度。
实操清单:给仓库新增一个行动循环前
结合原文档与本次复盘,部署任何新的 L2 行动循环前建议逐项核对:
- 声明分支属主:状态文件写入
acting_on: branch-or-pr-id,行动前读取所有其他模式的状态文件; - 用锁机械化约定:控制脚本在
create前loop-worktree lock --paths <globs> --owner <pattern>,完成后unlock,冲突时exit 2跳过并记入loop-run-log.md; - 遵守优先级栈:确认自己的循环在冲突时的优先级(CI Sweeper > PR Babysitter > Dependency Sweeper > Post-Merge Cleanup > Daily Triage);
- 设置预算与熔断:行动循环要配
loop-guard熔断(loop-context --check)与尝试上限,防止同一条失败反复重试烧 token(成本画像与每日上限见 patterns/ci-sweeper.md 与 patterns/pr-babysitter.md,安全细则见 docs/safety.md); - 共享 denylist 与聚合预算:把同一份路径 denylist 复制到每个
LOOP.md,token 预算按 templates/loop-budget.md.template 聚合; - 保持可观测:跳过、等待、升级都要落 loop-run-log.md,歧义冲突进
STATE.md的 Human Inbox。
参考
- 事故原始记录:stories/multi-loop-collision.md
- 协调规范:docs/multi-loop.md
- 工具实现:tools/loop-worktree(lock.ts、cli.ts、lock.test.mjs)
- 循环模式:patterns/ci-sweeper.md、patterns/pr-babysitter.md
- 同类事故:stories/dependency-vs-ci-sweeper-collision.md
- 本仓库实际调度与优先级:LOOP.md
- 人工智能
- AI Agent
- Agent 工作流
- CLI
- 研发协作
- AI 技能
- MCP 服务
【免费下载链接】loop-engineering
Practical patterns, starters & CLI tools for loop engineering with AI coding agents. Design systems that prompt and orchestrate agents (inspired by Addy Osmani and Boris Cherny). Includes loop-audit, loop-init, loop-cost.
相关推荐
PR Babysitter Loop 实战指南:用 loop-engineering 模式自动化 PR 审查、CI 修复与合并就绪判定
PR Babysitter Loop 实战指南:用 loop engineering 模式自动化 PR 审查、CI 修复与合并就绪判定 导读 :本文基于 pat
人工智能AI AgentAgent 工作流CLI研发协作AI 技能MCP 服务Codex 上的 PR Babysitter:基于 Loop Engineering 的 PR 看护与自动修复实践
Codex 上的 PR Babysitter:基于 Loop Engineering 的 PR 看护与自动修复实践 PR Babysitter(PR 看护循环)
人工智能AI AgentAgent 工作流CLI研发协作AI 技能MCP 服务loop-engineering 多循环优先级冲突实战复盘:Dependency Sweeper 与 CI Sweeper 在同一分支上互踩的根因、损失与修复方案
loop engineering 多循环优先级冲突实战复盘:Dependency Sweeper 与 CI Sweeper 在同一分支上互踩的根因、损失与修复方
人工智能AI AgentAgent 工作流CLI研发协作AI 技能MCP 服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考