gstack AskUserQuestion 分链规则(Split Rule)详解:5 个以上选项时如何不静默裁剪用户决策空间
2026/9/7 3:19:53 网站建设 项目流程

gstack AskUserQuestion 分链规则(Split Rule)详解:5 个以上选项时如何不静默裁剪用户决策空间

【免费下载链接】gstackUse Garry Tan's exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack

本文围绕 gstack 仓库中的 AskUserQuestion 分链规则完整参考文档 展开,讲清一条被注入到每个 tier-2+ 技能指令中的核心约束:当一次决策存在 4 个以上真实选项时,Agent 如何合规地分批提问、逐选项追问、处理 Hold 与依赖冲突,并通过question_id命名约定和运行时检查器保证分链中的每次询问都真实到达用户。读完后你能掌握:如何判断该用 batched 还是 split 两种形态、分链的完整编号与调用时序(D<N>.k/D<N>.final/D<N>.revise-<k>)、question_id的长度与冲突处理规则,以及 gstack 用「机制 + 运行时强制」两层防御阻止 AUTO_DECIDE 绕过用户决策的实现证据。

规则要防止的 bug:Agent 单方面裁剪选项

Conductor 等宿主对 AskUserQuestion 有 4 个选项的上限。规则诞生前的真实故障模式来自一条用户投诉的原始 transcript(原文 逐字引用):

"I'm hitting Conductor's limit of 4 options in the AUQ, so I need to cut one. E4 (the detect-mappings codegen) is the biggest lift and probably beyond scope for v0.42 anyway — users can hand-author their mapping rules for the 9 clusters. I'll drop that and keep E1, E2, E3, and E5..."

"Conductor caps at 4 options. Trimming: E4 (detect-mappings codegen) is the largest-effort item and a natural v0.43+ follow-up — moving it to TODOS.md without asking. Re-firing with 4."

问题在于:Agent 在没有任何用户输入的情况下,单方面删除了一个真实选项。选项集合是用户的决策空间,静默缩小它就是 bug。这条规则的全部语义——编号、四桶、Hold、final 校验、运行时强制——都是围绕「不得 drop、不得 merge、不得静默 defer」这一条红线展开的。

这条规则在仓库中的落地分两层:

  1. 常载内联摘要:由 scripts/resolvers/preamble/generate-ask-user-format.ts 生成,其中### Handling 5+ options — split, never drop小节(约 L75–L102)被注入到每个 tier-2+ 技能的SKILL.mdpreamble 中。这一小节刻意压缩,因为它会随每个技能常驻上下文;它末尾明确指向本参考文档:「Full rule + worked examples + Hold/dependency semantics: seedocs/askuserquestion-split.mdin the gstack repo. Read on demand when N>4.」
  2. 深度参考:即本文所依据的 docs/askuserquestion-split.md。当出现 N>4 的选项、需要完整示例或 Hold / 依赖 / final-summary 语义时按需加载。

同一 resolver 的### Self-check before emitting检查清单(约 L111–L126)中也有三条与分链直接相关的自检项:5+ 选项是否已 split/batched 而非 drop、是否在开链前检查了选项间依赖、是否在某项 Hold 后立即停链。

选形:batched 还是 split

文档给出两种合规形态(compliant shapes),选择依据是「读选项的性质」:

形态一:Batched into ≤4-groups(分组到一次调用内)

适用于选项是连贯的互斥替代项、最终只会选中一个的场景,例如:

  • 版本号 bump 的major / minor / patch / micro
  • 5 个布局变体中用户选一个;
  • 「选哪个框架:rspec / minitest / cucumber / none」。

做法:把前 4 个选项打包进一次 AskUserQuestion;若 4 个都不合适,第 5 个再作为后续提问浮出。这是适用时更低摩擦的路径。

形态二:Split per-option(逐项串行提问)

适用于选项是相互独立的范围项(scope items),每个选项各自携带 include/defer/cut 决策,例如:

  • "E1..E6,哪些要 ship?"
  • "Q3 的 5 个候选集成"
  • "审计浮出的 8 条 TODO,哪些要落地?"

做法:发起 N 次串行 AskUserQuestion,每个选项一次。

拿不准时默认选 split per-option。文档强调:把不相干的范围项硬塞进同一次提问(shoehorning orthogonal scope items into one question),和直接 drop 一个选项是同一种失败模式——都是擅自压缩了用户的决策空间。

分链的完整机制

开链之前:依赖检查

先检查选项之间的依赖关系。若 E3 依赖 E1,或 E5 与 E2 冲突,必须在对应选项的 per-option ELI10 里显式浮出,例如:

"Cutting this orphans E3 — they're linked."

不做依赖浮出的后果是:链跑完后产出一个不自洽的选中集合(比如 E3 选 Include、其依赖的 E1 却选了 Cut),最终交付一个无法构建的范围。

D-numbering 编号体系

分链沿用 gstack 决策简报(decision brief)的D<N>编号:

编号含义
D<N>父决策,N 为全局问题计数器
D<N>.k(k=1..K)父决策下第 k 个选项的 per-option 提问
D<N>.final链条结束后的最终汇总校验
D<N>.revise-<k>仅重问第 k 个选项的定点修订

5 个选项、父决策为 D3 时的链形:

D3.1 → D3.2 → D3.3 → D3.4 → D3.5 → D3.final

每次 per-option 调用的形状

对每个选项 Eₖ 发起一次 AskUserQuestion,包含:

  • D<N>.k头(如 D3.1, D3.2 ... D3.5);
  • 仅针对该选项 scope、成本及其携带依赖的 ELI10;
  • Recommendation:Include / Defer / Cut,附具体理由;
  • 每个选项 4 个桶(bucket):
    • A) Include纳入本范围(可标注是否推荐)
    • B) Defer推迟到后续(TODOs / 下个版本)
    • C) Cut完全砍掉
    • D) Hold— 停止链条,先讨论再决定
  • 注释:选项之间是 kind 不同、不是 coverage 不同——因此不写 completeness 分数。因为 Include/Defer/Cut/Hold 是决策动作,按既有格式规则应省略Completeness: N/10,改用 kind-note:Note: options differ in kind, not coverage — no completeness score

这一 kind-note 约定在 内联摘要源码 中同样被固化(per-option call shape 一行明确写了 "kind-note (no completeness score — Include/Defer/Cut/Hold are decision actions)"),保证常载摘要与深度参考两处表述一致。

Hold 的语义:是「停」,不是「排队」

用户在任一 per-option 调用上选了 Hold,链条必须立即停止。错误做法:把 E4、E5 排在 E3 的 Hold 后面,之后用过期上下文继续发问。正确做法:停下来,让用户先讨论被 Hold 的选项、重新设定父决策,然后从断点恢复(用户说 "continue" 或点名下一个要问的选项)。

Final summary:三步收尾

链条无 Hold 地跑完后,发起D<N>.final确认并校验组装出的集合:

Step 1 — 校验依赖。若选中集合不自洽(例如 E3 选了 Include 但它的必需依赖 E1 被 Cut),不得静默接受,而是把冲突重新发成一次 AskUserQuestion:

"E3 needs E1 but you cut E1. Revise: A) keep E1 B) cut E3 too C) leave as-is and accept the broken state"

Step 2 — 确认组装集合。若集合自洽,请用户确认 ship:

"Here's the assembled set: E1, E2, E5. Ship this scope? A) Ship this scope (recommended) B) Revise one option (you pick which) C) Cut more"

Step 3 — 定点修订。用户选 B 时,先问要修订哪个选项,然后在D<N>.revise-<k>上只发一次 per-option AskUserQuestion 更新该选项。不要重跑整条链——这是 split 形态相比 batch 的另一层摩擦成本,换来的是每个选项的独立决策权。

Sizing rules:按选项数量选路径

  • N ≤ 4:用普通单次 AskUserQuestion 形式,不要 split;

  • N = 5 或 6:split(或存在干净分组时 batch);

  • N > 6:开链之前,先在D<N>.0上发一次元提问(meta-AskUserQuestion):

    "About to ask N per-option questions. Options: A) Proceed with the full split (recommended only if every option is independent) B) Narrow scope first — I'll propose a smaller set C) Batch into groups of 4 instead"

    文档特别注明:这本身也是一次 AskUserQuestion 工具调用而非 prose,它算作链中的第一个 prompt,不违反「must be tool_use, not prose」的规则。

question_id 规则:分链的唯一标识与校验

每个 per-option AskUserQuestion 发出形如<skill>-split-<option-slug>的唯一question_id,其中<option-slug>是该选项 key 的 kebab-case 形式(全小写、连字符、纯 ASCII)。文档给出的示例:

  • plan-ceo-review-split-e4-detect-mappings
  • ship-split-rspec
  • plan-eng-review-split-add-coverage-test

冲突处理:若两个选项会产出相同 slug,用-2-3等后缀消歧。

长度上限:总长必须 ≤64 字符,校验发生在写入偏好时——bin/gstack-question-preference 的--write子命令对question_id执行/^[a-z0-9-]+$/正则与 64 长度检查(源码),非法 id 直接报invalid question_id退出。超长时截断 option slug,但必须保留<skill>-split-前缀,否则下一条要讲的运行时 carve-out 就无法匹配。

AUTO_DECIDE 的防御:两层机制,而不是靠 id 唯一性

gstack 的/plan-tune机制允许用户对某个question_id设置never-ask(下次直接按推荐项 AUTO_DECIDE)。这对分链是危险的,文档用两层防御化解:

第 1 层 — 机制(mechanism)。每个 per-optionquestion_id对其选项唯一,因此在一个选项 id 上设置的偏好不会泄漏到链的其他选项:ship-split-rspec上的never-ask不会顺带批准ship-split-minitest

第 2 层 — 运行时强制(runtime enforcement)。bin/gstack-question-preference 的--check子命令对 id 执行/-split-/正则匹配(即文档所称的 canonical slug pattern):只要命中,无论该 id 是否存有never-askask-only-for-one-way偏好,一律输出ASK_NORMALLY,并在存在被覆盖的偏好时附加说明性 NOTE:

"split-chain per-option calls always ASK_NORMALLY; your never-ask preference does not apply to options inside a sequential split."

一个值得注意的实现细节:源码匹配的是 kebab 形式的-split-而非裸词 "split"。test/gstack-question-preference.test.ts 中专有测试验证了这一点——形如qa-splitscreen-test的非分链 id(恰好含 "split" 词素)不会被误伤,而plan-ceo-review-split-e4-detect-mappingsnever-ask时仍被强制ASK_NORMALLY且输出上述 NOTE,配always-ask时不输出 NOTE(偏好与强制方向一致)。

结论:分链的 per-option 调用永远不进入 AUTO_DECIDE 通道。这是运行时契约,不只是「靠 id 唯一性抗碰撞」。文档的原话点题:用户的选项集合是神圣的——恢复用户对决策空间的主权,才是 split 的全部意义。

与 per-skill 规则的关系

这条 split 规则覆盖任何 per-skill 的「batch decisions」指引。而那些本来就要求 one-issue-per-call 的 per-skill 模板(例如plan-eng-review)天然兼容——它们只是本规则更严格的特例。也就是说,当某个技能模板与 split 规则冲突时,以 split 规则为准。

完整实战示例:5 个平台集成决策

文档给出的 worked example 正是回归测试 test/skill-e2e-plan-ceo-split-overflow.test.ts 使用的 fixture:一个 plan 含 5 个相互独立的聊天平台候选(「独立」是刻意设计的,排除依赖干扰):

  • E1) Slack DM bot(~2 周,~40% 的 asks)
  • E2) Discord guild bot(~3 周,~15%)
  • E3) Microsoft Teams(~4 周,~5%)
  • E4) Telegram(~1 周,~8%)
  • E5) Mattermost(~2 周,~3%)

用户明确要求对每个候选做独立决策、而不是打包一次选完。合规 Agent 的行为序列:

  1. 识别这是「5 选项独立 scope 决策」→ split;
  2. 检查依赖(本例无——每个平台独立);
  3. D3.1D3.5,每个平台一次,带 Include/Defer/Cut/Hold 四桶和基于工作量+需求数据的推荐;
  4. 链后发D3.final汇总组装范围,例如 "Ship E1 + E4 — Slack and Telegram pull most demand for least build cost. Defer the rest. A) Ship / B) Revise / C) Cut more"。

修复前的失败形态(即 bug):Agent 构造一次 AskUserQuestion 把 E1..E4 塞成四个选项,用一句 prose("E5 is the smallest revenue segment, moving to TODOs")把 E5 扔掉。用户从未对 E5 有过决策机会。

测试如何钉住这条规则

该 E2E 测试(periodic 层,真实 PTY 跑/plan-ceo-review)的断言逻辑在 测试源码 中清晰可见:fixture 提供 5 个独立选项,FLOOR = N - 1 = 4——即 review 阶段至少要有 4 次 AskUserQuestion 浮出(容忍带来自既有 finding-count 测试的标准口径,容纳链前一次预期的 scope 收窄调用);若reviewCount < FLOOR则判定「这就是 drop-to-fit-4-options 回归」,错误信息里直接点名 内联规则源头。

fixture 内容定义在 test/fixtures/forcing-finding-seeds.ts 的FORCING_SPLIT_OVERFLOW_CEO中:5 个候选各带构建成本与需求占比,并显式声明 "each fully independent ... no dependencies between them" 和 "I want individual decisions per candidate, not a bundled pick"——前者让 split(而非 batch)成为唯一自然形态,后者排除了「打包成一个问题」的合规逃生路径。测试的注释还说明了它为何独立于finding-count系列:后者验证的是「一个问题一次调用」,而本测试构造的是「一个决策内有 5 个选项」,正好命中 Conductor 4 选项上限并触发 split-vs-drop 指引。

小结

  • 4 选项上限下,5+ 真实选项只有两条合规路径:batched into ≤4-groups(连贯替代项)或 split per-option(独立 scope 项),拿不准就 split;
  • 分链由 D-numbering 体系(D<N>.kD<N>.finalD<N>.revise-<k>)组织,Hold 立即停链,final 阶段做依赖校验 + 集合确认 + 定点修订三步;
  • question_id采用<skill>-split-<option-slug>(kebab ASCII、≤64 字符、-2/-3消歧),写入侧由 bin/gstack-question-preference 的--write校验;
  • 检查侧对-split-id 强制ASK_NORMALLY,使分链永远不进入 AUTO_DECIDE——「用户的选项集合是神圣的」是一条运行时契约,配套有 单元测试 和 E2E 回归测试 双重钉住。

【免费下载链接】gstackUse Garry Tan's exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA项目地址: https://gitcode.com/GitHub_Trending/gs/gstack

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询