首页
/ gstack Pacing Updates 设计解读:如何把 30–50 次审查中断压缩到可接受范围

gstack Pacing Updates 设计解读:如何把 30–50 次审查中断压缩到可接受范围

2026-09-06 09:10:20作者:滑思眉Philip

本篇解读 gstack 仓库中的设计文档 PACING_UPDATES_V0.md(状态:V1.1 计划,尚未实现),它回答了一个具体的工程问题:当一套 AI 审查流水线(/autoplan)在一次约 45 分钟的运行里向用户抛出 30–50 次 AskUserQuestion 提问时,如何通过"节奏(pacing)"改造把中断量压下来,同时不牺牲安全性(one-way door 决策必须全量呈现)。读完本文,你能理解该方案的问题建模、十个结构性缺陷、十项改造范围、可量化的验收标准,以及它如何与仓库中已有的 question-log、问题注册表、one-way 分类器等基础设施对接。

1. 问题来源:两种"疲劳",两种解法

设计文档的开篇把非技术用户阅读 gstack 审查输出时的疲劳归结为两个独立来源:

  1. 术语密度(Jargon density)——技术术语出现时没有解释。这一半由 V1 计划(ELI10 写作标准)解决,对应 PLAN_TUNING_V1.md 中的写作风格规范(术语首用注释、结果导向措辞、短句)。
  2. 中断量(Interruption volume)——/autoplan 依次运行 4 个阶段(CEO + Design + Eng + DX),每个阶段触发 5–10 次 AskUserQuestion 提问,全链路约 30–50 次,持续约 45 分钟。文档给出的关键经验值是:非技术用户在约 10–15 次中断后就开始"走神"。这一半正是本文(V1.1)的主题。

文档用一句话点明方案边界:"Translation alone doesn't fix interruption volume. A translated interruption is still an interruption."(光做翻译修不了中断量,被翻译过的问题依然是问题。)因此修复必须改变"发现何时浮出水面"(WHEN findings surface),而不仅是"措辞怎么写"(HOW they're worded)。

这与 autoplan/SKILL.md 中的真实阶段结构相互印证:Phase 0.5 预检后,Phase 1(CEO,必跑)、Phase 2(Design,仅当检测到 UI 范围)、Phase 3(Eng,必跑)、Phase 3.5(DX,仅当检测到开发者向范围),每个阶段都以 STOP 标记开头,要求先 Read 对应 sections/*-phase.md 再执行,阶段之间"Each phase MUST complete fully before the next begins"。每个阶段内部的 STOP/AskUserQuestion 序列是硬编码在技能模板里的,这正是后文"结构性缺陷 #4"的由来。

2. 为什么被从 V1 中拆出来:10 个结构性缺陷

Pacing 工作流最初是 V1 计划的一部分(在 PLAN_TUNING_V1.md 的"Revision 3"中被提出:给发现排序、自动接受 two-way door、每阶段最多 3 次提问、Silent Decisions 块、flip <id> 命令)。第三轮 Eng Review 加第二轮 Codex Review 暴露出 10 个无法靠"改计划文字"修复的缺口,文档因此被整体拆出,进入独立的 V1.1 设计轮次。这 10 个缺口可以归为五类:

(a)状态模型缺失(#1、#5)

  • Pacing 需要"每阶段状态":哪些发现已浮出、哪些被自动接受、哪些用户还能翻回。V1 只有"每次技能调用"级别的状态(用于 glossing),没有承载每阶段 pacing 记忆的后端存储。
  • "回复 flip <id> 可更改"原本只是一句散文承诺:没有命令解析器、没有状态存储、没有重放行为。一旦对话被压缩、Silent Decisions 块滑出上下文,原始决策就丢了。

(b)观测基础设施缺字段(#2)

  • 静默 Eng 审查 #8 希望在"单阶段内提问超过 3 次"时告警,但 V0 的 question-log.jsonl 没有 phase 字段;V1 声称"无需 schema 变更"与这个强制目标自相矛盾。仓库中 bin/gstack-question-log 的现行 schema 确实只有 skill / question_id / question_summary / category / door_type / options_count / user_choice / recommended / followed_recommendation / session_id / ts / source 等字段——没有 phase,证实了该缺口。

(c)注册表覆盖不了动态发现(#3)

  • scripts/question-registry.ts 的注册表覆盖的是"问题"(在技能定义时静态注册,约 30–50 个常见类别,one-way door 覆盖率要求 100%)。而审查发现(review findings)是运行时动态生成的,agent 会随手生成 {skill}-{slug} 形式的临时 id。靠注册表做 door_type: one-way 强制执行,对 ad-hoc 发现无能为力——"agent 审查中途生成的一-way 安全性无法强制执行"。

(d)散文规则无法反转既有控制流(#4)

  • V1 打算在 preamble 散文里加一条"先排序再提问"的规则。但现有技能模板(如 plan-eng-review)是按小节硬编码 STOP/AskUserQuestion 序列的;preamble 里的一句散文规则无法可靠覆盖模板中每一节的 STOP。文档的结论很明确:"The behavioral change is sequencing, not prompt wording."(行为改变在于执行顺序,不是提示词措辞。)

(e)边界中断与未校准的参数(#6、#7、#8、#9、#10)

  • 升级后的迁移提示(提供恢复 V0 散文)本身算一次中断,会挤占 V1.1 想压缩的预算;首跑的 lake intro、telemetry、proactive、routing 注入等 meta-prompt 也都发生在"第一个真实技能运行之前",同样要计入。
  • 排序公式从未用真实数据校准:V1 先后考虑过 product 0-8(被证明取值分布是 {0,1,2,4,8},公式失效)和 sum 0-6 加阈值 ≥4,但都没对照过真实发现分布。
  • "每个 one-way door 都必须浮出"与"每阶段最多 3 次"两条规则同时存在却未声明优先级,逻辑上矛盾。
  • 验证值未定义:Silent Decisions 块的 "≥ N 条" 中 N 从未给值,吞吐量 JSON 的 active: true 字段也无定义。

3. V1.1 的十项范围

文档的 Scope 部分给出 10 项具体改造,逐项对应上述缺口:

  1. 定义会话状态模型——per-skill-invocation / per-phase / per-conversation 三选一;后端存储预计为 ~/.gstack/sessions/<session_id>/pacing-state.json,记录每阶段"已浮出 vs 自动接受"的发现清单;清理 TTL 与 preamble 中既有的会话跟踪(120 分钟,见 plan-tune/SKILL.md 的 preamble 中 find ~/.gstack/sessions -mmin +120 逻辑)保持一致。
  2. question-log schema 增加 phase 字段——把每次 AskUserQuestion 归入其来源阶段(CEO / Design / Eng / DX / other);存量条目默认 "unknown",非破坏性扩展。
  3. 扩展注册表对动态发现的覆盖,两个候选方案(CEO review 时二选一):
    • (a) 拓宽 scripts/question-registry.ts 支持运行时注册(ad-hoc id 也要被记录并分类);
    • (b) 新增二级运行时分类器 scripts/finding-classifier.ts,用模式匹配把发现文本映射到风险层级。
  4. 把 pacing 从 preamble 散文移入技能模板控制流——更新每个审查技能模板,使其按显式序列执行:(i) 阶段内部先跑完,(ii) 用 gstack-pacing-rank 二进制对发现排序,(iii) 最多发出 3 个 AskUserQuestion,(iv) 其余打包为 Silent Decisions 块。这不是 preamble 规则,而是模板中的显式执行顺序。
  5. 实现 flip 机制——新二进制 bin/gstack-flip-decision:从用户消息解析 flip <id>,在 pacing-state.json 中查原始决策,重新展开为一个显式 AskUserQuestion,用户新选择持久化。
  6. 迁移提示的预算裁定——一次性迁移提示豁免于每阶段中断预算,理由:它们在审查阶段开始之前触发,而不是期间。
  7. 首跑 preamble 审计——逐个审计 lake intro / telemetry / proactive / routing 注入:"对首次用户是承重件,还是可延后?"文档预判的结果是:除 lake intro 外全部压到第 2 个会话之后,其余通过用户自愿调用的 /plan-tune first-run 提供。
  8. 排序阈值校准——V0 的 question-log 已在运行且有历史数据,先测量近期 CEO + Eng + DX + Design 审查中 severity × irreversibility × user-decision-matters 的真实分布,再定阈值。目标:约 20% 的发现浮出,约 80% 自动接受
  9. 显式规则:one-way door 不设上限——硬编码进技能模板散文:"one-way doors surface regardless of phase interruption budget";two-way 发现每阶段上限 3 个。
  10. 给出具体验证值——定义 Silent Decisions 的 N(例如"非平凡计划预期 ≥ 5 条"),并给吞吐量 JSON 定义具体字段名。

4. 与仓库现有基础设施的对接(源码佐证)

设计文档不是凭空立论,其每一项都与仓库中已存在的机制衔接。以下几处源码可以让"锦上添花"部分落到实处。

4.1 问题日志:中断量的数据底座

bin/gstack-question-log 是 append-only 的 JSONL 写入器,落盘到 $GSTACK_HOME/projects/<SLUG>/question-log.jsonl。它对每个事件做严格校验:skill 必须 kebab-case;question_summary 限 200 字符且经 hasInjection()(共享自 lib/jsonl-store.ts 的审计模式列表)做注入防御;user_choice 限 64 字符;自动计算 followed_recommendation(比较前会剥掉双方尾部的 (Recommended) 标记)。它还支持 source 字段区分写入方(agent / hook / auto-decided 等),并对 source:tool_use_id 组合做 100 行窗口去重。写入后以 fire-and-forget 方式触发 gstack-developer-profile --derive 维持推断维度最新。

V1.1 的"每阶段可观测性"(/plan-tune 能显示任意会话的每阶段 AskUserQuestion 计数)正是建立在这条数据流之上——只需补一个 phase 字段。文档 #2 指出的矛盾在于:V1 声称"无 schema 变更",而告警目标恰恰依赖该字段。

4.2 问题注册表与 door_type:one-way 安全的主闸门

scripts/question-registry.ts 中每个注册项都声明 door_type: 'one-way' | 'two-way',注释规则是:one-way 用于破坏性操作、架构/数据模型分叉、超 1 天 CC 工作量的范围扩张、安全合规选择,"ALWAYS asked regardless of user preference";two-way 可被显式用户偏好自动决策。文件头注释说明注册表是 /plan-tune 的底座:question-log 用它打标、question-preferences.json 以它为键、心理画像信号映射(scripts/psychographic-signals.ts)以 (id, user_choice) 查维度增量。

Pacing 方案的排序前提就是这套 door_type 分类:预算优先花在最难逆转的决策上(见第 6 节的 fork 合并决策),two-way 发现才允许进"自动接受"通道。

4.3 双层 one-way 防线:注册表 + 关键词兜底

scripts/one-way-doors.ts 是"第二道防线",classifyQuestion() 的判定顺序在文件头注释中写得很清楚:

  1. question_id 查注册表,命中则直接用注册表的 door_type(reason: registry);
  2. 未命中则查技能类别兜底——cso:approvalland-and-deploy:approval 组合恒为 one-way;
  3. 再对 question_summary 做破坏性关键词正则匹配(rm -rfforce pushgit reset --harddrop tableterraform destroyrollback、凭据 revoke/reset/rotate 等,reason: keyword);
  4. 无任何证据时默认 two-way。

文件头特别引用了 V1 设计文档 Decision C 的结论:"散文解析太弱,不能当主闸门——措辞会变;注册表才是主闸门,这里只是未编目问题的兜底。" 这一分层结构与 V1.1 范围 #3 中方案 (b)(运行时 finding-classifier.ts 模式匹配器)在方法论上同源:动态发现也需要"先查注册表、再用模式匹配兜底、保守倾向多问一句"的次序。

4.4 用户偏好检查:AUTO_DECIDE 通道已经存在

bin/gstack-question-preference 提供 --check <id>,输出三态:ASK_NORMALLY / AUTO_DECIDE / ASK_ONLY_ONE_WAY;偏好值只能是 always-ask / never-ask / ask-only-for-one-way,且对注册表 one-way id 拒绝写入 never-ask("always-ask 在 one-way id 上没问题,因为它与安全覆盖一致")。autoplan/SKILL.md 中每个提问前的流程是:从注册表或 {skill}-{slug}question_id,然后管道调用 gstack-question-preference --check "<id>" --summary-stdin(摘要走 stdin 喂给 one-way 关键词网);AUTO_DECIDE 意味着选推荐项并注明 "Auto-decided [summary] → [option] (your preference). Change with /plan-tune."。

这正是 V1.1 要"提速"的既有管道:现状是逐题询问偏好,V1.1 要在阶段维度上批量做"排序 + 自动接受 + Silent Decisions 块",并保证 one-way 恒问。

4.5 模板硬编码 STOP:为何散文规则失效

autoplan/SKILL.md 展示了缺口 #4 所指的具体形态:每个阶段以 **STOP.** Before starting Phase N ... Read sections/<phase>.md and execute it 的模板指令开头,且"NEVER run phases in parallel — each builds on the previous"。这意味着"先内部跑完一个阶段、再统一提问"的行为改变,必须落在各阶段模板的执行序列上(范围 #4 的第 (ii)(iii)(iv) 步),而不是加一句 preamble 散文。

5. 验收标准:可重跑、可计数的定义

文档的 Acceptance criteria 全部是可测量的:

  • 中断计数:Louise(或同等非技术协作者)在一份与 V0-baseline 相当的计划上端到端重跑 /autoplan,AskUserQuestion 次数 ≤ V0 基线的 50%(V1 负责捕获基线转录供 V1.1 校准)。
  • one-way 覆盖:100% 安全关键决策(door_type: one-way 或被分类器标记的动态发现)以完整技术细节逐条浮出,不设上限。
  • Flip 往返:用户输入 flip test-coverage-bookclub-form,原自动接受决策重新展开为 AskUserQuestion;用户新选择持久化进 Silent Decisions 块(或若翻回"显式浮出"则从块中移除)。
  • 每阶段可观测性/plan-tune 可读取 question-log.jsonl 的新 phase 字段,显示任意会话的每阶段 AskUserQuestion 计数。
  • 首跑削减:新用户第一个真实技能运行前看到的 meta-prompt ≤ 1 个(仅 lake intro),对比 V1 的 4 个(lake + telemetry + proactive + routing)。
  • 人工重跑:Louise 与 Garry 独立定性评审,模式同 V1。

6. 对 V1 的依赖与明确不碰的东西

V1.1 构建在 V1 的基础设施之上:explain_level 配置键与 preamble 回显模式(V1 的 A4 项)、术语表 + 写作风格节(V1.1 的中断措辞本身要遵守 ELI10 规则)、V0 休眠负向测试(V1.1 同样不能唤醒 5D 心理画像机器)、以及 V1 捕获的 Louise 转录(验收校准基线)。文档同时声明 V1.1 不依赖任何 V2 项(E1 substrate wiring、narrative/vibe 等)。

"NOT touched in V1.1" 一节列出的 V2 延后项包括:困惑信号检测、5D 心理画像驱动的技能自适应(V0 E1)、/plan-tune narrative + /plan-tune vibe(V0 E3)、per-skill 或 per-topic 的 explain 级别、团队画像、基于 AST 的"已交付功能"指标。

7. Fork 合并:链级(chain-scoped)预算记账

文档末尾的"Fold-in from fork port wave 2 (2026-08-14)"记录了与 time-attack/gstack fork 的取舍:该 fork 从互补轴攻击同一问题——用构建规模分类(session/hobby/project/product/venture)决定机器体量,并引入全链提问预算。2026-08-14 CEO review 批准的合并决策是:只吸收 fork 的记账判断,不吸收其数值常量。具体规则四条:

  • 预算是链作用域的:链式审查从剩余预算中扣减,绝不重置
  • 阶段交接时携带"questions-already-spent"(已花费的提问数);
  • 审批/变更门(approval/mutation gates)永不计入预算;
  • 预算优先花在最难逆转的决策上。

文档明确拒绝采纳 fork 的 5/8/12 数值常量——因为 fork 自己后来用"零默认的自主性拨盘"取代了它们。分工被表述为:"Scale sizes the machinery and sets the budget; pacing (this doc) ranks what the budget is spent on."(规模决定机器体量并设定预算;pacing 决定预算花在哪。)

8. 评审计划与文档定位

评审计划本身也体现了 gstack 的审查文化:预工作是从当前 V0 数据捕获真实 question-log 分布,作为范围 #8 的校准输入;CEO review 要求挑战前提("pacing 是对的药吗,还是干脆把 4 个阶段合并成一次统一审查?"),scope 模式预判为 SELECTIVE EXPANSION;Codex 独立过一遍,重点盯范围 #4 的控制流改动(V1 在这里翻过车);DX review 专注 flip 机制——flip <id> 是否可发现、命令语法是否自然、错误路径是否清晰;Eng review 预期多轮。

综合来看,PACING_UPDATES_V0.md 的价值不在任何单条技术点,而在于它示范了 AI 驱动的开发工具如何量化"人机中断成本":以 bin/gstack-question-log 的 JSONL 流水为数据底座,以 scripts/question-registry.ts 的 door_type 分类为安全不变式,以 scripts/one-way-doors.ts 的双层判定为兜底防线,再叠加"每阶段 ≤3、one-way 不设上限、~20% 浮出 / ~80% 自动接受"的量化目标与可重跑的验收标准。若你正在设计多阶段 LLM 审查流水线,这套"中断预算 + 门型分类 + 决策可翻转"的组合,是一个可以直接对号参考的工程范式。

登录后查看全文
热门项目推荐
相关项目推荐