从两份设计文档看 Agent 方法论框架 superpowers 怎样重做工作区与修复回路

2026-07-29

本文基于 superpowers 仓库 commit 44c9b2d(2026-07-27)梳理,该项目仍在持续迭代,具体行为以仓库 https://github.com/obra/superpowers 最新代码与文档为准。

这两份文档里真正值得你抄走的,不是「一个计划一个目录」这个结论,而是它们把「评审不过怎么办」当成一个会自己失控的循环来治理——先让磁盘上的状态可辨认,再给循环加上限和收敛条件。 结论是一句话,过程改了两版,中间还有一版评测结果跟预期完全相反、作者照实写进了文档。这个过程比结论有用。

superpowers 是 Jesse Vincent 的开源项目(MIT 许可证,https://github.com/obra/superpowers ),它把一套开发方法论写成给编码 Agent 读的技能文件。本文只盯着其中一个技能 subagent-driven-development——由一个控制者 Agent 逐任务派发实现者子代理、每个任务派一次评审、最后再做一次全分支评审的执行模式。

站内已经有两篇讲通用方法论的文章:Agent 工作区隔离讲的是为什么要给 Agent 划一块可丢弃的地盘,Agent 中间产物落盘讲的是为什么中间结果要走文件而不是走对话。本篇不重复那两套道理,只看一个真实项目怎么把它们落到具体的脚本签名、目录结构和文本约定上,以及落地过程中撞到了什么。

一、第一版改的是磁盘:让状态能自己说清「我是谁的」

第一份文档是 docs/superpowers/plans/2026-07-06-sdd-plan-scoped-workspace.md,一份可执行的实施计划。

改动前,这个技能的所有临时产物——任务简报、实现者报告、评审包、进度台账——都堆在仓库根下的一个固定位置 .superpowers/sdd/,台账固定叫 progress.md。同一个工作树里跑完第一个计划再跑第二个,第二个计划的控制者会在同一个路径上看到一份写满「complete(review clean)」的台账。

计划文档里记录的真实后果不是抽象的:某个工作树累积了跨三个计划的文件,其中一个控制者只好自己发明 progress-p2.mdp2-task-N-report.md 这类旁路命名来躲开前一个计划的台账,还留下一个废弃的 progress-p3.md 残桩;简报在共享的默认路径上被静默覆盖;产物混进了 git,需要两次清理提交,至今仍有三个产物被跟踪在主分支上。

改法是把工作区按计划切分。三个脚本一起改签名:

  • sdd-workspace PLAN_FILE 成为工作区位置的唯一事实来源,打印 <repo-root>/.superpowers/sdd/<plan-basename>,并顺手在 .superpowers/sdd/ 下写一个内容为 *.gitignore
  • task-brief PLAN_FILE N [OUTFILE] 默认写到 <workspace>/task-<N>-brief.md
  • review-package PLAN_FILE BASE HEAD [OUTFILE] 默认写到 <workspace>/review-<base7>..<head7>.diff

台账则被要求带一行身份:# SDD ledger — plan: <plan file path>。技能文本里的规则相应变成:首行指向别的计划文件的台账,或者残留在老扁平路径 .superpowers/sdd/progress.md 的台账,都属于别人的进度,原地留着,自己另起一份。计划跑完、最终评审干净之后,删掉这份计划自己的工作区目录,兄弟目录不动。

有一个决定值得单独看。sdd-workspace 脚本的注释里写明了工作区为什么放在工作树里而不是 .git/ 下:Claude Code 把 .git/ 当受保护路径,拒绝 Agent 写入,实现者子代理会写不了自己的报告文件。所以它退而用一个自我屏蔽的 .gitignore 来换取「不进 git status、不被误提交」,代价是 git clean -fdx 会连工作区一起清掉。

二、评测结果跟预设相反,文档照实写了

这份计划带了一轮 RED→GREEN 压力测试,结果记录在 docs/superpowers/specs/2026-07-06-sdd-plan-scoped-workspace-eval-results.md。原本假设的失败模式是「控制者盲目采信陈旧台账,跳过没做的任务」。三轮基线、25 次复现,一次都没出现:所有控制者都拒绝把台账当成跳过工作的许可。

那真实的代价是什么?文档给的答案是一笔取证税。在最接近真实崩溃恢复的那一轮里,每个复现都要先花掉一串工具调用去证明「这份台账不是我的」——单次 7、13、9、10、6 次调用。因为老文本没给它别的判断依据,它只能去把台账引用的提交拉出来,比对内容属于哪份计划。

改完之后再测,工具调用次数是 9、11、9、7、12,均值反而比基线高。文档没有掩饰这一点,专门写了一段「Read this table honestly」:原始调用数没降,变的是这些调用花在哪——新文本下的控制者靠结构判断(解析自己计划的工作区、看台账首行身份),剩下的调用花在确认自己这份计划确实还没动过工,那是任何全新开始的控制者本来就要做的事。文档明确写了它这一轮只主张两件事:回归安全(合法的同计划续跑仍然能续上),以及机制上的改变;调用数的下降不在主张范围内。

这一段是整份文档里最值得学的写法。一个改动做了、评测没给出漂亮数字、作者把「这个场景没能证明什么」写进了结论。

三、第二版改的是循环:谁来修、修几轮、复评看什么

第二份文档是 docs/superpowers/specs/2026-07-15-sdd-fix-loop-redesign-design.md,一份已批准的设计规格。它开头列了四个「都在真实会话里观察到」的问题,头两个是回路本身的:

没有轮次上限。 原文本的循环就是字面意义上的「Repeat until approved」。

每轮复评都是对整个 diff 的全新完整评审。 一个有随机性的前沿模型每轮都会翻出新东西,于是循环变成「实现、评审、修、评审、评审、修、评审、修」,没有任何断路装置。文档把这个称作 churn engine——搅动的发动机,而不是收敛的机制。

第三个问题是「谁来修」在一个技能里有三个答案:流程图和「构造评审提示」章节派专门的修复子代理,Red Flags 说同一个实现者自己修,implementer-prompt.md 的 After Review Findings 段落又假设实现者会被重新唤起。

设计给出的答案很明确:由原来的实现者修自己的评审发现,就地唤起它。理由写在决策表里——它已经握着任务上下文,所有权胜过路过打补丁的人;新开的修复子代理要为每条发现重建上下文,而且没有任务框架。

回路的形状是这样的:

第 1 到 3 轮,唤起原实现者,把 Critical/Important 发现和规格缺口原样发给它。如果你的运行环境没法给一个活着的子代理再发消息,那么「唤起」就是带着简报路径、报告文件路径和发现列表重新派发一个——报告文件在两种情况下都是持久记忆。

第 4 到 5 轮,换一个更强的模型全新派发,带上完整任务上下文和一句框定:先前有个实现者试过 N 次,现在这个任务归你了,读报告文件看看试过什么。设计文档给的理由是:一个能扛过三次唤起的循环,通常意味着实现者看不见自己的问题,换新脑子加提升能力一步到位。

每一轮的复评都是定向的。review-package PLAN_FILE FIX_BASE HEAD 生成修复范围的 diff,FIX_BASE 是上一次评审看到的那个 head。复评走一个单独的模板 skills/subagent-driven-development/re-review-prompt.md,它的契约跟完整评审完全不同:逐条给发现下判定(ADDRESSED / NOT ADDRESSED,且「尝试过」不算已解决,具体缺陷必须不再存在),只在修复 diff 里找新破坏,修复没碰过的代码上发现的问题写进 Out-of-Scope Observations,非阻塞,由控制者记进台账留给最终评审。设计文档解释了为什么要单开一个模板文件:因为这是另一份契约,硬塞进完整评审模板正是现在这种含混的来源。

不许提前退出。 控制者在到达上限之前不得裁决,因为提前退出会重新打开「为了给自己省一轮评审而预判发现」这个已经被堵上的口子。唯一的例外沿用旧规则:一条与计划文本明文要求冲突的发现,立刻交给人判断谁说了算。

到顶之后才裁决,分三个走向:评审方错了或有争议,搁置并写一行裁决理由,最终评审能看到双方说法;确实是真问题但没有下游依赖,同样搁置并注明真实且延后;确实是真问题且承重(后续任务建立在它之上,或者它暴露了计划本身的缺陷),走既有的 BLOCKED 停机上报。文档给的理由是:把结构性失败搁置,等于让所有依赖它的任务继续往上盖,最后把一个最终评审也修不动的问题丢给最贵的环节。

每一次裁决都是一条台账记录,静默丢弃始终禁止。

四、拆开看:这套东西由哪些件构成

组成部分它负责什么对应仓库位置你什么时候会碰到它
sdd-workspace解析并创建这份计划专属的工作区目录,维护自我屏蔽的 .gitignoreskills/subagent-driven-development/scripts/sdd-workspace技能启动的第一件事
task-brief把计划里某个任务的完整文本抽成一个文件,实现者一次 Read 读完skills/subagent-driven-development/scripts/task-brief每次派发实现者之前
review-package生成提交列表、改动统计和带上下文的净 diff,写成一个文件skills/subagent-driven-development/scripts/review-package每次派发评审前、以及每轮修复之后
进度台账 progress.md记完成行、修复轮次行、搁置裁决行、BLOCKED 行<workspace>/progress.md(工作区内,git 忽略)上下文压缩之后重新定位
定向复评模板逐条发现判定、修复 diff 内的新破坏、范围外观察skills/subagent-driven-development/re-review-prompt.md修复回路的每一轮
实现者模板的 After Review Findings 段告诉实现者它会被带着发现唤起,修完追加修复报告skills/subagent-driven-development/implementer-prompt.md第 1 到 3 轮唤起时
SKILL.md 的 The Task Loop五个步骤把上面这些串起来:派发、处理报告、评审、修复回路、完成任务skills/subagent-driven-development/SKILL.md全程

台账行的格式在实施计划的 Global Constraints 里是逐字规定的,因为评测脚本要 grep 它们,比如修复轮次行是 Task <N>: fix round <R>/5 (<X> addressed, <Y> open — <finding one-liner>; commits <a7>..<b7>),搁置行是 Task <N>: parked — <finding one-liner> — ruling: <one-liner>。恢复规则也定死了:一个任务算 DONE,当且仅当它有一行 Task <N>: complete

五、边界与代价:它明确不管什么

它不改脚本层的能力。 修复回路那份设计的 Non-Goals 写得很直白:task-briefreview-package 已经够用,这次不动脚本。定向复评能成立,靠的是 review-package 本来就接受任意提交区间。

它不做台账的会话级切分。 那件事归另一个 PR,设计文档只标注了「这次改动碰同一批章节,实施计划要注明冲突风险」。

工作区是可丢弃的临时物,不是记录。 它被 git 忽略,git clean -fdx 会毁掉它,恢复手段只有 git log。计划跑完还要主动删掉——文档的说法是「git 历史现在就是记录了」。如果你想要一份跨计划、可回溯的执行档案,这套设计不提供,你得自己另存。

老扁平路径的台账不迁移。 规则是「留在原地,自己另起一份」。仓库里已有的历史污染也不是它清理的对象。

工作区的名字取自计划文件的 basename 去掉 .md 脚本里就是 basename "$plan" .md。两个放在不同目录、文件名相同的计划,会落到同一个工作区目录里。

评测只覆盖了很窄的一段。 那份结果文档自己的 Limitations 写了:每格 5 次是冒烟强度的信号不是统计强度;场景测的只是「恢复时的判断」,不是一次完整执行;工具调用次数是个粗糙的成本代理。

这套流程会让开发变慢,也会让 Agent 更啰嗦。 一个任务最坏情况要跑 5 轮修复加 5 次定向复评的派发,每轮都要生成 diff 文件、写台账行。这个代价在仓库里是被记下来的:修复回路那份实施计划的 Global Constraints 把「实跑评测」单独标成受信任的维护者操作,理由是它需要额外的环境变量和 API 凭证、而且要花真钱,所以连作者自己都没法密集地跑它。对于改一行文案、调一个常量这种任务,简报文件、评审包、台账行、断路器全套是明显的过度设计——直接改完跑测试更快。这套东西的适用面是「有一份写好的多任务实施计划、任务之间大致独立、你不打算全程盯着」,不在这个描述里的场景,用它是在为不存在的风险付费。

它假设你的运行环境有 git、有 bash、有能派发子代理的宿主。 脚本全程用 git rev-parse --show-toplevel 定位仓库根,技能文本里也给了「没有 bash 时」的手工替代路径,但整套设计是围着一个能开子代理的控制者转的。

六、上手清单:会踩的坑和踩的原因

照旧签名调用会直接报错。 review-package 现在第一个参数是计划文件,你从旧笔记或旧文章里抄来的调用有两种死法:只给 BASE 和 HEAD 两个参数,脚本的参数个数检查(下限是三个)直接打印用法并以退出码 2 退出;给了 BASE、HEAD、输出文件三个参数,脚本会把 BASE 当成计划文件路径去做文件存在性检查,同样以退出码 2 失败。好在两种都是硬失败,不会静默写错地方。避法:任何调用之前先跑 sdd-workspace PLAN_FILE 拿到目录,把计划文件路径当成这三个脚本的固定第一参数记住。

BASE 用 HEAD~1 会静默丢东西。 一个任务如果产生了多个提交,HEAD~1..HEAD 只覆盖最后一个,评审看到的是残缺的改动,而且不会有任何报错。避法:在派发实现者之前就 git rev-parse HEAD 把 BASE 记下来,技能文本里把这件事列在派发实现者的第一条。

手写台账忘了首行身份,等于自己制造一份孤儿台账。 恢复规则是按首行是否指向自己的计划文件来判断归属,没有首行的台账会被当成别人的进度原地留着,你的进度就丢了。避法:创建台账时第一行永远写 # SDD ledger — plan: <plan file path>,别等到写第一条完成行时再补。

把工作区往 .git/ 里塞会让实现者写不了报告。 这是个很自然的直觉——反正是临时文件,藏进 .git/ 就不用管 gitignore 了。脚本注释里记了真实原因:Claude Code 把 .git/ 视为受保护路径,拒绝 Agent 写入。避法:留在工作树里,靠 .superpowers/sdd/.gitignore 里那个 * 屏蔽。

git clean -fdx 会连着台账一起清。 工作区是 git 忽略的临时物,清理命令不会区分它和别的垃圾。避法:跑清理之前确认没有进行中的计划;真清掉了,只能从 git log 重建进度。

提前裁决会让整套断路器失效。 循环跑到第二轮,控制者很容易觉得「这条发现明显不对,我判它无效省一轮」——设计文档把这个行为定性为换了名字的预判。避法:记住裁决只发生在第五轮之后,唯一的提前出口是「发现与计划文本冲突」,而那个出口通向人,不通向自己。

复评方越界会把定向复评变回全量评审。 如果你自己拼复评提示词而不用那个模板,很容易漏掉「不要复审修复没碰过的代码」这条,于是每轮又开始冒新发现,回到搅动状态。避法:用 re-review-prompt.md 这份模板,它的 Scope 段和 Out-of-Scope Observations 段就是为这件事写的。

修复报告不完整就派复评,等于让复评方替实现者跑测试。 技能里有一道保留下来的闸门:派复评之前确认修复报告写明了覆盖测试、跑的命令、以及输出。少了任何一项,复评方要么去重跑整套测试(贵),要么只能信一句空话。避法:把这三项当成硬性收货条件,缺就打回。

收束

把这两份文档连起来看,改动顺序其实是有讲究的:先让磁盘上的状态可辨认(工作区按计划切分、台账带身份),再谈循环怎么收敛(唤起原实现者、定向复评、五轮断路、到顶裁决)。顺序反过来做不成——如果控制者连「这份台账是不是我的」都要靠取证判断,那给循环加轮次上限也没有可靠的计数依据。这跟站内讲的无限循环与停机条件失败重试策略是同一类问题的两个层次:状态可辨认在下,循环可终止在上。

你要动手核对的话,读这个顺序最省力:先 skills/subagent-driven-development/scripts/sdd-workspace(三十来行,注释比代码长,把设计理由都写在里面了),再 skills/subagent-driven-development/SKILL.md 的 Setup 和 The Task Loop 两节,最后才是两份设计文档——先看落地形态再看推理过程,比反过来快得多。

三条自检,可以拿去量你自己那套 Agent 执行流程:第一,一个 Agent 在上下文被压缩之后重新读磁盘,能不能只靠结构判断出「哪些状态是我的」,而不需要去比对提交内容;第二,你的评审到修复的循环有没有一个明确的上限,以及到顶之后由谁做决定、决定写在哪;第三,复评的时候,评审方被明确告知了「不要看什么」吗——没有这一条,每一轮复评都会长出新的一轮。

本文属于 superpowers 方法论专题(共 30 篇,含三篇与其它开源 Agent 项目的对照)。想看把资产铺满的另一种取向,见 ECC 开源 Agent 套件专题

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。