Loop 工程实战:在 Mango 里落地 builder + checker 循环
上一篇《Loop 工程:从 prompter 到 loop 设计师》把方法论翻译了一遍,读完最大的感受是——道理都懂,但不落到一个真实项目里跑一遍,永远不知道哪里会硌手
这篇是落地记录:我在自己的 Mango(一个 Electron + TypeScript 的卡片笔记工具)里,把 Loop Engineering 搭成了一套 builder + checker 的自动循环,配好防打断的权限和防黑盒的日志 hook,再用隔离沙箱跑了两轮端到端验证;内容全部来自实际操作与验证,不是纯理论
01 Loop 工程要解决什么
方法论来源是 Datawhale / 筱可的《全网爆火的 Loop Engineering,保姆教程来了!》
一句话概括:把写代码和查代码拆成两个 Agent,让编排器循环调度,查到全绿为止;不用人逐条检查,系统自动跑完所有检查,有问题反馈给写代码的 Agent,修完再查,直到全绿
1.1 传统单次交付的痛点
传统方式是单次的——写需求,Agent 生成代码,肉眼看一遍收工;但肉眼看容易漏:代码能跑 ≠ 测试全过,测试过 ≠ 类型没错
更常见的是 Agent 写的代码有问题,贴回去让它改,改完又引入新问题,来回几轮都不确定到底修好没有
1.2 把验证交给系统
Loop 工程的转变,是把验证从"你的工作"变成"系统的工作":
- 一次性交付:验证是你的工作,你是质检员
- 循环交付:验证是系统的工作,你从质检员变回了需求方
02 核心原则:执行与验证分离
最硬的一条原则是:写代码的 Agent 会高估自己的答案——写完再问它"行不行",答案大概率是"行";所以必须把执行和验证拆到两个角色:
- builder:只写代码、只修代码
- checker:只查代码,从工具层面无法修改任何文件
关键不是靠提示词约束 checker "别改代码",而是工具可见性的硬隔离——checker 的 tools 字段里根本没有 Write / Edit,它想改也改不动
03 Mango 的落地架构
整条循环长这样,你只需要抛一个任务,剩下的交给编排器:
你: /build-loop <任务>
│
▼
┌─────────────────────────────────────────────────────────┐
│ 编排器 /build-loop(.claude/commands/build-loop.md) │
│ 循环,最多 5 轮: │
│ 1. 调 builder ← 任务(首轮)/ checker 的原始失败报告(后续) │
│ 2. 调 checker → 跑 pnpm check │
│ 3. 全绿? → 停,汇报 diff;否则把【完整失败报告】原样喂回 builder │
│ 4. 命中停止规则? → 刹车,附升级报告 │
└───────────────┬─────────────────────────┬─────────────────┘
│ │
▼ ▼
builder checker
tools: Read/Write/Edit/Bash/Grep/Glob tools: Read/Grep/Glob/Bash ← 无 Write/Edit
只写/改代码 只跑 pnpm check,原样回报
3.1 与原文有意的差异
原文是通用教程,落到 Mango 我做了几处适配:
- 编排器命名
/build-loop,避开 Claude Code 内置的/loopskill - 模型直接用当前 Claude,不接 StepFun step-3.7-flash——现阶段循环频率不大,够用
- builder 提示词加了 Mango 特有约束:改 IPC 要同步
shared/types+ handler + 调用三处;不推翻tech-spec.md§8 锁定的架构决策
3.2 文件放置:单一事实源
遵守 claude-config 的单一事实源规范——真实文件放在配置仓库里,项目侧只放软链:
| 资产 | 真实文件 | 项目使用位置 |
|---|---|---|
| builder agent | claude-config/mango/agents/builder.md | .claude/agents/(目录软链) |
| checker agent | claude-config/mango/agents/checker.md | 同上 |
| build-loop 编排器 | claude-config/mango/commands/build-loop.md | .claude/commands/(目录软链) |
| 日志 hook 脚本 | claude-config/mango/hooks/loop-log.py | .claude/hooks/(目录软链) |
| 停止规则 | 写入 CLAUDE.md(项目文档,不软链) | — |
04 四个组件
4.1 builder agent
frontmatter 关键:tools: Read, Write, Edit, Bash, Grep, Glob
职责要点:
- 输入首轮是任务,后续轮是 checker 的完整失败报告(含行号 / 堆栈)
- 先定位根因再动手;最小改动修复本轮失败项,不顺手重构、不碰无关代码
- 遵守项目约定(中文注释、TS 分号 + 双引号 + 空格缩进);改 IPC 同步三处
- 尊重
tech-spec.md§8 锁定决策,不为绕过检查而推翻 - 改完简述改了什么、为什么,不自称通过(通过与否由 checker 判定)
- 禁止:改测试迁就实现、加
@ts-ignore/eslint-disable骗过检查、git commit/push
4.2 checker agent
frontmatter 关键:tools: Read, Grep, Glob, Bash——没有 Write / Edit,这是整套设计的地基
职责要点:
- 跑
pnpm check - 全绿 →
RESULT: PASSED+ 各步骤一行摘要;失败 →RESULT: FAILED+ 原样粘贴完整输出(命令、退出码、文件:行号、堆栈、原始报错) - 保留完整输出,绝不只贴最后一行、绝不总结、绝不替 builder 猜根因
- 禁止:修改任何文件、放宽检查、跳过用例、解读 / 过滤 / 总结失败信息
4.3 build-loop 编排器
驱动 "builder → checker → 判定 → 反馈" 循环,最多 5 轮,铁律有三条:
- 是调度器不是审查员:不替 checker 判定通过,不替 builder 写代码
- 转发失败报告逐字原样——"帮忙总结"实际在制造信息损耗,行号 / 堆栈 / 中间输出正是 builder 定位根因所需的东西
- 循环结束(无论全绿还是刹车)都不自动
git commit/push
4.4 停止规则
停止规则写在 CLAUDE.md 里,所有 Agent 可见;编排器每轮开始前检查,命中任一条即刹车并输出升级报告(当前轮次 / 失败项 / builder 已尝试的方法 / 失败原因判断):
- 轮次上限:已达 5 轮仍未全绿
- 无进展:连续 2 轮同一失败项且行号 / 报错未变,builder 在原地打转
- 反复横跳:修好 A 引入 B,下一轮修好 B 又打破 A
- 改动越界:触及任务外文件,或推翻
tech-spec.md§8 锁定决策 - 破坏性 / 绕过:删测试、加
@ts-ignore/eslint-disable、放宽pnpm check骗过验证 - 需要人决策:涉及产品取舍、外部依赖选型、或改动面 > 10 个文件
05 配套配置:权限与日志 hook
为让 loop 不被打断、又不黑盒,在 .claude/settings.local.json 里做了两件事(合并追加,未动原有配置)
5.1 权限 allowlist 防打断
只 allow loop 明确需要的命令,不用 Bash(*) 全开:
- checker 验证链:
pnpm check/typecheck/test/lint/build及pnpm run/npm run回退形式,加底层tsc --noEmit、eslint、vitest run、vite build、node --test/--check - builder 写代码:
Edit、Write
5.2 SubagentStop hook 防黑盒
为什么选 SubagentStop:它的 payload 直接带 agent_type(哪个 agent)和 last_assistant_message(干完的总结),是"builder / checker 执行完"最贴合的时机,比 PostToolUse + Task 更干净
JSON 结构:
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/loop-log.py\" 2>/dev/null || true",
"timeout": 10
}
]
}
]
}
}
脚本 loop-log.py 的行为:只记 builder / checker 两类 subagent(其它忽略)→ 写到项目内 .mango/loop.log(已 gitignore,不入库)→ 同时向 UI 回显一行摘要 → 字段名多重兜底、空输入不崩、绝不阻断循环
日志行格式示例:
[2026-07-04 00:58:50] agent=checker session=verify99 :: RESULT: PASSED — 5/5 全绿
06 怎么用
/build-loop <任务描述>
M1 阶段的典型任务:
/build-loop 实现卡片 ID 生成 newCardID(d) 与 timeFromID(id),含 Vitest 单测
/build-loop 实现 [[链接]] 解析 parseLinks(srcId, content),覆盖 id/标题/带锚文本三种形态
/build-loop 实现向量 encodeVector/decodeVector + cosineSimilarity,单测对拍 SiYuan 数值
/build-loop 实现 IPC card.get/create/update,同步 shared/types 与主进程 handler
流程很简单:你输入任务 → 看它循环到 pnpm check 全绿 → 审一眼 diff 决定要不要提交
pnpm check 的全绿标准是四步串行,任一非零即整体失败:
"check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build"
// typecheck: tsc --noEmit lint: eslint . --max-warnings=0
// test: vitest run build: vite build
顺序有讲究:快的、根因最清晰的先跑(类型错误行号最精确),最重的 build 最后;&& 串行让 checker 拿到第一个失败点
07 验证记录
用隔离沙箱(/tmp,测完即删,不碰真实仓库)跑了两轮端到端验证
7.1 第一轮:cosineSimilarity(RAG 核心函数)
- 场景:故意埋 bug——只算点积、没除模长、没做长度 / 零向量校验
- 轮次:checker 判 FAILED(原样贴完整输出)→ builder 只改
cosine.mjs一个文件、逐条对着失败项修 → checker 复验 PASSED(5/5)→ 编排器停止 - 结论:闭环走通,职责隔离生效,信息无损传递让 builder 拿到行号一轮修好
7.2 第二轮:parseTags(标签提取,更严苛)
这一轮特意加了难度:2 个 bug(未去重 + 空标签),外加一段"引诱 builder 顺手重构"的诱饵函数 isWordChar
不只信 agent 自述,逐项独立核对:
- checker 两轮都完整贴 diff / 行号 / 堆栈,零总结,判定准确
- builder 一轮修好两个 bug
- 诱饵函数 md5 完全不变(
0da4dbf9...)——builder 没上钩,守住了"最小改动"铁律 - checker 复验 PASSED 5/5,循环收敛
- checker 主动指出沙箱 check 缺 lint / build 步骤,诚实不放水
- SubagentStop hook 在循环中未触发——这个坑见下节
08 踩坑与经验
8.1 信息无损传递:原文三个坑
原文点出的三个坑,本质是同一件事:信息在 Agent 之间传递时会被"好心"地压缩,而循环效率恰恰依赖信息无损传递
- builder 爱顺手改别的:读完一圈代码有冲动把看到的问题一起改,引入 deadcode / 类型错误多花一轮 → 提示词写死"最小改动、不碰无关代码",停止规则 3、4 条兜底(第二轮的诱饵函数专门测了这条)
- checker 报告太简会拖垮循环:只贴最后一行、不带上下文,builder 找不到根因瞎猜 → 提示词写死"保留完整输出、原样粘贴"
- 编排器爱自作主张总结:拿到失败报告先自己理解再转述,行号 / 堆栈全丢 → 提示词写死"逐字原样转发,不解读不过滤"
8.2 hook 需重载才生效
现象:两轮验证共 6 次 subagent 执行,.mango/loop.log 一个都没生成
定位:手动跑 loop-log.py 证明脚本本身完全正常,能写日志、能回显;真正原因是——SubagentStop hook 是会话中途才写进 settings 的,配置监听器只监听会话启动时已有 settings 文件的目录,中途新增的 hook 不会自动加载
解决:打开一次 /hooks 菜单(或重启 Claude Code)重载配置,此后 hook 才会在真实 /build-loop 里触发
经验:新增 hook 后务必 /hooks 或重启验证一次;不能只靠 pipe-test 脚本,那只证明脚本对,不证明 hook 被 harness 调用
8.3 验证方法论
- 不只信 agent 自述:agent 的总结描述的是"意图",不是"实际";用 md5 核对诱饵函数、用独立命令复验,才是可信验证
- 用沙箱隔离:验证 loop 一定在
/tmp沙箱做,用真实可跑的pnpm check(本机没 pnpm 就用 shim 转发到 npm + node 内置--test/--check,零依赖);测完整个目录删掉,绝不污染真实仓库 - 埋诱饵:想验"builder 守不守规矩",就在待修文件里放一段明显可优化但与失败无关的代码,事后核对它有没有被动过
09 前置依赖与落地顺序
关键前置:必须有可跑的 pnpm check——/build-loop 依赖它,而 Mango 目前还没 package.json(处于设计阶段);所以真正跑起来的前提是 M1 起脚手架时先把 pnpm check(tsc + eslint + vitest + build)搭起来,哪怕先只有一两个占位单测;没有它,checker 无从验证,loop 跑不起来
建议的落地顺序:
/hooks或重启,让 SubagentStop hook 生效- M1 脚手架:建
package.json+pnpm check四步 + Electron/Vite 骨架 - 用一个真实小任务(如
newCardID+ 单测)跑一轮/build-loop,确认权限不打断、hook 日志落地、循环收敛 - 之后每个功能都用
/build-loop <功能>推进,checker 全绿才算该功能完成
一句话收尾:Loop 工程的核心价值不在于让 Agent 变聪明,而在于让验证变成内置环节;搭好 builder(只写)+ checker(只查、工具硬隔离)+ 编排器(循环 + 逐字转发)+ 停止规则,再配好防打断的权限和防黑盒的日志 hook,你就从质检员变回了需求方
参考资料
方法论来源:《全网爆火的 Loop Engineering,保姆教程来了!》(Datawhale / 筱可)
理论篇:本站《Loop 工程:从 prompter 到 loop 设计师》
