背景
我们团队基于 SDD(Spec-Driven Development)模式做多仓库协同开发,用公司内部的 Agent 工具来执行工作流。工作流中有一套 Skill 机制——每个命令(比如 apply、archive)对应一份完整的执行指令文档(SKILL.md),Agent 调用 use_skill 加载后按步骤执行。
最近连续两次出现 Agent 跳步的情况,具体表现是:明明 SKILL.md 里写了完整的后续流程,Agent 执行到一半就结束了。排查后发现问题不在文档本身,而是 Agent 压根没加载那份完整文档。
问题现象
现象 1:apply 命令跳过了收尾步骤
执行 /opsx:apply 时,这个变更涉及子仓库。按照完整 Skill 流程,确认子仓库任务完成后还要走 Phase C(总参仓库收尾):发 GitLab Issue 评论、更新 Label、commit + push、创建 MR。
实际表现是 Agent 把上游 tasks 勾完就停了,后面的 Issue 评论、MR 创建一个都没做。
现象 2:archive 命令的交互方式不对
执行 /opsx:archive 时,SKILL.md 明确写着用一个「是/否」确认来决定是否 commit + push。结果 Agent 弹了个 5 选项的多选框出来,把操作拆得很碎。
两次问题的共同点:Agent 没有调用 use_skill 加载完整的 SKILL.md。
原理:Skill 的两层加载机制
要理解这个问题,先得搞清楚 Agent 工具中 Skill 的加载流程。它其实分两层:
第一层:系统消息中的精简描述(自动注入)
Agent 工具启动时,会把所有注册的 Skill 的描述信息注入到系统消息(System Prompt)中。大概长这样:
<skills>
Available skills:- "openspec-apply-change": Implement tasks from an OpenSpec change.Use when the user wants to start implementing...- "openspec-archive-change": Archive a completed change in theexperimental workflow. Use when the user wants to finalize...
</skills>
这段内容的作用是意图识别——告诉 Agent "有这些 Skill 可用,遇到匹配的用户意图时去调用"。每个 Skill 的描述大概 20~40 行,包含基本的步骤概要和输出格式。
第二层:SKILL.md 完整指令(主动加载)
当 Agent 识别到用户意图后,应该调用 use_skill("openspec-archive-change"),这会读取并返回对应的 SKILL.md 文件——通常有 300~500 行,包含:
- 每个步骤的详细执行逻辑和条件分支
- 多仓库场景的级联处理流程
- 交互方式的精确定义(弹几个选项、怎么措辞)
- 各种异常场景的处理方式
- 6 种以上的输出模板
问题出在哪
正常流程应该是:
用户输入 /opsx:archive→ Agent 看到系统消息里的精简描述,识别意图→ Agent 调用 use_skill("openspec-archive-change")→ 拿到 SKILL.md 完整内容(430+ 行)→ 按完整指令逐步执行
实际发生的是:
用户输入 /opsx:archive→ Agent 看到系统消息里的精简描述(~30 行)→ 觉得"这里已经有步骤了,够用了"→ 直接按精简版执行 ← 没调 use_skill→ 精简版里没有的步骤全部跳过
精简版 vs 完整版的内容差异
拿 archive 命令举例,精简版和完整版的覆盖范围差距很大:
| 内容 | 精简版(~30 行) | 完整版 SKILL.md(430+ 行) |
|---|---|---|
| 基础归档流程 Step 1-5 | ✅ 有 | ✅ 有 |
| downstream 级联归档 Step 6a-6e | ❌ 无 | ✅ 有 |
| 提交推送确认 Step 7(是/否) | ❌ 无 | ✅ 有 |
| Issue Label 更新 Step 7.5 | ❌ 无 | ✅ 有 |
| 6 种场景的输出模板 | ❌ 只有 2 种 | ✅ 全部 |
| Guardrails 约束条件 | ❌ 无 | ✅ 有 |
Agent 拿着精简版执行,自然就把 Step 6 以后的内容全丢了。
为什么 Agent 不主动加载完整版
本质上是 LLM 的行为特性——当上下文中已有"看起来足够"的信息时,它不会主动去获取更多信息。具体来说:
- 上下文里有了就不找了——system prompt 里已经有一段步骤指令,从 token 效率角度 LLM 倾向于直接用,不会额外花一次 tool call 去"确认是否有更完整的版本"
- 精简版的"诱导性"——它包含步骤编号、输出模板这些结构化元素,长得太像正式指令了
- 缺少显式阻断——精简版里没写"这不是完整指令,你必须调 use_skill",LLM 没有被明确告知需要再做一步
这不是某个特定 Agent 工具的 bug,而是所有基于 LLM 的 Agent 平台都可能遇到的问题。只要架构里存在"系统消息注入摘要 + 运行时加载详情"这种两层设计,就有这个风险:
| 平台/工具 | 两层机制 |
|---|---|
| 公司内部 Agent 工具 | <skills> 注入描述 + use_skill 加载完整版 |
| Cursor | .cursorrules 注入上下文 + tool call 取详细指令 |
| Claude (MCP) | System prompt 摘要 + MCP tool 取完整文档 |
| GitHub Copilot | Instructions 文件 + 扩展命令加载 |
解决方案
方案 1:在 always-apply 规则中强制要求加载
我们的 Agent 工具支持 alwaysApply: true 的规则文件——每次会话启动时自动注入系统消息。在这里写一条硬性约束:
## ⛔ Skill 加载强制规则(MUST 级别)以下命令在执行时,必须调用 use_skill 加载完整的 SKILL.md 指令,
不得依赖系统消息中的内联精简版:
- /opsx:apply → use_skill("openspec-apply-change")
- /opsx:archive → use_skill("openspec-archive-change")> 曾多次发生 Agent 按内联精简版执行、跳过关键步骤的事故。
> 精简版仅用于意图识别,不包含完整执行流程。
因为这条规则每次会话都会出现在系统消息中,Agent 在执行这几个命令时会被强制提醒"你得先调 use_skill"。相当于在它工作台上贴了张便利贴。
方案 2:在 SKILL.md 分支点内联后续步骤清单
还有一种情况:Agent 确实加载了完整 SKILL.md,但 400 多行的文档它未必每行都看。执行到某个条件分支时,如果跳转目标写得太简略,它可能直接忽略后续内容。
解决办法是在分支点把后续步骤"剧透"出来:
// 修改前
If all done: suggest archive// 修改后
If all done AND .openspec.yaml contains `downstream`:MUST immediately proceed to Phase C — 包含:C1(子仓库验证)→ C1.5(Issue 评论 + Label)→C2(勾选上游 tasks)→ C3(commit + push + MR)
把子步骤名称直接写在跳转处,这样即使 Agent 没翻到 Phase C 的详细段落,也能知道"后面还有 4 件事要做"。
修复验证
加上这两个修复后重新跑了一轮完整流程(apply → archive),Agent 行为符合预期:
- ✅ 执行 apply 时调用了
use_skill,Phase C 完整执行(Issue 评论、Label 更新、MR 创建) - ✅ 执行 archive 时调用了
use_skill,用单个是/否确认而非多选框 - ✅ 后续多次执行均未复现跳步问题
小结
这个问题的根因可以一句话概括:Agent 拿到了一份残缺的指令,却当作完整版来用。
底层原因是 LLM 的行为特性——上下文里有了就不再主动获取更多。这不是 bug,改不了,只能在工程层面绕过去。几个实操要点:
- 关键命令必须在 always-apply 规则里写死
use_skill的调用要求 - 长流程的分支跳转点要把后续步骤名称列出来,别给 Agent 留"不知道后面还有活儿"的空间
- 每次出现跳步行为就补一条规则——规则文件是跨会话持久化的,对话历史不是
以上是最近踩坑的记录,主要想说明一个点:用 Agent 跑复杂工作流时,Skill 的加载机制值得多花点心思设计。指令写得再完整,Agent 不加载也白搭。
