生成时间: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 沉到中间。 |
|
|
|
|
|
要解释清楚"为什么自研流程会被盖掉、为什么必须改全局 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 区分提升到接口级别,system 和 tools 是顶层独立字段,不在 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: { "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 累积... ] }
|
这两个字段名都带「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": <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_result 是 user 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。
把上面两个概念套在项目级 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
| cp ~/.config/opencode/AGENTS.md ~/.config/opencode/AGENTS.md.bak
mkdir -p ~/.config/opencode/command
|
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 数组,永不被推走 |