get-shit-done 3156:为什么 /gsd-plan-phase 会被 OpenCode 的 agent 自动派发送进“子代理死胡同”——编排命令的上下文契约与结构性回归测试
2026/9/7 3:26:43 网站建设 项目流程

get-shit-done #3156:为什么 /gsd-plan-phase 会被 OpenCode 的 agent 自动派发送进“子代理死胡同”——编排命令的上下文契约与结构性回归测试

【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done

本篇基于 changeset 3156-plan-phase-opencode-dispatch.md 与对应源码、测试,讲解 get-shit-done 项目中一个跨运行时兼容缺陷的完整机理:agent:frontmatter 指令如何导致/gsd-plan-phase在 OpenCode 中丢失子代理派生能力,修复如何同时应用于plan-phase.mdmvp-phase.md,以及一个扫描全部命令文件 frontmatter 的结构性回归测试如何防止该类问题复发。读完你将掌握“命令文件 frontmatter 即运行时契约”的排查方法,以及如何在自己的多运行时项目中为这类上下文错配建立防线。

一、背景:/gsd-plan-phase是一个依赖 Agent 工具的编排器

/gsd-plan-phase的定位不是“自己干活”,而是“调度别人干活”。从 commands/gsd/plan-phase.md 的<objective>段落可以看到其编排角色定义:

Orchestrator role:Parse arguments, validate phase, research domain (unless skipped), spawn gsd-planner, verify with gsd-plan-checker, iterate until pass or max iterations, present results.

默认流程为Research(按需)→ Plan → Verify → Done,它需要派生三个子代理:

子代理职责
gsd-phase-researcher为阶段做技术研究,产出RESEARCH.md
gsd-planner生成可执行的PLAN.md
gsd-plan-checker计划质量审查,未通过则进入修订循环(最多 3 次迭代)

这三个角色在 agents/gsd-phase-researcher.md、agents/gsd-planner.md、agents/gsd-plan-checker.md 中分别定义;编排细节位于 get-shit-done/workflows/plan-phase.md,其中通过subagent_type="gsd-phase-researcher"subagent_type="gsd-plan-checker"等调用点显式派生子代理。

关键证据在 commands/gsd/plan-phase.md 的 frontmatterallowed-tools列表中——它显式声明了Agent工具(子代理派生器):

--- name: gsd:plan-phase description: Create detailed phase plan (PLAN.md) with verification loop argument-hint: "[phase] [--auto] [--research] [--skip-research] ..." allowed-tools: - Read - Write - Bash - Glob - Grep - Agent # 编排器依赖此工具派生子代理 - AskUserQuestion - WebFetch - mcp__context7__* requires: [discuss-phase, phase, review, update] ---

也就是说:Agent出现在allowed-tools里,说明该命令的正常工作方式必须以主代理上下文运行,否则派生动作无从谈起。

二、根因:OpenCode 对agent:frontmatter 指令的语义是“自动派发”

缺陷的触发点是一行看似无害的 frontmatter。修复前的plan-phase.md携带:

agent: gsd-planner

changeset 原文给出的根因解释是:

Per the OpenCode commands spec,agent: <name>causes the runtime to auto-dispatch the command to a named subagent context where theAgent(subagent-spawner) tool is unavailable.

即 OpenCode 的命令规范将agent: <name>解释为“把该命令自动派发(auto-dispatch)到名为<name>的子代理上下文中执行”。这与 Claude Code 的 slash command 语义并不对齐——同一条命令文件,在两种运行时中的执行上下文完全不同:

  • 在“普通”运行时中,该命令在主会话上下文中展开执行,Agent工具可用,编排流程正常;
  • 在 OpenCode 中,同一份 frontmatter 使运行时直接把它送进gsd-planner子代理上下文,而这个子代理上下文不具备Agent工具

于是出现了一个自相矛盾的处境:一个allowed-tools里声明需要Agent的命令,却被 frontmatter 指令送进了一个拿不到Agent的上下文。

三、失效模式:编排器“退化为单线程”,全量工作回落到内联执行

changeset 描述了实际观察到的失效行为:

The/gsd-plan-phaseorchestrator relies onAgentto spawngsd-phase-researcher,gsd-planner, andgsd-plan-checkersubagents; in the auto-dispatched context it fell back to doing all work inline.

从源码结构看,这一退化是必然的:get-shit-done/workflows/plan-phase.md 中的工作流按“派生 researcher → 派生 planner → 派生 checker → 修订循环”组织,每一环都是对子代理的 spawn 调用。当Agent工具不可用时,LLM 无法按工作流原样执行,只能回退为在主上下文中内联完成研究、规划、校验的全部工作。后果是:

  1. 上下文窗口被三个阶段的全部中间产物占满,原本由子代理隔离上下文的设计目的完全失效;
  2. “Research → Plan → Verify”的门控循环(含最多 3 次迭代的修订机制)被实质性绕过或简化;
  3. 缺陷只在 OpenCode 运行时上显现,在 Claude Code 上测试完全正常——典型的跨运行时行为分歧。

四、修复:移除agent:指令,让命令回到主代理上下文

修复方式极其克制——不是增加兼容性代码,而是删除一行 frontmatter

Theagent: gsd-plannerdirective has been removed fromplan-phase.mdso the command runs in the main agent context whereAgentis available.

对照当前仓库中 commands/gsd/plan-phase.md 的 frontmatter,agent:指令已不存在,allowed-tools中保留Agent,命令在 OpenCode 中也将运行于主代理上下文,派生能力恢复。

同样的修复被同步应用到 commands/gsd/mvp-phase.md。changeset 说明:

The same fix was applied tocommands/gsd/mvp-phase.md, which carried the same directive and had the identical failure mode.

mvp-phase是垂直 MVP 切片的用户入口:它先引导用户写下 “As a / I want to / So that” 用户故事、执行 SPIDR 拆分检查,然后委托给/gsd plan-phase <N>。其 frontmatter 同样在allowed-tools中声明了Agent,因此一旦携带agent:指令就会落入与plan-phase完全相同的失效模式。两个文件一并修复,消除了整条 MVP 规划链路上的同类隐患。

该修复随版本 v1.41.0 发布,见 docs/RELEASE-v1.41.0.md 中的变更记录:

/gsd-plan-phaseno longer auto-dispatches to a subagent on OpenCode — theagent: gsd-plannerfrontmatter directive caused OpenCode to run the orchestrator in a context where theAgenttool is unavailable. Directive removed.

五、防线:扫描全部命令 frontmatter 的结构性回归测试

单点修复解决的是已知的两个文件,但真正有价值的产出是那条结构性回归测试。changeset 最后一句:

A structural regression test parses the YAML frontmatter of everycommands/gsd/*.mdfile and asserts that no command carries anagent:directive.

对应实现是 tests/bug-3156-plan-phase-opencode-dispatch.test.cjs,其设计要点如下:

1. 为什么可以直接断言“frontmatter 即行为”

测试文件开头有一段注释,明确了这条测试的正当性:

// allow-test-rule: source-text-is-the-product // commands/gsd/*.md files are the deployed skill surface. Their frontmatter // IS the runtime contract. Checking frontmatter fields checks deployed behaviour.

在这个项目里,commands/gsd/*.md就是实际部署给各 AI 运行时使用的技能面(skill surface),frontmatter 字段会被 OpenCode 等运行时直接消费。因此“解析 frontmatter 并断言字段”不是文本风格检查,而是对部署行为的检查——这正是测试能以纯静态解析方式存在的原因。

2. 测试体结构:两层断言,belt-and-suspenders

测试对commands/gsd目录下每一个.md文件解析 YAML frontmatter(parseFrontmatter处理首个---分隔块,支持多行列表字段),然后执行两个describe块:

第一层(全量硬约束):任何命令文件都不得携带agent:frontmatter 指令。

describe('#3156 — no command file may have an `agent:` frontmatter directive', () => { for (const { name, content } of commandFiles) { test(`${name}: no agent: directive in frontmatter`, () => { const fm = parseFrontmatter(content); assert.ok( !Object.prototype.hasOwnProperty.call(fm, 'agent'), `${name}: has \`agent: ${fm['agent']}\` in frontmatter — this causes OpenCode to auto-dispatch to a subagent context where the Agent tool is unavailable...`, ); }); } });

断言用的是hasOwnProperty检查键的存在性,而非值匹配,避免对空值等边缘形态漏判;失败信息直接给出根因与修复指引(移除指令、回到主代理上下文)。

第二层(精确复现失效模式)allowed-tools包含Agent的命令(即编排器)绝不允许携带agent:指令。测试注释坦承这一层与第一层冗余(“belt-and-suspenders”),但它精确刻画了 #3156 的失败模式:

describe('#3156 — orchestrator commands (allowed-tools: Agent) must not have agent:', () => { const orchestrators = commandFiles.filter(({ content }) => { const fm = parseFrontmatter(content); const tools = allowedTools(fm); return tools.includes('Agent'); }); ... });

其中allowedTools辅助函数同时兼容多行 YAML 列表与逗号分隔两种写法。这一层断言的价值在于:即便未来放宽第一层的“全局禁令”,第二层仍能守住“编排器自毁”这一具体失效面。

3. 这类测试的通用启示

从测试结构可以提炼出一条可复用的经验:当一个项目的“配置即行为”(markdown frontmatter、TOML 段落、JSONC 配置直接驱动运行时),那么对这些声明面做全量结构化扫描,比针对单条命令写功能测试更廉价也更全面——每个文件自动变成一个测试用例,新增命令文件时无需修改测试代码即被纳入约束。

六、经验总结:命令 frontmatter 是跨运行时的上下文契约

综合 changeset 与源码证据,#3156 给出的规则可以归纳为:

  1. agent: <name>不是“提示 LLM 用这个 agent 风格”,而是运行时级的上下文切换指令。在 OpenCode 中它会触发自动派发,把命令送进子代理上下文,且该上下文没有子代理派生工具。
  2. 凡是allowed-tools声明了Agent的编排型命令,frontmatter 中必须省略agent:指令,否则命令会被送进一个自身无法工作的上下文。测试注释将其总结为:“Commands that need to run in the main agent context (i.e., all GSD commands) must omit this directive.”
  3. 同一 frontmatter 在不同运行时中语义可能不同,跨运行时支持的项目必须在目标运行时上实际走查一遍编排链路,不能以某一运行时的测试通过作为全量验证。
  4. 用结构性回归测试固化契约:全量扫描 frontmatter、按“部署面即契约”的原则断言,使未来任何新增命令文件自动继承该保护。

延伸阅读(仓库内相对路径)

  • changeset 原文:.changeset/3156-plan-phase-opencode-dispatch.md
  • 被修复的命令:commands/gsd/plan-phase.md、commands/gsd/mvp-phase.md
  • 编排工作流(researcher/planner/checker 调用点):get-shit-done/workflows/plan-phase.md
  • 回归测试:tests/bug-3156-plan-phase-opencode-dispatch.test.cjs
  • 发布记录:docs/RELEASE-v1.41.0.md

【免费下载链接】get-shit-doneA light-weight and powerful meta-prompting, context engineering and spec-driven development system for Claude Code by TÂCHES.项目地址: https://gitcode.com/GitHub_Trending/getshi/get-shit-done

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

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

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

立即咨询