---
url: /wave-agent/specs/core/memory-management.md
description: '`AGENTS.md` 与 `.wave/rules` 分层记忆、自动记忆提取 fork 与 `MEMORY.md` 容量治理、嵌套注入与陈旧提醒'
---

# 功能规格说明：记忆管理

**创建日期**：2026-01-22

## 澄清

### 2026-01-27 会议

* 问：如果任务涉及多个文件，路径特定的记忆规则应如何激活？ → 答：并集：激活匹配任务中当前正在读取或修改的任何文件的所有记忆规则
* 问：哪些工具交互或上下文信号应触发路径特定记忆规则的激活？ → 答：上下文中的任意文件：当前加载在上下文窗口中（通过 Read、Grep 等）或正在写入的任何文件

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

### 用户故事：保存项目特定记忆（优先级：P1）

作为用户，我希望代理保存项目特定的规则或上下文，以便它记住这个项目的信息。

**为什么是这个优先级**：这是核心功能，允许代理适应不同的项目需求和风格。

**独立测试**：可以要求代理记住项目特定规则（如"始终使用 pnpm"）并验证它被保存到 `AGENTS.md`。

**验收场景**：

1. **假设**用户要求代理记住项目相关内容，**当**代理识别为项目规则时，**则**它必须被保存到当前目录的 `AGENTS.md`
2. **假设**信息已保存在 `AGENTS.md` 中，**当**代理被问到相关问题时，**则**它必须在响应中使用该信息

***

### 用户故事：保存全局用户记忆（优先级：P1）

作为用户，我希望代理保存跟随我跨所有项目的全局偏好，这样我不必重复它们。

**为什么是这个优先级**：对不同工作空间之间的个性化体验至关重要。

**独立测试**：可以要求代理记住全局偏好（如"我的名字是 Alice"）并验证它被保存到全局记忆文件（如 `~/.wave/AGENTS.md`）。

**验收场景**：

1. **假设**用户要求代理全局记住某些内容，**当**代理识别为用户偏好时，**则**它必须被保存到全局用户记忆文件
2. **假设**信息已保存在全局记忆中，**当**代理在任意项目中使用，**则**它必须能够访问该信息

***

### 用户故事：管理记忆（优先级：P2）

作为用户，我希望查看和删除已保存的记忆条目，以便保持我的上下文准确和最新。

**为什么是这个优先级**：防止记忆因过时或不正确的信息而变得杂乱。

**独立测试**：可以打开记忆管理 UI，找到条目并删除它。验证它从相应文件中被移除。

**验收场景**：

1. **假设**用户打开记忆管理 UI，**则**他们必须看到所有项目和用户记忆条目的列表
2. **假设**条目被选中，**当**用户选择删除它时，**则**它必须从存储文件中被移除

***

### 用户故事：自动记忆（优先级：P1）

作为用户，我希望代理自动跨会话记住重要信息，而不必手动保存。

**为什么是这个优先级**：显著减少摩擦并允许代理建立对项目和工作流的长期理解。

**独立测试**：可以执行涉及特定构建命令或架构决策的任务。开始新会话并通过检查系统提示或提出相关问题来验证代理知道该信息。

**验收场景**：

1. **假设**自动记忆已启用，**当**代理识别有价值的信息（构建命令、调试见解等）时，**则**它必须能够将其保存到项目的自动记忆目录
2. **假设**信息已保存在 `MEMORY.md` 中，**当**新会话开始时，**则**`MEMORY.md` 的前 200 行必须包含在系统提示中；**当**索引超过 200 行或超过 25000 字符时，**则**必须按两个上限一并截断（先按行、再按字符且在换行处切分，避免切断行），并附加一条写明触发原因（行数 / 字符数）与「单行索引应短于约 150 字符，细节移到主题文件」的警告，且该警告必须点名丢失范围（见「自动记忆容量治理」场景 1）
3. **假设**代理在 git 仓库中，**则**该仓库的所有工作树必须共享同一个自动记忆目录
4. **假设**代理需要写入其自动记忆，**则**它必须能够在无需手动批准的情况下执行（安全区域）

***

### 用户故事：自动记忆容量治理（优先级：P1）

作为用户，我希望 `MEMORY.md` 与每个记忆正文文件一旦接近或越过上限就立刻被拦下并说明损失，而不是静默截断——索引是正文唯一的发现入口，被截掉的条目等于正文不可发现；而截断切在末尾、新条目也追加在末尾，两者叠加就是「越新写的越读不到」。

**为什么是这个优先级**：索引超限是单调恶化的——加载期那句警告只在内存里拼、从不写回磁盘，写入侧没有任何检查，文件只会继续长大。实测本机两份真实记忆都已越线（29,659 字符 / 73 行、219,935 字符 / 431 条），其中一份当天新写的 3 条在任何会话都读不到；根因是行长而非条数（73 条远低于 200 行上限，却已超字符上限）。

**独立测试**：构造一份超过 25000 字符的 `MEMORY.md` 构建系统提示，检查警告是否点名丢失行数与起始行号；让代理通过 Write/Edit 把 `MEMORY.md` 写到 80% 以上与 100% 以上、把某个正文文件写到 4096 字节以上、写一个缺 `description` 的正文文件，检查工具结果里追加的提醒；删掉 `MEMORY.md` 只留正文文件后新会话启动，检查索引是否被自动生成。

**验收场景**：

1. **假设**`MEMORY.md` 触发了任一上限，**当**加载并构建系统提示时，**则**警告除触发原因（行数 / 字符数）外必须算出具体损失：已加载多少行、共多少行、从第几行起被丢弃，并回显首条被丢弃内容的前 80 字符；**当**损失是字符上限把第一行切断时，**则**改为说明「第 1 行前 N 个字符之后的全部内容被切断」
2. **假设**警告被附加，**则**其中给出的单行建议长度必须与提示词侧一致（150 字符），不得在同一份提示里出现两个建议值
3. **假设**代理通过 Write 或 Edit 写入了 `MEMORY.md`，**当**写入成功时，**则**必须在工具结果里追加一条占用率提醒：行占用率与字符占用率取较大者，≥ 80% 时提醒压缩到 140 行 / 17500 字符以下；> 100% 时必须明确报错——写入已成功，但超出部分每次加载都会被静默丢弃、末尾条目对读者已经不可见，要求立即重写
4. **假设**写入后的 `MEMORY.md` 存在超过 150 字符的索引行，**则**同一条提醒必须点名这些行的行号（最多列举 5 行，其余只报条数）
5. **假设**提醒被追加，**则**写入本身必须仍然成功、磁盘上必须保留写入后的内容（提醒是通知，不是拒绝）
6. **假设**某个写入的目标不在自动记忆目录内，**则**不得产生额外文件读取，也不得追加任何提醒
7. **假设**自动记忆已启用，**当**初始化自动记忆（会话启动、或提取 fork 开始前）时，**则**只保证目录存在，**不得**预先创建 `MEMORY.md`——索引的创建必须延后到加载时按场景 8 / 10 判定，或由代理按场景 13 自行创建
8. **假设**自动记忆目录里已有正文文件而 `MEMORY.md` 不存在，**当**加载并构建系统提示时，**则**必须按每个正文文件的 frontmatter `description` 机械生成整份索引：每行 `- [title](file.md) — <description>`，整行不超过 150 字符（超长截断；缺 `description` 时只留链接；文件名含 `[]()` 或换行、或路径非规范化时跳过该文件），总量按 140 行 / 17500 字符封顶，并原子发布
9. **假设**`MEMORY.md` 已存在，**则**自动生成不得运行，也不得以任何方式覆盖或重写既有索引
10. **假设**自动记忆目录里既没有正文文件也没有 `MEMORY.md`，**则**什么都不得创建（不写初始模板）；**假设**索引不存在或为空，**则**系统提示不得出现 `## MEMORY.md` 段
11. **假设**代理写入或编辑了自动记忆目录下除 `MEMORY.md` 以外的 `.md` 文件，**当**写入成功时，**则**必须按单文件上限 **4096 字节 / 200 行**（占用率取较大者）追加提醒：≥ 80% 时提醒「一个文件一件事、摘要化而不是追加、不同的事拆成文件」；> 100% 时必须明确报错——写入已成功，但超出部分对读者不可见，要求立即重写
12. **假设**被写入的正文文件缺 frontmatter `description`，**则**必须提示补上（`description` 是索引行与提取清单唯一的摘要来源，缺了就无法被索引化）；**假设**其 `description` 超过 300 字符，**则**必须提示压到约 150 字符
13. **假设**自动记忆已启用，**当**构建系统提示时，**则**必须说明 `MEMORY.md` 尚不存在时用 Write 创建它（初始化不再预建，索引可能不存在）；并写明单文件上限（一个文件一件事、含 frontmatter 约 4096 字节以内），使代理不必靠写侧提醒才发现越线

***

### 用户故事：记忆提示词对齐 Claude Code（优先级：P1）

作为用户，我希望代理的记忆提示词完整描述「记什么、不该记什么、怎么记、何时读、读到后怎么用」，使写入与读取遵循同一套口径，而不是主代理与后台提取子代理各说一套。

**为什么是这个优先级**：主代理提示词（`packages/agent-sdk/src/prompts/autoMemory.ts`）是自写的精简版，缺类型学、frontmatter 与索引写法、何时访问、陈旧核实、与 plan/task 的分工、目录已存在指引；提取 fork 提示词（`prompts/autoMemoryExtraction.ts`）则是照抄 Claude Code 的完整版，且 fork 复用主系统提示词（`aiManager.ts` `runAutoMemoryFork` 用主系统提示词构建请求，见「自动记忆完美 Fork 提取」场景 1）⇒ 同一个请求里同时含「要记 architecture / file paths / debugging insights」与「禁止记 architecture / file paths / debugging solutions」两条相反指令，产物格式也随之分叉（主代理写无 frontmatter 的裸 markdown，fork 写带 `name`/`description`/`type` 的文件）。

**独立测试**：构建系统提示，检查其记忆段的段落构成；触发一次提取 fork，比较其提示词与主提示词对「可保存内容」的约束是否互相矛盾。

**验收场景**：

1. **假设**自动记忆已启用，**当**构建主代理系统提示时，**则**记忆段必须包含四类记忆（`user` / `feedback` / `project` / `reference`）的类型学，每类给出 description、when_to_save、how_to_use 与示例
2. **假设**自动记忆已启用，**当**构建系统提示时，**则**记忆段必须包含「不应保存的内容」清单（可由当前项目状态推导的代码模式/约定/架构/文件路径、git 历史与谁改了什么、调试方案与修复配方、CLAUDE.md 已记录的内容、临时的会话内任务状态），并声明该清单在用户显式要求保存时同样适用（此时应先问其中「令人意外」或「不显然」的部分）
3. **假设**自动记忆已启用，**当**构建系统提示时，**则**记忆段必须包含两步保存流程：Step 1 用带 `name`/`description`/`type` frontmatter 的主题文件模板写记忆；Step 2 在 `MEMORY.md` 加一行指针；并声明 `MEMORY.md` 是索引不是记忆（每条一行、约 150 字符以内、无 frontmatter、不得把内容直接写进索引），且该行由主题文件的 frontmatter `description` 派生（`- [Title](file.md) — <description>`），而不是另写一段 hook
4. **假设**自动记忆已启用，**当**构建系统提示时，**则**记忆段必须包含「何时访问记忆」（记忆看起来相关或用户引用先前对话时；用户明确要求查看/回忆/记住时必须去读；用户表示忽略或不使用记忆时，按 `MEMORY.md` 为空处理——不应用、不引用、不比较、不提及记忆内容）
5. **假设**自动记忆已启用，**当**构建系统提示时，**则**记忆段必须包含「推荐之前先核实」的可信度规则（记忆里出现的函数/文件/flag 是写入当时的断言，可能已被改名或删除；若用户即将据此行动，须先核实存在）
6. **假设**自动记忆已启用，**当**构建系统提示时，**则**记忆段必须包含记忆与其他持久化形式的分工（需要在本会话内对齐方案时用 Plan；需要跟踪本会话的推进步骤时用 tasks；memory 只保留对未来会话仍有用的信息）
7. **假设**自动记忆已启用，**当**构建系统提示时，**则**记忆段必须说明记忆目录已存在、可直接用 Write 写入，不得先运行 `mkdir` 或检查其是否存在
8. **假设**主代理提示词与提取 fork 提示词在同一次 fork 请求中同时生效，**当**比较两者对「可保存内容」的约束时，**则**不得出现一条要求记录、另一条禁止记录的情形
9. **假设**自动记忆已启用，**则**主代理与提取 fork 写出的记忆文件必须遵循同一套 frontmatter 格式（`name` / `description` / `type`）

***

### 用户故事：自动记忆开关（优先级：P1）

作为用户，我希望关闭「自动记忆」后代理不再自动从对话中提取记忆、关闭前保存的记忆也按我的轮次设置触发，以便按需开关该后台行为。

**为什么是这个优先级**：2026-09 客户反馈插件端设置页关闭「开启自动记忆」后仍持续写入记忆——设置页开关此前只保存到各宿主自有配置（VS Code `globalState`/JetBrains 持久化/桌面 configStore），从未传到 agent 运行侧（SDK `resolveAutoMemoryEnabled` 只读 settings.json 与环境变量），实际等于开关无效。本故事把「宿主设置 → 运行中会话」的生效语义钉死，避免三端各自实现漂移。2026-09-10 用户拍板改造保存路径：这类用户偏好（自动记忆开关/频率）统一写入**用户级 `~/.wave/settings.json`** 并经 SDK 实时重载生效，**不再**经 stdio `initialize`/`updateConfig` 当 `AgentOptions` 覆盖项下发（覆盖层会永久遮蔽 settings.json 的实时值，见 [agent-config.md](./agent-config.md)「设置实时重载」与「环境变量作用域与优先级」）。

**独立测试**：在宿主（desktop/VSCE/JetBrains）设置页「个性化 → 自动记忆规则」关闭开关并保存，随后在同一会话继续对话并新建会话，验证不再产生记忆提取（自动记忆目录/`MEMORY.md` 无新增、无提取 fork 调用）；重新开启并设置频率后验证按轮次恢复提取。

**验证分层（2026-09-10 追加拍板）**：场景 1–4 的「保存即生效、下一轮起停/恢复」→ 单测（SDK gate + 频率合并与计数）+ **真 host**（`packages/desktop/tests/integration`，真 `wave --stdio` + 真 `~/.wave/settings.json`：写文件后下一轮读到新值——真 host 上以 `language` 为可观测代表，`autoMemoryEnabled`/`autoMemoryFrequency` 与 `language` 走同一条写文件 + 实时重载路径）；场景 5「关闭后不把自动记忆目录加入无批准安全区」与场景 1 的「关闭后目录写入被权限层拒绝」→ 单测覆盖白名单的幂等增删（`LiveConfigManager.syncAutoMemorySafeZone`）；**真 host 未覆盖**：驱动权限层判定需要模型发出工具调用，而真 host 的本地假模型只回文本（可观测的替代口径是同一 `~/.wave/settings.json` 已由真 host 断言写入、下一轮生效）；场景 6 的优先级链（用户级 settings.json > 环境变量 > 默认）→ 单测。**webview 保存路径**（#2115 回归网：这两个字段全 optional，链路缺字段编译期不报错，只能靠测试盯住）→ 单测 `packages/webview/tests/webview/settingsMemory.test.tsx`（`SettingsPage.onSave` 载荷 + ChatApp 把 `autoMemoryEnabled`/`autoMemoryFrequency` 放进 `updateConfiguration.configurationData`）+ **e2e** `packages/webview/e2e/desktop-settings-auto-memory-save.e2e.ts`（真 bundle：点保存后从真 `postMessage` 报文里读这两个键，并按宿主回包回填开关/轮次）；host → CLI 的 stdio 透传由对应透传测试负责（不在本组内重复）。逐条对照见 [agent-config.md](./agent-config.md) 边界说明「验证分层（单测 / e2e / 真 host）」。真 host 层挂在 main-only CI job（Linux `real-host-e2e`；Windows `check-windows` 同样跑真 host 与 desktop 单元套件）。

**验收场景**：

1. **假设**用户已在设置页把「开启自动记忆」关闭并保存，**当**该宿主后续继续对话或新建会话，**则**任何轮次结束都不得触发记忆提取（不写自动记忆文件、不发起提取 fork），无需重启会话或宿主
2. **假设**会话运行中（含正在生成）用户关闭自动记忆并保存，**当**保存完成，**则**从下一个对话轮次起停止提取；保存前已在途的提取放行完成、不截断写入
3. **假设**用户关闭自动记忆若干轮后再次开启并保存，**当**后续轮次结束，**则**恢复提取且触发计数从开启时刻重新累计（关闭期间的轮次不计入）
4. **假设**用户把触发轮次设为 N（1–100），**当**自动记忆开启时，**则**每满 N 个对话轮次触发一次提取；变更频率保存后按新值重新累计（与场景 3 同语义）
5. **假设**自动记忆关闭，**当**新建会话初始化时，**则**不再为该会话创建自动记忆目录、不把自动记忆目录加入无批准安全区（对齐运行时 gate 的同一判定来源）
6. **假设**存在多个设置来源，**当**解析自动记忆开关与频率时，**则**按 用户级 `~/.wave/settings.json`（设置页保存的落点，经 SDK 实时重载在各会话**下一轮开始时**生效）> 环境变量 `WAVE_DISABLE_AUTO_MEMORY`/`WAVE_AUTO_MEMORY_FREQUENCY` > 默认（true/1）的优先级生效；设置页保存值**写入**该文件、是三端共享的同一份用户偏好（各宿主读写同一来源、互不覆盖），**不得**经 stdio 会话参数（`AgentOptions` 覆盖层）下发——该层只保留会话级/单次覆盖语义（见 [agent-config.md](./agent-config.md)「环境变量作用域与优先级」）；改造前宿主私有存储（VSCE `globalState` / JB `wave.xml` / 桌面 `wave-desktop.json`）里的旧开关/频率值**不迁移**、不再被读取，升级后回落本场景的默认值（true/1），用户需在设置页重选一次（取舍见 [agent-config.md](./agent-config.md) 边界说明「升级用户的一次性迁移：不做」）

***

### 用户故事：自动记忆完美 Fork 提取（缓存共享）（优先级：P1）

作为用户，我希望自动记忆提取子代理复用主会话的请求前缀（相同的系统提示、相同的工具列表、相同的模型、相同的消息前缀），以便提取请求命中 prompt 缓存，降低 token 成本并加快提取速度。

**为什么是这个优先级**：当前提取子代理使用独立的系统提示、独立的工具列表和快速模型，与主会话请求前缀不一致，导致每次提取都无法命中缓存、重复处理完整历史。完美 Fork 使前缀一致，静态块命中缓存（Claude 精确前缀匹配，Qwen/GLM 向后扫描窗口）。

**独立测试**：可以启用自动记忆并配置缓存模型，完成一轮对话触发提取，检查提取请求与主会话最后请求的前缀一致，且 usage 中缓存读取 token 大于 0。

**验收场景**：

1. **假设**自动记忆已启用且触发提取，**当**提取 fork 构建请求时，**则**系统提示必须与主循环一致（相同的静态/动态分块，静态块带缓存标记）
2. **假设**提取 fork 构建请求，**当**检查工具列表和模型时，**则**必须与主循环一致（同一模型而非快速模型——模型是缓存键的一部分）
3. **假设**提取 fork 构建请求，**当**检查消息时，**则**消息前缀必须与主循环最后请求一致（含记忆注入的 system-reminder），提取提示词追加在末尾
4. **假设**配置了支持缓存的模型，**当**提取请求发出后，**则**usage 必须报告缓存读取 token（cache_read_input_tokens 或 cached_tokens）大于 0，证明前缀命中
5. **假设**提取 fork 需要阅读文件，**当**调用只读工具（Read/Grep/Glob、只读 Bash）时，**则**在本地执行并把结果回填到 fork 消息
6. **假设**提取 fork 需要写入记忆，**当**Write/Edit 路径在自动记忆目录内时，**则**允许执行；路径在目录外时，**则**拒绝并把拒绝结果回填给模型继续下一轮
7. **假设**提取 fork 调用目录外写入、Bash rm、MCP、Agent 等受限工具，**当**请求发出时，**则**必须被拒绝（与提取提示词一致）
8. **假设**提取 fork 运行期间，**则**不得写入主会话记录，不得影响主会话的消息、工具块或加载状态
9. **假设**配置了不支持缓存的模型，**当**提取请求发出时，**则**功能仍正常，仅失去缓存收益
10. **假设**主代理已读取若干文件并建立了读状态基线，**当**提取 fork 启动时，**则** fork 必须从主代理克隆这份读状态（fork 内可直接 `Edit` 已读文件而无需先 `Read`），而非使用空表
11. **假设**提取 fork 在运行中读取或写入了文件，**当** fork 结束后，**则** fork 内新建/更新的读状态不得写回主会话的读状态，也不得影响主会话的读取去重与陈旧判定
12. **假设**提取提示词要预置「已有记忆清单」，**当**构建该清单时，**则**每条必须形如 `- [<type>] <filename> (<ISO 时间戳>): <description>`（`type` 与 `description` 取自各记忆文件的 frontmatter，缺失时留空），以便提取子代理无需先花一轮 `ls` 也能判断某条内容是否已存在
13. **假设**某个记忆文件的 frontmatter 用 YAML 块标量写 `description`（`>`／`>-`／`|`／`|-` 等），**当**构建已有记忆清单时，**则**该条的 `description` 必须是折叠／保留后的真实文本（不含 `>`、`|`、`-` 这类指示符），续行**不得**被解析成独立的 frontmatter 键，也不得让描述退化成 `>-`

***

### 用户故事：自动记忆提取并发保护（优先级：P1）

作为用户，我希望自动记忆提取不会并发运行，以便多个提取不会同时写入同一记忆文件导致更新丢失。

**为什么是这个优先级**：提取是后台任务，可能跨多个对话轮次运行。上一轮提取尚未完成时新一轮对话结束会再次触发提取，两个 fork 并发写 MEMORY.md 会互相覆盖。

**独立测试**：可以模拟慢速提取，在提取进行中触发新一轮对话结束，验证不会启动第二个并发提取；验证代理清理时等待进行中的提取完成。

**验收场景**：

1. **假设**一次提取正在进行，**当**新一轮对话结束触发提取时，**则**必须跳过本次提取，不得启动并发 fork
2. **假设**提取因进行中被跳过，**当**后续轮次条件满足时，**则**仍会再次触发提取，不丢失提取机会
3. **假设**主代理在本轮成功写入了记忆目录内的文件，**当**触发提取时，**则**跳过本次提取，避免与手动写入冲突（已有行为保留；被拒绝或失败的写入未实际更新记忆，不计入手动写入，仍需触发提取）
4. **假设**代理退出或清理时仍有提取在运行，**当**清理发生时，**则**等待进行中的提取完成后再退出，不截断写入

***

### 用户故事：外部文件改动回灌（变更通知）（优先级：P1）

作为用户，我希望主代理能感知到自己在别处（后台提取 fork、外部编辑器、其他会话）被改动过的已读文件，以便它不会基于过期的文件内容继续工作。

**为什么是这个优先级**：主代理与后台自动记忆提取 fork 并发运行。fork 可以改写主代理读过的记忆文件，用户也可能在编辑器里改文件；没有回灌时主代理对磁盘上的改动完全无感，会基于陈旧内容继续编辑或断言。对齐 Claude Code 的 `changed_files` 附件机制。

**独立测试**：主代理读取某文件后由另一进程修改它，触发下一轮请求，验证注入了一条包含路径与改动摘要的通知；再次触发无改动时验证不重复注入。

**验收场景**：

1. **假设**主代理完整读取过某文件，**当**该文件在磁盘上被外部写入者改为新内容、下一轮请求构建时，**则**必须向主代理注入一条变更通知，包含文件路径与改动摘要
2. **假设**文件的 mtime 变新但内容未变（touch、编辑器原地保存等），**当**检测时，**则**不得注入变更通知
3. **假设**读状态条目来自分页读取（提供了 `offset` 或 `limit`），**当**检测变更时，**则**该条目必须被跳过（部分内容无法给出可靠变更摘要）
4. **假设**主代理自己通过 `Write`/`Edit` 写入了文件，**当**下一轮检测时，**则**不得为主代理自己刚写的文件注入变更通知
5. **假设**某文件的变更已被通知过一次且此后未再变化，**当**后续轮次构建请求时，**则**不得重复注入同一变更通知
6. **假设**一轮内检测到多个文件变更，**当**注入通知时，**则**必须受上限约束（单条摘要截断 + 文件条数与总字节上限），被省略的部分不得无界增长上下文
7. **假设**后台提取 fork 在记忆目录内改写了主代理读过的记忆文件，**当**下一轮请求构建时，**则**主代理必须收到该文件的变更通知（对齐 CC 的 `changed_files`）

***

### 用户故事：记忆陈旧提醒（读取时标注新鲜度）（优先级：P1）

作为用户，我希望代理在读取记忆文件时被明确告知该记忆有多旧，以便它不会把陈旧的 `file:line` 引用或函数名当成当前事实断言。

**为什么是这个优先级**：记忆是写入那一刻的快照，代码演进后其中的行号与符号会失效；而带着 `file:line` 引用的陈旧断言反而显得比没有引用更权威。对齐 Claude Code 的 `memdir/memoryAge.ts` + `FileReadTool` 前置注记。

**独立测试**：把一个记忆文件的 mtime 调到多天前，让主代理 Read 它，验证工具结果前置了陈旧提醒且文案包含天数；把 mtime 设为今天/昨天验证不附加；读取非记忆文件验证不附加。

**验收场景**：

1. **假设**主代理通过 Read 读取自动记忆目录内的文件，**当**该文件 mtime 距今超过 1 天（天数按 `Math.floor((现在 - mtime) / 86400000)` 计算，记为 N），**则**该工具结果内容必须前置 `<system-reminder>This memory is N days old. Memories are point-in-time observations, not live state — claims about code behavior or file:line citations may be outdated. Verify against current code before asserting as fact.</system-reminder>`
2. **假设**记忆文件的 mtime 是今天或昨天（N ≤ 1），**当**读取时，**则**不得附加陈旧提醒
3. **假设**读取的文件不在自动记忆目录内，**当**读取时，**则**不得附加陈旧提醒
4. **假设**同一文件在同一 mtime 下被多次读取，**则**提醒文本必须逐字节相同（不得内联当前时间戳或其他每轮变化的量，以免破坏前缀缓存）
5. **假设**自动记忆被关闭，**当**读取记忆目录内的文件时，**则**不得附加陈旧提醒

***

### 用户故事：嵌套记忆注入（子目录的记忆文件）（优先级：P2）

作为开发者，我希望代理在读到某个目录下的文件时，自动看到该目录（或其最近祖先）中的 `AGENTS.md` / `CLAUDE.md`，以便子目录特有的约定在真正处理该子目录的文件时才进入上下文，而不是常驻在根记忆里。

**为什么是这个优先级**：把「某子包的构建方式」「某目录的特殊约定」放进根 `AGENTS.md` 会持续占用上下文且与当前任务无关；嵌套注入让它们按需生效。对齐 Claude Code 的 `nested_memory` 附件。

**独立测试**：在 `packages/foo/AGENTS.md` 写一条独特约定，让主代理读 `packages/foo/src/a.ts`，验证该约定作为 `<system-reminder>` 被注入；再读一次同目录文件验证不重复注入。

**验收场景**：

1. **假设**主代理通过 Read 成功读取某文件，**当**该文件所在目录或其某个在项目根之下的祖先目录存在 `AGENTS.md`（不存在时回退 `CLAUDE.md`）且该文件尚未被注入过，**则**必须在下一轮请求构建前把其内容作为 `<system-reminder>Contents of <path>:\n\n<内容>` 注入；触发面只包含 Read（不含 Grep/Glob），读取失败或被去重跳过的读取不计
2. **假设**同一嵌套记忆文件已被注入过，**当**后续再次读取该目录下的文件时，**则**不得重复注入；去重依据是会话级的已注入集合，不随读取状态缓存的驱逐而失效
3. **假设**该文件同时命中路径特定记忆规则（`.wave/rules/` 的 `paths`），**则**两者都必须独立生效，嵌套注入不得取代路径规则注入
4. **假设**嵌套目录链上存在多个 `AGENTS.md`，**则**必须按「由远到近」（项目根方向 → 文件所在目录）的顺序注入，且不得只注入最内层的那一个；项目根自身的 `AGENTS.md` 不作为嵌套注入（它已作为项目记忆常驻）
5. **假设**读取的文件不在项目根之下（如用户级记忆目录、additionalDirectories 中的外部仓库），**则**不得触发任何嵌套注入
6. **假设**同一目录下 `AGENTS.md` 与 `CLAUDE.md` 同时存在，**则**只注入 `AGENTS.md`
7. **假设**会话经 EnterWorktree 进入 worktree（会话 workdir 仍是 checkout）后读取 `<worktree>/packages/a/src/x.ts`，**则**按 checkout↔worktree 映射沿目录链查找，并注入 worktree 侧的 `AGENTS.md`（worktree 有自己的副本时读它，而不是 checkout 的那份）

***

### 用户故事：模块化记忆规则（优先级：P1）

作为开发者，我希望将项目指令组织到 `.wave/rules/` 目录中的多个文件中，以便我可以维护聚焦的记忆规则文件而不是一个大的 `AGENTS.md`。

**为什么是这个优先级**：这是该功能的主要目标——启用记忆规则的模块化组织。

**独立测试**：可以在 `.wave/rules/` 中创建多个 `.md` 文件并验证它们自动作为项目记忆加载，优先级与 `AGENTS.md` 相同。

**验收场景**：

1. **假设**项目有 `.wave/rules/code-style.md` 和 `.wave/rules/testing.md`，**当**Wave 启动时，**则**两个文件都应该作为项目记忆加载
2. **假设**`.wave/rules/` 中有记忆规则，**当**Wave 执行任务时，**则**它应该遵守这些文件中的指令。无条件规则应与 AGENTS.md 一起出现在消息数组开头作为 `<system-reminder>`

***

### 用户故事：路径特定记忆规则（优先级：P1）

作为开发者，我希望使用 YAML frontmatter 将记忆规则限定到特定文件，以便记忆规则仅在 Wave 处理匹配指定模式的文件时生效。

**为什么是这个优先级**：对不同目录（如前端 vs 后端）有不同要求的大型项目至关重要。

**独立测试**：可以创建带有 `paths` 字段的记忆规则并验证它仅在代理处理匹配文件时活跃。

**验收场景**：

1. **假设**记忆规则的 `paths` 包含 `"src/api/**/*.ts"`，**当**Wave 编辑 `src/api/user.ts` 时，**则**记忆规则应该活跃。匹配的条件规则应在 Read 工具触发时作为持久化 `isMeta: true` 用户消息注入到消息数组末尾，在所有工具结果之后
2. **假设**相同的记忆规则，**当**Wave 编辑 `src/ui/Button.tsx` 时，**则**记忆规则不应该活跃
3. **假设**没有 `paths` 字段的记忆规则，**当**Wave 编辑任何文件时，**则**记忆规则应该始终活跃。无条件规则应与 AGENTS.md 一起出现在消息数组开头作为 `<system-reminder>`
4. **假设**记忆规则的 frontmatter 用 YAML 块标量或缩进续行写多行值（如 `paths: >-` 后跟缩进路径、或 `paths:` 后跟块列表），**当**Wave 加载规则时，**则** `paths`／`priority` 等字段必须解析成折叠／保留后的真实值（不含 `>`、`|`、`-` 这类指示符），续行**不得**被解析成独立的 frontmatter 键
5. **假设**规则位于 `<项目根>/.wave/rules/`，**当**Bash 命令里 `cd` 到别处之后读／改文件，**则** `paths` 的解析基准仍是该规则所在的项目根（`~/.wave/rules/` 下的用户规则以会话工作目录为基准），不随 shell 的当前目录变化
6. **假设**会话在 linked worktree 中读取 `<worktree>/packages/a/src/x.ts`，而规则来自其 checkout 的 `.wave/rules/`（会话 workdir 仍是 checkout），**则**按 checkout↔worktree 路径映射匹配 —— `src/**`、`packages/a/**` 这类前缀模式必须照常命中，不得因为相对路径带出 `.wave/worktrees/<name>/` 前缀而失配
7. **假设**读取的文件无法相对基准表达（在基准之外，或处在与基准无关的 worktree 中），**则**该条 `paths` 规则不激活；判定必须显式（相对化失败即不命中），不得依赖 glob 恰好不匹配 `../…` 而静默失配

***

### 用户故事：子目录中的记忆规则组织（优先级：P2）

作为团队负责人，我希望将记忆规则组织到 `.wave/rules/` 的子目录中并使用符号链接跨项目共享记忆规则。

**为什么是这个优先级**：改善复杂设置的可维护性并支持跨项目标准执行。

**独立测试**：可以在 `.wave/rules/` 中创建子目录和指向外部记忆规则文件的符号链接，然后验证它们被发现和加载。

**验收场景**：

1. **假设**`.wave/rules/frontend/react.md` 中有记忆规则，**当**Wave 加载记忆规则时，**则**它应该从子目录中发现并加载 `react.md`
2. **假设**符号链接 `.wave/rules/shared` 指向外部目录，**当**Wave 加载记忆规则时，**则**它应该解析符号链接并加载其中的记忆规则

***

### 用户故事：用户级模块化记忆规则（优先级：P2）

作为用户，我希望在 `~/.wave/rules/` 中定义跨所有项目生效的个人记忆规则。

**为什么是这个优先级**：允许用户全局维护个人偏好（如工作流、风格）。

**独立测试**：可以在 `~/.wave/rules/preferences.md` 中创建记忆规则并验证它在任何项目中被加载。

**验收场景**：

1. **假设**`~/.wave/rules/` 中有记忆规则，**当**Wave 在任何项目中启动时，**则**用户级记忆规则应该被加载
2. **假设**用户级和项目级记忆规则之间存在冲突，**则**项目级记忆规则应该优先

***

### 边界情况

* **缺失的存储文件**：系统应该创建 `AGENTS.md`、全局记忆文件或自动记忆目录（如果不存在）
* **重复条目**：系统应该理想地防止或警告重复的记忆条目
* **大型记忆文件**：如果记忆文件增长过大，系统应该高效处理。对于 `MEMORY.md`，只加载前 200 行且不超过 25000 字符（行数上限挡不住超长单行，实测出现过 200 行内接近 200 KB 的索引），超出时截断并按「自动记忆容量治理」场景 1 点名丢失范围；写入侧按同一故事的场景 3–6 与 11–12 拦截
* **索引缺失但有正文**：按「自动记忆容量治理」场景 7–10 处理——初始化不预建索引、有正文则按 `description` 回填、无正文则什么都不建、索引已存在则不动
* **记忆文件的新鲜度计算**：陈旧提醒按文件 mtime 与当前时间之差计算；系统时钟回拨或文件来自其他时区时，负数天数一律按「今天」处理（不产生负值提醒）
* **嵌套记忆的重复与循环**：同一个嵌套记忆文件在一次会话中只注入一次；目录链遍历必须终止（不得因符号链接或异常路径循环反复注入）
* **嵌套记忆文件不可读**：某层目录的记忆文件存在但读取失败（权限、编码）时，跳过该文件继续处理其余目录，不得因单层失败而放弃整条目录链
* **并发访问**：处理多个代理实例可能尝试写入同一记忆文件的情况
* **Git 工作树**：确保项目 ID 在同一仓库的不同工作树之间稳定
* **循环符号链接**：系统如何处理指向自身或创建循环的符号链接？（需求：检测并优雅处理）
* **无效 YAML**：系统如何处理格式错误的 frontmatter 的记忆规则文件？
* **大括号展开**：确保 `{src,lib}/**/*.ts` 等模式被正确展开
* **空的规则目录**：如果 `.wave/rules/` 缺失或为空，系统不应报错

## 假设

* 用户对当前目录和全局 `~/.wave/` 目录有写入权限
* AI 模型有足够的上下文窗口来包含组合后的记忆
* Markdown 是人类可读性和易于编辑的首选格式
* 记忆文件由主代理或提取 fork 按提示词约定写出，其 frontmatter（`name`/`description`/`type`）用于构建预置给提取 fork 的既有记忆清单，并供人按类型筛选；老文件缺 frontmatter 时按「无描述」降级处理，不影响可读性。`description` 同时是索引行的来源：提示词要求主代理照它派生 `MEMORY.md` 的行，索引缺失时的自动回填也用它
