生成时间:2026-05-07
目标:在不改 sandbox 镜像、不动 某企业级 Agent 框架 存量 system prompt 的前提下,把自研 SDD 流程(自研 SDD 流程 / 自研 SDD 流程)稳定塞进 OpenCode,让它在每个项目里都能可复现地盖过默认 openspec-* 流程。
适用读者:希望在某 sandbox 平台 内做 agent 行为定制的开发者
综合来源:3 份前置探索文档(配置体系探索之旅 / Sandbox 配置全解 v4 / 需求澄清流程控制实验)


0. TL;DR

自研 SDD 被默认 openspec-* 盖掉,根因不是权限不够,是 LLM API 协议层的字段归属之争。要稳定不被盖掉,得把自研流程的优先级声明放到 API 顶层 system 字段里,再加上多层兜底。
最小可行方案,按优先级从上到下:

优先级 改动 协议层位置 投入 跨项目生效 抗 sandbox 重建
P0 编辑 ~/.config/opencode/AGENTS.md 顶部加「流程优先级声明」 system 顶层字段 5 分钟 ❌(需 P3 兜底)
P1 自研 SDD 所有子 skill 的 description 加防干扰锚 tools 顶层字段 30 分钟 ❌(需 P3 兜底)
P2 注册 /自研 SDD 流程-* slash command tools + 项目 command 目录 30 分钟 ❌(需 P3 兜底)
P3 通过 sandbox dotfiles / init script 持久化 P0-P2 同上 + 启动脚本 需协调 sandbox 团队

为什么必须动全局 AGENTS.md 而不是项目级:全局 AGENTS.md 进到 API 顶层 system 字段,永远在请求最前面,不会被对话推走;项目级 AGENTS.md 是 opencode 在你 read 项目目录时塞到 tool_result 里的一段文本,模型把它当成 user 说的话看,位置会随后续 turn 沉到中间。

1. 系统原理:从 Transformer 到 OpenCode 的四层归因

要解释清楚"为什么自研流程会被盖掉、为什么必须改全局 AGENTS.md",得先把"对话结构"拆到协议层看清楚。

1.1 四层模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─────────────────────────────────────────────────────────────┐
│ 层 4: AGENT 框架层 (opencode) │
│ ├─ <system-reminder> 文本标签包装 │
│ ├─ 「目录访问触发」的 reminder 注入 │
│ ├─ 「hook 条件触发」的 reminder 注入 │
│ ├─ skills 索引拼装、memory-lake 检索注入 │
│ └─ ★ opencode 在协议允许范围内自由发挥 │
├─────────────────────────────────────────────────────────────┤
│ 层 3: API 协议层 (Anthropic Messages API) │
│ ├─ 顶层 system 字段(独立,不在 messages 里) │
│ ├─ 顶层 tools 字段(独立,不在 messages 里) │
│ ├─ messages 数组:role ∈ {user, assistant} 仅两种 │
│ ├─ tool_result 必须是 user role 的 content block │
│ └─ ★ 所有用 Claude API 的 agent 框架都得遵守 │
├─────────────────────────────────────────────────────────────┤
│ 层 2: 训练约定层 (Chat Template / RLHF) │
│ ├─ Anthropic 训练 Claude 时教它「system 是最高指令」 │
│ ├─ 用特殊 token 标记 role 边界 │
│ └─ ★ 模型「知道」system 优先,是被训练出来的,不是架构天然 │
├─────────────────────────────────────────────────────────────┤
│ 层 1: 模型架构层 (Transformer) │
│ ├─ 只认 token 序列 │
│ ├─ ★ 不知道什么是 role / system / user / assistant
│ └─ 经 attention 加权预测下一个 token │
└─────────────────────────────────────────────────────────────┘

层 1 的模型本身完全不知道对话结构。层 2 通过训练让模型学会了「看到 system 字段要重视」。层 3 把 role 区分提升到接口级别,systemtools 是顶层独立字段,不在 messages 数组里,根本不是 turn。层 4 的 <system-reminder> 标签其实不是协议特殊概念,只是 opencode 在 tool_result 字符串里加的普通文本,模型靠训练数据里出现过类似模式才学会重视它。

1.2 一次真实 API 请求长什么样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
POST https://内部 API 网关/api/anthropic/v1/messages
{
"model": "claude-opus-4-7",
"system": [
{ "type": "text", "text":
"<agent-identity>... Sisyphus prompt ...
<Role>... <Behavior_Instructions>...
Instructions from: ~/.config/opencode/AGENTS.md
[全局 32KB 内容] ★★★★★ 永远在最前
<available_skills>... <memory-lake>... <env>..." }
],
"tools": [
{ "name": "openspec-explore", ... }, ← ★ 9 个 openspec-*
{ "name": "openspec-new-change", ... }, 永远在前,不被推走
{ "name": "自研 SDD 流程", ... } ← ★ 你的 自研 SDD 流程 在这里平级
],
"messages": [
{ "role": "user", "content": "评估当前需求..." },
{ "role": "assistant", "content": [
{"type": "tool_use", "name": "read", "input": {...}}
]},
{ "role": "user", "content": [
{"type": "tool_result", "content":
"<file-content>...</file-content>
<system-reminder> ← ★ opencode 自加的标签
Instructions from: 示例项目/AGENTS.md 协议不知道
[项目 6.2KB 内容] 模型当 user 说的话
</system-reminder>"
}
]},
...更多 turn 累积...
]
}

1.3 顶层 tools 字段 vs messages 里的 tool_result:到底是什么关系?

这两个字段名都带「tool」,但完全不是一回事。一个是工具说明书,一个是工具执行结果。

维度 顶层 tools 字段 messages[].content 里的 tool_result block
在 API 协议中的位置 顶层独立字段,和 system / messages 平级 messages 数组里某个 user message 的 content block
内容是什么 工具的「说明书」:name、description、input_schema 工具实际执行后的「输出」:read 读到的文件内容、bash 跑完的 stdout
谁写的 agent 框架(opencode)拼装好后传给模型 agent 框架执行完工具,把返回结果包成 user role 的 message 发回去
在请求中出现几次 每次请求都完整发,永远在请求最前 每次工具调用后产生一条,跟着 turn 累积
模型怎么用 「这次对话能用哪些工具、参数怎么传」 「上一步 tool_use 的实际输出是什么,下一步该干什么」
注意力位置 顶部 ★★★★★,永不被推走 跟随 turn 沉到中间塌陷区
例子 {"name": "read", "input_schema": {...}} {"type": "tool_result", "content": "<文件内容>..."}

举一次完整循环看就清楚了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
请求 #1 (顶层 tools 里写了 read 工具的 schema)

模型回 tool_use: {"name": "read", "input": {"filePath": "示例项目/AGENTS.md"}}

opencode 执行 read,拿到文件内容

opencode 把结果包成 user message 发回去:
{ "role": "user", "content": [
{ "type": "tool_result", "tool_use_id": "...",
"content": "<file-content>...</file-content>
<system-reminder>...项目 AGENTS.md 全文...</system-reminder>"
}
]}

请求 #2 (tools 字段还是那个 schema, messages 里多了一条 tool_result)

注意 opencode 干了一件协议没规定的事:在 tool_result 字符串末尾追加了 <system-reminder>AGENTS.md 全文。这段东西在协议层就是普通字符串,模型把它读成「user 说的话」。
回到本方案的核心矛盾:

  • 默认 openspec-* 流程的描述放在 顶层 tools 字段的 description 里(永不衰减)
  • 项目级 AGENTS.md 的「禁用 openspec」声明放在 messages 里某个 tool_result block 里(沉降)
    字段不同,胜负就定了。

1.4 关键事实清单

事实 协议层证据
system 是顶层独立字段 不在 messages 数组里,根本不是 turn
messages 只有 user / assistant 两种 role Anthropic 协议规定,没有 role: system
tool_resultuser role 的 content block 协议规定
<system-reminder> 在协议层不是特殊概念 只是 tool_result 字符串里的普通文本
tools schema 在哪 顶层独立 tools 字段,不在 messages 里

2. Context Window 的物理布局与注意力分布

2.1 物理布局图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
┌────────────────────────────────────────────────────────────────┐
│ ▲ TOP(最强注意力 - Primacy) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ [1] System Prompt(顶层独立字段,永不被推走) │ ★★★★★ │
│ │ ├─ Sisyphus Role / Behavior / Constraints │ │
│ │ └─ Instructions from: │ │
│ │ ~/.config/opencode/AGENTS.md (32KB) ★★★★★ │ │
│ │ §两阶段流程 / §openspec-explore / ... │ │
│ ├──────────────────────────────────────────────────┤ │
│ │ [2] <available_skills> 索引(顶层 tools 字段) │ ★★★★ │
│ │ openspec-* / 自研 SDD 流程-* / 其他 skill 平级注册 │ │
│ ├──────────────────────────────────────────────────┤ │
│ │ [3] <memory-lake> top-10 检索结果 │ ★★★ │
│ ├──────────────────────────────────────────────────┤ │
│ │ [4] <env> 启动环境变量快照 │ ★★★★ │
│ ├──────────────────────────────────────────────────┤ │
│ │ [5] 工具 schema (200+ tools) │ ★★ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ░░░░░ 中间塌陷区(Lost in the Middle)░░░░░ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ [6] 历史消息(每个 turn 累积) │ ★★ │
│ │ turn 1: user → assistant + tool_use │ │
│ │ turn 2: tool_result + assistant ... │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ tool_result(首次 read 示例项目 │ │ │
│ │ │ 时追加 <system-reminder> + 6.2KB) │ ★★★ │ │
│ │ │ ★ 项目级 AGENTS.md 沉在这里 │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ [7] 动态 system-reminder(按事件触发) │ ★★★★★ │
│ │ [TODO CONTINUATION] / [CONTEXT WINDOW MONITOR]│ │
│ ├──────────────────────────────────────────────────┤ │
│ │ [8] 当前 user message │ ★★★★★ │
│ └──────────────────────────────────────────────────┘ │
│ ▼ BOTTOM(最强注意力 - Recency) │
└────────────────────────────────────────────────────────────────┘

2.2 U 型注意力曲线

LLM 对开头(Primacy)和结尾(Recency)记忆最强,中间塌陷,详见 Lost in the Middle (Liu et al. 2023)

1
2
3
4
5
6
7
8
9
10
11
12
注意力强度
★★★★★ ┤██ ██
│██ ██
★★★★ ┤██ ██
│████ ████
★★★ ┤██████ ██████
★★ ┤██████████░░░░░░░░░░░░░░░░░░░░░░██████████
★ ┤██████████░░░░ 中间塌陷区 ░░░░██████████
└──────────────────────────────────────────────────→
顶部 system prompt 中间地带 末尾 user msg
全局 AGENTS.md 项目 AGENTS 动态 reminder
(永不被推走) (沉降) + 当前提问

2.3 两类 system-reminder:触发式 vs 事件式,到底差在哪

这是本文档最容易混淆的两个概念。它们都叫 system-reminder,注入位置都在某个 turn 的末尾,但触发机制和频率完全不同。
核心区别一句话:触发式由「agent 干了某个动作」激活,事件式由「环境状态满足某个条件」激活。

维度 触发式注入 事件式注入
触发条件 agent 调用了某个会触及目录文件的工具(read / grep / bash 等) hook 监控的某个状态满足阈值(token 数、todo 状态、调用频率等)
谁判断要不要注入 opencode 框架内置规则:「检测到访问 X 目录就附加 X 目录的 AGENTS.md 注册的 hook 函数:「if condition then inject」
频率 同一目录只在「首次访问时」注入一次,之后不再注入 每次条件满足都会注入(同一 session 可能注入很多次)
注入位置 附加在那次 tool_result 的末尾 附加在当前 turn 的某个末尾位置
跟着谁沉降 跟着首次访问那个 turn 沉到对话中部 永远在最新位置(每次新触发都是新 turn 的末尾)
典型例子 项目级 AGENTS.md(只在第一次 read 示例项目/* 时注入) [CONTEXT WINDOW MONITOR](token 用量超过 80% 触发)
[TODO CONTINUATION](todo 列表非空时触发)
[Category+Skill Reminder](task 调用次数到阈值触发)
在请求里能看到几条 一份目录的 AGENTS.md 全 session 只有 1 条 同一类 reminder 可能有 N 条,每次条件满足就多一条

举例对照:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
turn 1: 用户说「评估需求」
└─ agent 执行 read 示例项目/xxx.md
└─ tool_result 末尾附加 <system-reminder> + 示例项目/AGENTS.md
★ 触发式:因为这是 agent 第一次 read 示例项目/*
turn 2: 用户继续问问题
└─ agent 执行 read 示例项目/yyy.md
└─ tool_result 末尾不再附加 示例项目/AGENTS.md
★ 触发式:同一目录只在首次访问注入
turn 3-N: 对话推进,token 用量逐渐增加
└─ 当 token 超过 80% 阈值时
└─ 当前 turn 末尾追加 [CONTEXT WINDOW MONITOR] 提醒
★ 事件式:每次条件满足都会注入
turn N+1: 用户开新任务,agent 创建了 todo
└─ 每个 turn 末尾都可能追加 [TODO CONTINUATION]
★ 事件式:todo 状态持续触发条件

为什么这个区别对本方案重要:

  • 触发式注入 = 项目级 AGENTS.md 的命运。它只有一次进入注意力顶峰的机会(首次访问那个 turn 的末尾),之后随对话沉降。这就是为什么靠项目级 AGENTS.md 想覆盖全局 SDD 流程会越来越无力。
  • 事件式注入 = 系统给 agent 的「实时硬指令」通道。它持续在最新位置,注意力始终是 ★★★★★。本方案不直接利用事件式(因为是 opencode 框架内置的,用户写不了),但理解它有助于知道:项目级 AGENTS.md 拿不到这种持续 recency。

2.4 项目级 AGENTS.md 的真实命运

把上面两个概念套在项目级 AGENTS.md 身上:

1
2
3
4
5
6
7
8
9
turn 1: read 示例项目/xxx.md
└─ tool_result 末尾追加 <system-reminder> + AGENTS.md 全文
★ 此时位置 = 末尾, 注意力 ★★★★★
turn 2..N: 没有再注入这个 system-reminder
└─ 该内容驻留在 turn 1 的 tool_result 中, 跟着 turn 1 一起沉下去
到了 turn 11:
├─ 全局 AGENTS.md 仍在 system 顶层 → ★★★★★(永不衰减)
├─ 9 个 openspec-* 仍在 tools 顶层 → ★★★★(永不衰减)
└─ 项目 AGENTS.md 沉到 turn 1 中部 → ★★(中间塌陷区)

2.5 为什么「中文 + 某企业级 Agent 框架」覆盖能成功,「禁用 openspec-*」会失败?

不是因为项目级 AGENTS.md 一直靠后,而是综合四个机制:

机制 「中文/身份」覆盖 「禁用 openspec-*」覆盖
首次注入位置最强
简短条款高凸显度 ✅ 10 行中文+3 行身份
全局对应条款的共振强度 ★★ 隐含约束,弱 ★★★★★ 30KB + 9 个 openspec-* skill name
agent 自我强化回声 ✅ 每次回答都用中文,约定被反复强化 ❌ SDD 偶发触发,没有持续回声

回声效应决定了什么样的项目级条款能稳定盖过全局:每 turn 都触发的简短约定(语言、身份、风格)项目级能盖;偶发的复杂流程(SDD、部署、测试模式)项目级盖不了,得改全局。

3. 失败案例与对照实验:信号竞争的实证

为了验证「协议层位置劣势」不是空想,复盘一组真实 A/B 实验。

3.1 失败侧(被覆盖)

用户首条消息:

1
为这个服务增加一个 某调度平台 任务,定时扫描 bizType 为 life 的线索...

用户在 question 工具回答里说:「我不按 agents.md ,就走普通的 自研 SDD 流程 流程」
Sisyphus 实际执行:

1
2
3
skill("openspec-explore")        ← 错误,未走 自研 SDD 流程
skill("openspec-new-change")
skill("openspec-push-specs")

LLM 在歧义消解上走偏了:

1
2
3
4
"普通的 自研 SDD 流程 流程"两种解析:
解析 A (用户原意): 普通的 [自研 SDD 流程 流程] = 自研 SDD 流程 的标准玩法
解析 B (我误解): [普通的] [自研 SDD 流程 流程] = 普通流程, 自研 SDD 流程 那种
→ 因为 AGENTS.md 里 openspec-* 是「普通流程」的强 anchor, 选了 B

3.2 成功侧(未被覆盖)

用户首条消息:

1
基于 自研 SDD 流程-start,为这个服务增加一个 某调度平台 任务...

Sisyphus 实际执行:

1
2
3
4
✓ skill("自研 SDD 流程-start")    ← 首词 anchor 直接命中
✓ bash: scripts/自研 SDD 流程/preflight.sh
✓ skill("自研 SDD 流程-apply")
✓ ...全程忠实 自研 SDD 流程

3.3 对照分析

因子 失败侧 成功侧
用户提及 自研 SDD 流程 的位置 question 回答里(第 2 轮才出现) user message #1 首句首词
用户措辞 「走普通的 自研 SDD 流程 流程」 「基于 自研 SDD 流程-start」
关键词形态 自研 SDD 流程(被「普通的」修饰) 自研 SDD 流程-start(带连字符 = skill 名级精确)
与 skill 列表的字面匹配度 弱(要语义跳跃) 强(直接命中 自研 SDD 流程-start
AGENTS.md 信号 同样的 ~250 行 system-reminder 完全相同
system prompt 同样 完全相同

相同的容器、相同的 AGENTS.md anchor、相同的 system prompt,仅用户关键词形态与位置变了,routing 就从偏移变成了忠实。这反过来说明当前的对齐完全依赖用户每次精准措辞,是脆弱的。要让自研 SDD 不被覆盖能稳定复现,不能把宝压在用户层。

4. 三层防御方案:与 某企业级 Agent 框架 存量设计和谐共处

要稳定不被盖掉,必须同时在三层建防御,单点都不足以扛住。这套方案的核心特征是不删除任何 某企业级 Agent 框架 存量约束、只增量插入自研流程的优先级声明,可回滚。

4.1 防御机制全景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──────────────────────────────────────────────────────────┐
│ 用户层(措辞精准) │
│ 作用:降低 LLM 歧义消解的难度 │
│ 局限:不可靠,不能假设用户每次都精准 │
└────────────────────────┬─────────────────────────────────┘
│ 兜底
┌────────────────────────▼─────────────────────────────────┐
│ 个人层(agent 行为硬规则) │
│ 作用:在 LLM 推理路径上设防偏移闸口 │
│ 局限:依赖 agent 每次都遵守自己的规则 │
└────────────────────────┬─────────────────────────────────┘
│ 兜底
┌────────────────────────▼─────────────────────────────────┐
│ 系统层(信号源头改造)★ 必须做 │
│ 作用:把武器放进 system / tools 顶层字段,反脆弱 │
│ 局限:需要项目层一次性投入改造 │
└──────────────────────────────────────────────────────────┘

三条原则:宝不能压在用户层,用户措辞会变;宝不能压在个人层,agent 自我约束会被强 anchor 压过;必须在系统层下重手,让自研流程的优先级声明出现在协议顶层字段。

4.2 P0:在全局 AGENTS.md 顶部加「流程让位声明」

改动位置:~/.config/opencode/AGENTS.md
改动内容(在文件最顶部、所有现有内容之前插入):

1
2
3
4
5
6
7
8
9
10
11
12
> 【流程优先级声明】
> 本文档描述项目的默认 SDD 流程(如 openspec-* 命令)。
> 若用户在当前 turn 显式提及自研流程关键词
> (自研 SDD 流程 / 自研 SDD 流程 / `/自研 SDD 流程-*`),
> 必须先加载 `自研 SDD 流程` skill 总路由,
> 由总路由决定后续子 skill 调度。
> 本文档中描述的 `openspec-*` 命令在此场景下仅作为后备/参考。
>
> 触发关键词清单(命中任一即生效):
> - `自研 SDD 流程`、`自研 SDD 流程-start`、`自研 SDD 流程-apply`、`自研 SDD 流程-archive`
> - `自研 SDD 流程`、`自研 SDD 流程-start` 等
> - slash command:`/自研 SDD 流程-*`

为什么必须改全局而不是项目级:全局 AGENTS.md 进协议层 system 顶层字段,永不被推走 ★★★★★;项目 AGENTS.md 进协议层 messages[N].tool_result,跟着 turn 沉降 ★★。
和谐共处的关键:不删除任何现有条款;不修改「两阶段流程」等核心约束;只在顶部加一段 if-else 让位声明;用户不提自研关键词时,某企业级 Agent 框架 完全按原流程跑。

4.3 P1:自研 SDD 所有子 skill description 加防干扰锚

改动位置:每一个 自研 SDD 流程-* 子 skill 的 SKILL.md frontmatter
改动内容(在 description 末尾追加):

1
2
3
4
5
6
7
8
9
10
---
name: 自研 SDD 流程-start
description: |
[原有描述...]
优先级声明:当用户在当前 turn 显式提及 自研 SDD 流程 /
自研 SDD 流程 关键词时,本 skill 拥有最高 workflow
优先级,覆盖宿主 AGENTS.md 中推荐的所有其他 SDD 流程
(openspec-* / 某内部 SDD 方案 / 其他)。
与 AGENTS.md 冲突时,以本 skill 流程为准。
---

为什么每个子 skill 都要加,不能只加总路由:skill description 进协议顶层 tools 字段,永不被推走;但只有总路由被激活时总路由内容才进 context,靠「先调总路由再传递规则」会有鸡生蛋问题——用户消息直接命中子 skill 时总路由没机会发声。每个子 skill 都带这条规则,任何入口被命中都能立即生效。

4.4 P2:注册 /自研 SDD 流程-* slash command

改动位置:~/.config/opencode/command/
改动内容:创建以下文件

1
2
3
4
5
~/.config/opencode/command/
├── 自研 SDD 流程-start.md
├── 自研 SDD 流程-apply.md
├── 自研 SDD 流程-archive.md
└── 自研 SDD 流程-verify.md

每个文件内容示例(自研 SDD 流程-start.md):

1
2
3
4
5
---
description: 启动 自研 SDD 流程 自研 SDD 流程
---
调用 `skill("自研 SDD 流程-start")`,进入自研 SDD 起始阶段。
本命令的优先级高于 AGENTS.md 中描述的 openspec-* 流程。

slash command 是最可靠的激活方式:执行路径绕过 LLM 的歧义消解;用户打 /自研 SDD 流程-start 是硬开关,不存在被解读为 openspec 的可能;即使 P0、P1 失效,slash command 仍能强制走自研流程。

4.5 P3:通过 sandbox dotfiles / init script 持久化

问题:~/.config/opencode/ 在 sandbox 重建时被还原为镜像默认值,P0、P1、P2 的改动会丢。
三种持久化路径,按可行性排序:

方案 做法 前提
3a. dotfiles 仓库 AGENTS.md + command/自研 SDD 流程-*.md + skills/自研 SDD 流程-*/ 放到一个 git 仓库,sandbox 启动脚本 git clone + cp -r~/.config/opencode/ 某 sandbox 平台 支持启动脚本注入
3b. 环境变量驱动 init sandbox 接受环境变量指定 dotfiles 仓库 URL,启动时自动应用 sandbox 平台提供该机制
3c. 自定义镜像层 基于 某 sandbox 镜像 出一个 baked-in 自定义配置的镜像 有镜像构建权限

短期先做 P0+P1+P2 立即生效;中期用 3a 落地,团队可共用;长期推动 某企业级 Agent 框架 团队官方支持「用户自定义 SDD 流程」的扩展点。

4.6 个人层(agent 行为硬规则)

把以下规则写进自研 SDD 的总路由 skill 内部(作为 agent 自检清单):

Rule 说明
关键词触发即加载总路由 用户消息出现自研框架关键词时,第一动作必须是 skill("自研 SDD 流程"),禁止跳过总路由直接调子 skill
当前 turn 用户明示 > 项目静态规则 AGENTS.md 是静态规则,user 当前 turn 是动态最高优先级。冲突时显式向用户复述:「你刚才说 X,与 AGENTS.md 的 Y 冲突,按 X 执行」
歧义消解必须列举 遇到「普通的 X 流程」这类有多解的措辞,列出所有可能解析让用户拍板,不要让 attention anchor 替我做选择
禁止字面相近的语义跳跃 自研 SDD 流程openspec 仅相似不等价,必须当作两个独立框架处理

4.7 用户层(措辞习惯建议)

虽然不能依赖用户每次精准,但好习惯能显著降低风险:

不推荐 推荐
「走普通的 自研 SDD 流程 流程」 「基于 自研 SDD 流程-start 开始」
「用 自研 SDD 流程」 「用 自研 SDD 流程 这个 skill 家族」
在 question 回答里第二轮才提框架名 在 user message #1 首句首位就锚定框架
自然语言描述 直接打 /自研 SDD 流程-start slash command

5. 协议层胜负盘点

把方案叠到 §1 的协议结构图上,可视化「武器在哪个字段」:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──────────────────────────────────────────────────────┐
│ API 顶层 system 字段(永不被推走)★★★★★ │
│ • 全局 AGENTS.md(30KB 推 openspec-*) │
│ • [P0 后] 顶部新增「流程让位声明」 ← 你的武器 ✅ │
├──────────────────────────────────────────────────────┤
│ API 顶层 tools 字段(永不被推走)★★★★ │
│ • 9 个 openspec-* skill schema │
│ • [P1 后] 自研 SDD 流程-* skill description 含优先级声明 │
│ • [P2 后] /自研 SDD 流程-* slash command │← 你的武器 ✅
├──────────────────────────────────────────────────────┤
│ messages 数组(随对话沉降)★★ │
│ • 项目级 AGENTS.md(只能在这里) │
│ • 用户当前 turn message(仅最后一条 ★★★★★) │
└──────────────────────────────────────────────────────┘

改造前后对比:

维度 改造前 改造后
自研 SDD 在协议顶层的存在感 仅 skill description 里平级一行 system 让位声明 + 每个子 skill description 强调 + slash command
用户措辞精准度依赖 高(必须用 自研 SDD 流程-start 低(说 自研 SDD 流程 / 自研 SDD 流程 都能触发)
AGENTS.md 强 anchor 的对抗 项目级单点对抗,胜算 30% 系统级让位 + 多 skill 共振,胜算 95%
sandbox 重建后状态 P0-P2 丢,P3 持久

6. 实操清单

6.1 立即可做(30 分钟)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 备份现有全局 AGENTS.md
cp ~/.config/opencode/AGENTS.md ~/.config/opencode/AGENTS.md.bak
# 2. 在顶部插入 P0 让位声明
# (手工编辑,把 §4.2 的内容贴到文件最顶部)
# 3. 给自研 skill 加防干扰锚
# 遍历 ~/.config/opencode/skills/自研 SDD 流程-*/SKILL.md
# 在 description 末尾追加 §4.3 的优先级声明
# 4. 创建 slash command
mkdir -p ~/.config/opencode/command
# 创建 自研 SDD 流程-start.md / 自研 SDD 流程-apply.md / 自研 SDD 流程-archive.md
# (按 §4.4 模板)
# 5. 重启一个新 chat 验证
# 用普通措辞(不带连字符)测试:「用 自研 SDD 流程 走一遍 SDD」
# 观察 agent 是否调用 自研 SDD 流程-start 而非 openspec-explore

6.2 中期持久化(1-2 周)

  • [ ] 询问某 sandbox 平台 团队:是否支持 dotfiles / 启动脚本注入
  • [ ] 把 P0-P2 的所有改动放到一个独立 git 仓库(如 opencode-自研 SDD 流程-dotfiles
  • [ ] 配置 sandbox 在启动时拉取并 sync 到 ~/.config/opencode/

6.3 长期规范化

  • [ ] 团队内传播「用 自研 SDD 流程-start 这种带连字符的精确名」措辞约定
  • [ ] 推动 某企业级 Agent 框架 官方提供「自定义 SDD 流程」的扩展点(让 P0 不需要直接编辑 AGENTS.md

7. 风险与回滚

风险 缓解措施 回滚
P0 改动影响其他人 / 其他流程 改动只新增「if 自研关键词命中」的 if 分支,不删除任何现有条款 删除新增段落即可
sandbox 重建丢失 P3 dotfiles 持久化 重新跑 dotfiles sync 脚本
自研 SDD 内部 bug 影响业务 P0 让位声明里写明「仅当用户显式提及关键词时生效」 改动仅影响显式触发场景
与未来 某企业级 Agent 框架 升级冲突 用 dotfiles 仓库管理改动,每次 sandbox 重建后比对 git diff 后选择性合并

附录 A:本方案与 3 份前置文档的映射

前置文档 贡献的核心结论 在本方案中的位置
OpenCode 配置体系探索之旅.md 协议层四层模型 + system/tools/messages 字段归属 §1 系统原理
OpenCode Sandbox 配置体系全解 v4.md context window 物理布局 + U 型注意力 + 触发式 vs 事件式注入 + 自我强化回声 §2 context window
需求澄清的流程控制.md 信号竞争失败/成功 A/B 实验 + 三层防御机制 §3 失败案例 + §4 三层方案

附录 B:关键术语速查

术语 含义
Primacy 注意力曲线开头的高权重区——system prompt / 全局 AGENTS.md
Recency 注意力曲线末尾的高权重区——当前 user message / 动态 reminder
Lost in the Middle 中间塌陷区——历史消息累积后的弱注意力区
触发式注入 agent 干了某个动作(如首次 read 一个目录)才被注入;同一目录全 session 只注入一次
事件式注入 hook 监控的状态满足条件就注入;同一类条件可能反复触发
顶层 tools 字段 API 请求里和 system / messages 平级的独立字段,装的是工具说明书(name/description/schema),永不被推走
tool_result block messages 数组里某个 user message 的 content block,装的是工具实际执行后的输出,会随对话沉降
自我强化回声 agent 输出本身在历史中持续重复约定,相当于约定被反复「再注入」
协议顶层字段 Anthropic API 的 system / tools 字段,独立于 messages 数组,永不被推走