---
url: /wave-agent/specs/core/context-engineering.md
description: 系统提示词静态/动态分块与精简模式、`<system-reminder>` 注入、messages 组装层次与 fork 上下文边界
---

# 功能规格说明：上下文工程

**创建日期**：2026-03-25\
**改名日期**：2026-09-30（原名「Prompt 工程」，纳入上下文消息组装后改名）

## 用户场景与测试 *（必填）*

### 用户故事：集中式提示管理（优先级：P1）

作为开发者，我希望以集中和结构化的方式管理所有系统提示和工具描述，以便我可以轻松更新、版本化和优化它们而不修改核心逻辑。

**为什么是这个优先级**：这是该功能的核心价值，实现代理行为的更好可维护性和迭代。

**独立测试**：可以通过验证所有提示被移动到专用提示管理系统且代理仍然行为正确来测试。

**验收场景**：

1. **假设**有一组系统提示和工具描述，**当**我将它们移动到提示工程框架时，**则**代理应该在初始化期间仍然接收正确的提示
2. **假设**框架已就位，**当**我在框架中更新提示时，**则**代理应该在下一次会话中立即使用更新后的提示

***

### 用户故事：动态工具描述（优先级：P1）

作为开发者，我希望基于当前上下文（如可用子代理、技能或工作目录）提供动态工具描述，以便代理拥有工具选择的最相关信息。

**为什么是这个优先级**：这已经通过工具中的 `prompt()` 部分实现，但在框架内形式化将使其更健壮和易于管理。

**独立测试**：可以通过验证工具描述基于提供的上下文变化来测试。

**验收场景**：

1. **假设**带有动态提示的工具，**当**工具以不同上下文注册时，**则**工具在 OpenAI 函数调用配置中的描述应该反映每个上下文的正确提示

***

### 用户故事：提示词精简模式（优先级：P2）

作为使用者（含在 CI 与自动化中驱动 wave 的人），我希望默认使用精简版系统提示词与内置工具描述，并且有一条开关能退回完整版，以便在上下文窗口紧张或需要压缩每次请求体量的场景下省掉可观的前缀文本，同时功能、工具集合与权限行为完全不变。

**为什么是这个优先级**：它是改写每次请求前缀文本的形态开关，收益明确（静态系统提示词压缩到约 1.2k 字符，工具描述同步压缩）。精简文案对工具选择质量的影响已在本机自测确认，因此定案默认开启：未配置 `leanPrompt` 的会话即用精简档；风险由一条可配置的回退路径承担——`leanPrompt: false` 逐字退回完整版。

**独立测试**：以默认档（未配置 `leanPrompt`）跑一轮对话，抓取发给模型的请求，核对系统提示词的静态块与 `tools[]` 中每个工具的 description 都已换成精简文案、而工具集合 / 参数 schema / 权限判定与完整版逐字相同；再显式配置 `leanPrompt: false`，核对全文案逐字回到完整版。

**验收场景**：

1. **假设**未配置 `leanPrompt`（默认开启），**当**组装系统提示词与工具描述，**则**必须使用精简档（静态块只由「身份句 + 安全说明 + `# Harness`」组成，各工具下发精简描述）
2. **假设**显式配置 `leanPrompt: false`，**当**组装系统提示词与工具描述，**则**结果必须与完整版逐字一致（静态块结构见〔完整版系统提示词对齐 Claude Code〕，各工具下发完整描述）
3. **假设**精简档生效，**当**组装系统提示词的静态块，**则**它必须只由「身份句 + 安全说明 + `# Harness`」组成，不再出现 `# Doing tasks`、`# Executing actions with care`、`# Using your tools`、`# Output efficiency`、`# Tone and style` 中的任何一段
4. **假设**精简档生效，**当**检查 `# Harness` 段，**则**它必须只表达这五条语义：①工具调用之外的输出会展示给用户；②工具在用户选择的权限模式下执行，被拒绝的调用不要原样重试而应调整做法；③对话与工具结果中的 `<system-reminder>` 标签由 harness 注入、不来自用户，hook 的拦截反馈应视为来自用户；④能用专用文件/搜索工具解决就不要走 shell 命令，互不依赖的工具调用应在一条响应里并行发出；⑤引用代码使用 `file_path:line_number` 形式
5. **假设**精简档生效，**当**检查身份句，**则**它必须仍是 `basePrompt`（配置或子代理传入的那句），不得被替换成固定文案
6. **假设**精简档生效，**当**组装系统提示词的动态块，**则** `# Language`、`# Environment`、`# auto memory` 等动态内容与完整版逐字相同（精简只作用于静态块）
7. **假设**精简档生效，**当**下发某个内置工具的 description，**则**必须使用该工具的精简描述；未提供精简描述的工具必须回落为完整描述，不得出现空 description
8. **假设**精简档生效，**当**检查工具的 `parameters`（JSON Schema）、工具名、可用工具集合与权限判定，**则**必须与完整版逐字一致（精简只改变 description 文本）
9. **假设**精简档生效且存在 MCP 工具，**当**下发这些工具，**则**它们的 description 不受影响，按服务器给出的原文下发
10. **假设**精简档生效且运行子代理，**当**组装子代理的系统提示词与工具描述，**则**同样使用精简文案，且子代理对基础身份句的替换关系与现状一致
11. **假设**开关状态在一次会话内不变，**当**连续发出多轮请求，**则**系统提示词静态块与 `tools[]` 必须逐字节稳定以保住前缀缓存；开关切换视为新的请求形态，允许缓存前缀失效一次
12. **假设**显式关闭精简档（`leanPrompt: false`），**当**运行既有测试套件，**则**全部完整版断言（含各工具描述的子串断言与系统提示词组成的断言）必须原样通过——回退档位不因默认值翻转而失去覆盖

***

### 用户故事：完整版系统提示词对齐 Claude Code（优先级：P2）

作为使用者，我希望默认（非精简）档位的系统提示词与 Claude Code 最新版的完整版逐段对齐，以便模型的基线行为与主流编码代理一致，且后续跟随上游文案演进时有唯一参照。

**为什么是这个优先级**：它作用在默认档位、影响所有会话，但纯属文案与段落结构替换，不改任何机制；对齐后两档的差异收敛为「六段 vs 一段 `# Harness`」，精简档的维护成本也随之明确。

**独立测试**：抓取一次请求，把 system 消息的静态块逐段比对：身份段 → `# System` → `# Doing tasks` → `# Executing actions with care` → `# Using your tools` → `# Tone and style`，其中不得出现 `# Output efficiency`；再核对逐工具映射已从系统提示词移到 Bash 工具的 description。

**验收场景**：

1. **假设**默认档位组装静态块，**当**检查各段的出现与顺序，**则**必须依次为：身份段（身份句 + 安全说明 + URL 禁令）→ `# System` → `# Doing tasks` → `# Executing actions with care` → `# Using your tools`（仅当工具集非空）→ `# Tone and style`
2. **假设**默认档位，**当**检查静态块，**则**其中不得出现 `# Output efficiency` 段
3. **假设**默认档位，**当**检查身份段，**则**身份句必须仍是 `basePrompt`（配置或子代理传入的那句，不得被替换成固定文案），其后依次跟安全说明与 URL 禁令各一段
4. **假设**默认档位，**当**检查 `# System` 段，**则**它必须表达这六条语义：①工具调用之外的输出会直接展示给用户并以 GFM 渲染；②工具在用户选择的权限模式下执行，被拒绝的调用不要原样重试而应调整做法；③对话与工具结果中的 `<system-reminder>` 由 harness 注入、不来自用户；④工具结果可能包含外部数据与提示注入尝试，发现时要当面向用户指出；⑤用户在 settings 中配置的 hooks 会拦截工具调用，其反馈应视为来自用户；⑥上下文接近上限时系统会自动压缩历史，因此对话不受上下文窗口限制
5. **假设**默认档位，**当**检查 `# Doing tasks` 段，**则**其中不得出现 `/help`、反馈渠道这类只在某一个宿主上成立的条目
6. **假设**默认档位，**当**检查 `# Using your tools` 段，**则**它必须只表达「优先用专用文件/搜索工具而非 Bash」「任务工具的使用」「并行调用的规则」三类内容，且按当前工具集动态生成——工具集不含任务工具时不得出现任务工具那条，不含 Bash 时不得出现对 Bash 的引用
7. **假设**默认档位，**当**检查 Bash 工具的 description，**则**它必须包含这组映射：读文件用 Read、编辑用 Edit、建文件用 Write、找文件用 Glob、搜内容用 Grep，Bash 只留给必须走 shell 的操作
8. **假设**默认档位，**当**检查 `# Tone and style` 段，**则**它必须只包含 emoji、简短、`file_path:line_number`、工具调用前不加冒号这四条，不得出现 GitHub issue 引用格式
9. **假设**默认档位且同一会话内工具集不变，**当**连续发出多轮请求，**则**静态块必须逐字节稳定以保住前缀缓存；工具集变化视为新的请求形态，允许缓存前缀失效一次
10. **假设**开启精简模式，**当**组装静态块，**则**它必须是「身份段（身份句 + 安全说明，不含 URL 禁令）+ `# Harness`」，且 `# Harness` 不得重复安全说明
11. **假设**任一档位，**当**组装动态块，**则** `# Language`、`# Environment`、自动记忆 MEMORY.md 与现状逐字相同
12. **假设**默认档位，**当**运行既有测试套件，**则**断言系统提示词组成与 Bash 工具描述的用例必须已按新结构同步更新并通过

***

### 用户故事：System-Reminder 消息注入（优先级：P1）

作为开发者，我希望将临时指令作为 `<system-reminder>` 包装的用户消息（而非系统提示更改）注入对话，以便系统提示在模式切换间保持不变，保持提示缓存并减少 token 成本。

**为什么是这个优先级**：在会话中途修改系统提示（如附加计划模式指令）会在每次模式更改时失效整个缓存的系统提示前缀。将指令作为带 `<system-reminder>` 标签的用户消息注入保持缓存同时仍然向模型传递上下文指令。此模式被 Claude Code 用于所有动态模式特定指导。

**独立测试**：进入计划模式，验证系统提示与上一轮相同且计划模式指令作为 `<system-reminder>` 用户消息出现在 API 请求中。

**验收场景**：

1. **假设**代理进入计划模式，**当**下一个 API 请求被组装时，**则**计划模式指令必须作为 `<system-reminder>` 包装的用户消息注入，而非附加到系统提示
2. **假设**代理退出计划模式，**当**下一个 API 请求被组装时，**则**"退出计划模式"的 `<system-reminder>` 用户消息必须被注入（仅一次）
3. **假设**代理在退出后重新进入计划模式，**当**计划文件已存在时，**则**重新进入的 `<system-reminder>` 用户消息必须被注入，指示模型读取现有计划并评估是继续还是重新开始
4. **假设**系统提示已被先前请求缓存，**当**模式切换发生时，**则**系统提示必须保持字节相同，启用系统消息前缀的缓存命中

***

### 用户故事：节流提醒注入（优先级：P2）

作为开发者，我希望计划模式提醒被节流，以便它们仅每 N 个人类轮次注入一次（不是每个工具轮次），以减少 token 浪费同时保持约束意识。

**为什么是这个优先级**：没有节流，计划模式提醒在每次 API 调用（包括单个人类轮次内的工具轮次）时注入，浪费 token。节流到每 5 个人类轮次减少成本，同时定期完整提醒防止模型忘记约束。

**独立测试**：在计划模式下工作 10+ 轮，验证提醒每 5 个人类轮次出现一次，在完整版本和稀疏版本之间交替。

**验收场景**：

1. **假设**计划模式活跃且提醒刚被注入，**当**不到 5 个人类轮次过去时，**则**不注入计划模式提醒
2. **假设**自上次提醒以来已过去 5 个人类轮次，**当**下一个 API 请求发出时，**则**注入计划模式提醒
3. **假设**每第 5 次提醒注入，**当**提醒被注入时，**则**它必须是完整的计划模式指令（完整的 5 阶段工作流）
4. **假设**非第 5 次提醒注入，**当**提醒被注入时，**则**它必须是引用先前完整指令的简短稀疏提醒

***

### 用户故事：模式切换的覆盖语言（优先级：P1）

作为开发者，我希望在模式切换后注入的 `<system-reminder>` 指令包含明确的覆盖语言（如"This supercedes any other instructions you have received"），以便模型理解新约束优先于对话中先前的工具调用历史。

**为什么是这个优先级**：当在对话中途从 default/acceptEdits 模式切换到计划模式时，消息历史包含最近的 Edit/Write 工具调用，可能误导模型继续编辑。覆盖语言加上提醒是模型看到的最近指令，确保模型遵守新模式约束。

**独立测试**：进行包含 Edit/Write 工具调用的对话，然后进入计划模式，验证计划模式提醒包含"supercedes"覆盖语言并出现在消息流中所有先前工具调用之后。

**验收场景**：

1. **假设**对话包含最近的 Edit/Write 工具调用，**当**用户进入计划模式时，**则**计划模式 `<system-reminder>` 必须作为 API 请求中的最后一条用户消息注入（在所有先前工具调用之后）
2. **假设**计划模式提醒被注入，**当**模型读取它时，**则**提醒必须包含"This supercedes any other instructions you have received"或等效覆盖语言
3. **假设**计划模式提醒在模式切换后被注入，**当**模型尝试对计划文件以外的任何文件使用 Edit 或 Write 时，**则**权限系统在运行时阻止该操作（纵深防御）

***

### 用户故事：上下文消息组装结构（优先级：P1）

作为开发者，我希望每次请求发给模型的 `messages` 数组有明确、稳定的组装结构（层次顺序、注入时机、缓存标记、持久化与可见性），以便新增的注入（提醒、附件、工具目录公告、hook 上下文）都落到正确位置——既不击穿前缀缓存，也不把临时指令污染成历史事实。

**为什么是这个优先级**：组装位置同时决定三件事——前缀缓存能否命中（改写或插入已有历史会击穿）、模型是否会把临时指令当成已发生的事实（`isMeta` 的可见性差异）、会话恢复后该消息是否还在（是否持久化）。三者任一错位都会在长会话里放大成 token 成本或行为问题，且在实现中容易被各注入点各写一套。

**独立测试**：构造一轮含工具调用的对话，抓取发给模型的请求，核对 `messages` 数组的分层、每类消息的位置与缓存标记；再对比会话 JSONL 中实际持久化的消息集合。可对 `buildSystemPrompt` 与 `messageManager` 的产物分别断言。

**验收场景**：

1. **假设**一轮对话组装完成并发出请求，**当**检查消息数组的第 0 位，**则**它必须是 system 消息：静态块（身份段 + `# System` + `# Doing tasks` + `# Executing actions with care` + `# Using your tools` + `# Tone and style`）带缓存标记，动态块（语言指令 + 环境信息 + 自动记忆 MEMORY.md）不带缓存标记；子代理仅替换基础系统提示词，其余块与位置不变
2. **假设**请求被组装，**当**检查消息数组的第 1 位，**则**它必须是 `<system-reminder>` 包裹的 user meta 消息（项目 AGENTS.md + 用户 AGENTS.md + 无条件规则）：每轮注入、无缓存标记、不持久化，且是**唯一**每轮注入的消息
3. **假设**一轮内产生了历史消息，**当**检查消息数组，**则**文本块 / 图片块 / 工具块 / 后台任务通知块 / 推理块必须持久化到 session JSONL、对用户可见，且只有最后一条有内容的消息带缓存标记
4. **假设**某个触发条件成立（计划模式提醒 / 条件规则 / 任务提醒 / SessionStart Hook 上下文 / 后台任务通知 / Token 限制续写），**当**下一轮请求组装时，**则**该内容必须以 `isMeta: true` 的 user 消息追加到当时的消息尾部、持久化到会话文件、对用户不可见，各类型按自己的触发条件独立判定
5. **假设**任一触发式注入发生，**当**检查消息数组，**则**它必须只追加，不得插入历史中间或改写既有消息（保住前缀缓存）
6. **假设**子代理（含 Workflow spawn 的常规子代理）发起请求，**当**组装消息数组时，**则**它必须复用主代理的同一套组装规则，仅把基础系统提示词替换为子代理自己的提示词，其余块与位置不变

***

### 用户故事：专用调用的上下文边界（优先级：P1）

作为开发者，我希望区分「复用主对话上下文的 Fork」与「自带系统提示词的独立调用」两类离主循环的请求，以便新增的旁路能力（总结、提取、单次问答、网页解析）选对路径——选错就丢掉缓存收益或让子任务污染主会话。

**为什么是这个优先级**：Fork 复用主请求前缀是缓存收益的来源（见 [prompt-cache-control.md](./prompt-cache-control.md)）；独立调用则必须完全隔离于会话历史。两类路径的边界一旦模糊，就会出现「本该命中缓存的 fork 每轮重算全量历史」或「独立请求把结果写回主会话」这类回归。

**独立测试**：分别触发压缩、自动记忆提取、BTW 三条 fork，核对各自请求前缀与主循环最后一次请求一致、且命中缓存；触发一次网页内容提取，核对请求使用独立系统提示词且不携带会话历史。

**验收场景**：

1. **假设**触发上下文压缩、自动记忆提取或 BTW 旁路问题，**当**构建请求时，**则**必须复用主对话上下文（主系统提示词、工具列表、模型与生成参数），指令作为末尾 user 消息追加、无独立系统提示词，并命中 prompt cache：压缩走 `runCompactFork` → `runForkLoop`、提取走 `runAutoMemoryFork` → `runForkLoop`、BTW 走 `runBtwFork`（同构的内联单轮调用，不提供工具）
2. **假设**触发网页内容提取，**当**构建请求时，**则**必须走独立调用：以内置 `WEB_CONTENT_SYSTEM_PROMPT` 作为系统提示词、不经过主系统提示词组装、独立请求、不携带会话历史
3. **假设** Workflow 需要结构化输出，**当**它 spawn 常规子代理（general-purpose）时，**则** schema 指令必须追加到 prompt 末尾并强制 StructuredOutput 的 `tool_choice`——这属于常规子代理机制而非独立调用路径，子代理仍复用主组装规则
4. **假设**主循环最后一条 assistant 消息仍停留在 streaming（未完成），**当** `runBtwFork` 组装请求时，**则**必须剥掉该消息后再组装，使请求前缀与主循环最后一次完整请求一致

***

### 边界情况

* **缺失的提示**：当框架中缺少必需提示时会发生什么？（系统应该回退到默认提示或显示清晰的错误消息）
* **大型提示**：系统如何处理可能超过 token 限制的非常大提示？（框架应该提供提示压缩或截断工具）
* **冲突的提示**：系统如何处理来自不同提示来源的冲突指令？（框架应该定义明确的优先级顺序）
* **快速模式切换**：当用户在计划和非计划模式之间快速切换时，进入和退出的 `<system-reminder>` 消息不应同时注入。重新进入计划模式时应清除退出附加标志
* **压缩后重新进入**：当压缩移除所有对话历史包括先前的 `<system-reminder>` 消息时，系统必须重新注入完整的计划模式提醒（非稀疏），因为没有更早的完整指令存在
* **`isMeta` 消息的可见性与持久化是两件事**：`isMeta: true` 的用户消息对用户不可见，但必须持久化在会话文件中——恢复会话后它仍然存在，否则模型会失去仍然生效的约束
* **触发式注入一律只追加**：提醒、条件规则、工具目录公告、MCP 说明等任何触发式注入都追加到当时的消息尾部，不得插入历史中间或改写已有消息；系统提示词与 `tools[]` 必须逐字节稳定以保住前缀缓存
* **每轮注入只有一个**：只有第 1 位的 meta 消息每轮重算，其余注入都按各自的触发条件判定，不得退化为逐轮注入
* **精简描述的维护**：精简文案与完整描述并列维护、逐工具一份；完整描述演进时精简版必须同步更新，不允许只改一边导致两者对同一工具给出不同语义
* **精简模式不得改变能力**：任何「某工具/某能力在精简模式下消失或行为不同」的实现都算缺陷——精简只允许替换文本，不允许改动工具集合、参数 schema、权限判定或任何运行时行为
* **开关的作用域**：会话/进程级配置，创建会话时读取；同一会话内保持不变，避免同一会话内系统提示词前缀漂移。开启与关闭是两种请求形态，切换时允许缓存前缀失效一次
* **两档位的关系**：默认档位是完整版（对齐 Claude Code 完整版的六段结构），精简档位只替换静态块内容，身份句与动态块的取用规则两档一致；两档的身份段都含安全说明，URL 禁令只出现在完整版
