跳到正文
coograph 17

Retro

指令文件靠堆积生长。每一条规则都是某人看着代理做错事之后加上去的,而没人衡量这条规则之后有没有用。Retro 把这个循环闭合。它记录护栏何时被打破,衡量每次会话花了多少 token,然后以 OpenSpec 的形式提出对指令、hook 和技能的具体修改,由你逐行批准或驳回。

什么都不会离开这台机器。什么都不会自动应用。

它记录什么

一个 Claude Code hook 解析 Claude Code 本来就保存在磁盘上的会话记录,并把元数据追加到 .coograph/signals.jsonl(被 git 忽略)。它在 SessionEnd 时运行,每次会话压缩时也运行。第二个触发点才让长会话变得可读:跑一整天的会话只会抵达一次结束,而且很晚,被强杀就根本没有;压缩是它唯一会反复触发的事件。重复采集是安全的,因为一次会话的记录是整体替换而不是追加。warn-scope.pyblock-generated.py 这两个 hook 每次触发时也追加一条记录。

每条违规都带着它发生的那一刻,取自引发它的那条消息,而不是采集运行的时刻。

每条记录包含工具名、计数、规则 id、仓库相对文件路径、shell 命令的哈希和程序名、时间戳和 token 用量。它永远不包含提示文本、助手文本、工具输出、文件内容或完整命令。允许列表写在代码里强制执行,还有一个哨兵测试:在一份会话记录的每个部分种下标记字符串,只要标记进了信号文件就失败。

检测器

检测器规则置信度触发条件
graph-firstgraph-first确定性在本次会话第一次访问代码图之前(mcp__code-graph__* 工具,或同时包含 sqlite3.code-graph 的 shell 命令)出现了 GrepGlob 调用,且 .code-graph/graph.db 存在。会记录之后是否碰到了代码图(mcp-latersqlite-later)还是从未碰到(total-bypass)。子代理的调用不计。
openspec-gateopenspec-gate启发式编辑了两个以上源文件,会话中没有任何操作触碰 openspec/changes,且不存在活跃的变更目录
scope-warningscope确定性warn-scope.py 打印了它的警告
generated-file-blockgenerated-files确定性block-generated.py 拦截了一次编辑
build-retry确定性同一条 shell 命令跑了三次以上,其中至少两次失败
user-correctionuser-correction启发式一条用户消息在工具调用之后匹配了某个纠正模式(只记录模式 id)。围绕同一主题反复纠正,是「某条指令缺失或含糊」最清楚的证据,所以它们归到自己的规则下聚类
defectdefect确定性会话时间窗内的一次修复提交,落在了过去 14 天内被非修复提交改过的文件上。每对「修复提交 + 来源提交」出一条信号,只带文件列表和两个短哈希,别的什么都不带。项目根是仓库就扫根目录,否则扫它下面一层的各个仓库,因此根目录下放着 app/admin/ 的项目也覆盖得到
new-dependencyno-new-deps确定性npm install <pkg>pip install <pkg>uv addcargo addgo get 之类,或对依赖清单的编辑。安装清单里已有的东西不算:pip install -r requirements.txtpip install -e .、不带参数的 npm install
session确定性总是记录:消息数、用过的工具、编辑的文件数、调用的技能、代码图是否存在、开始与结束时间、按会话汇总的 token 用量,以及会话记录文件大小

启发式信号永远不会是一处改动的唯一证据。

各工具支持情况

只有 Claude Code 暴露会话记录和生命周期 hook。其他工具能拿到分析器和技能,但没有捕获。

工具会话记录检测器hook 触发的信号会话开始行变更结束提示
Claude Code / Cowork 插件全部scope、generated-files
Codex CLI无(仅 Bash 审计日志)
OpenCode无(仅 Bash 审计日志)
Cursor、Devin Desktop、Aider、Cline

你怎么知道

你不需要记住任何步骤。每一次 Claude Code 会话开始时都会打印一行,位置就在代码图状态已经出现的地方:

[retro] 12 sessions captured, 3 rules over threshold (graph-first 11x), run /coograph-retro

没有任何规则超过阈值时,它会这么说。Retro 没启用、但项目已经归档了十个以上 OpenSpec 变更时,它会说 run /coograph-retro to bootstrap

coograph-archivecoograph-apply 也会在一次变更结束时问一次:Run /coograph-retro now? (n sessions since last retro)。默认是否。代理永远不会未经提示就跑 retro。

第一天,不是第二十天

Claude Code 把每一份历史会话记录保存在 ~/.claude/projects/<slug>/ 下,其中 <slug> 是项目的绝对路径,把 A-Z a-z 0-9 之外的每个字符替换成 -(例如 C--paul-code-app-home-paul-app)。初始化时会问你要不要读取它们,所以第一份报告里立刻就有真实的会话:

python3 .claude/hooks/capture-signals.py --backfill ~/.claude/projects/<slug> --cwd .

从没启用过 Retro、但已经有至少十个归档变更的项目,可以直接运行 /coograph-retro 引导启动。技能会初始化注册表,询问会话记录目录,回填,然后像正常运行一样继续。

报告

python3 .github/retro/retro.py --report

写出 .coograph/retro/report.md。它以一段大白话开头,然后是表格:每条规则对照阈值并附“升级到”一列,路径聚集,构建重试,每次会话的 token(以最近一次规则改动为界做前后拆分),工作流遵循度(同时跑了评审或验证技能的编辑会话),指令文件相对 token 预算的大小,以及归档统计。确定性的 Python,只用标准库,不涉及模型。

提案

/coograph-retro 读取报告,写出 openspec/changes/<date>-retro-<n>/,包含提案、规格、任务和现成的补丁。五种改动类型:

每一处改动都以三句大白话开头:发生了什么、为什么重要、改什么;后面是一个证据块,数字只来自报告。你批准后,用 /coograph-apply 像其他变更一样应用。

循环的规则

Episode

阈值计的是 episode,不是会话。一个 episode 就是「某个会话在某个自然日」。

单靠会话 id 撑不起阈值。习惯开长会话的人,好几天才产生一个 id,于是「三次会话」这种门槛无论规则被破坏多少次都够不着;而频繁重启的人,靠噪声就能越过它。按「会话 × 日」计数让两种工作方式可比,也意味着在一个长会话里、分三天各破坏一次的规则真的会达到阈值——而这正是过去按会话计数会掩盖掉的情形。报告里两个数字都给出来,所以三次短会话看起来依然和一次长会话不一样。

阈值

全部在 .github/retro/rules.json 里,可按项目调整。名字里带 session 的那几项,计的是 episode。

阈值默认值含义
deterministic_events / deterministic_sessions3 / 2确定性规则在这么多个 episode 里出现这么多次事件即超过阈值
heuristic_events / heuristic_sessions5 / 3启发式规则成为仅供佐证的证据
prune_sessions10非硬性文字规则在这么多个 episode 里零事件即为删除候选
defect_lookback_days14缺陷探测器往回找「修复所落在的那次改动」的天数
instruction_token_budget8000超过后,每新增一条规则必须配对一次删除
retro_prompt_min_sessions3上次 retro 之后累积多少个 episode,工作流才会提议再做一次
bootstrap_min_archives10从没启用过 Retro 的项目引导启动所需的归档变更数

它不是什么

Retro 不是模型意义上的递归自我改进。没有权重会改变,没有模型自己训练自己。它是指令层的版本:系统观察自身的失败并对自身的脚手架提出修改,由人守在闸门前。规则能否在多轮之后持续改进还没有被证明;第一次 retro 通常找到大部分价值。完整的检测器参考、注册表格式和测试在仓库的 .github/retro/README.md