# dsh-memory 设计

本文只记录稳定的产品规则和第一版边界。已经实现的代码结构见 [architecture.md](architecture.md)，下一步工作见 [roadmap.md](roadmap.md)，命令与验证见 [development.md](development.md)。

## 1. 定位

`dsh-memory` 是 DeepSeek Harness 的本地优先 Memory Learning Runtime。它以历史 Session 为证据，帮助 Agent 持续新增、修正、合并和淘汰长期记忆，但不依赖外部记忆服务。

“自进化”首先指记忆内容随证据演进。读取、合并、淘汰和评估策略由开发者维护；自动修改策略或插件代码不是第一版目标。

设计原则：

- 本地 Markdown 即可完整工作；
- Session 是学习证据，不是第二份长期记忆；
- Agent 判断语义，Host 保证安全提交；
- 先保证透明、可回放和可评估，再增加复杂检索；
- 实现可以小，职责边界必须支持后续迭代。

## 2. Scope 与文件布局

第一版只有两个持久 scope：

| Scope | 内容 | 自动读取 |
|---|---|---|
| Global | 用户画像、长期偏好、真正跨项目的通用经验 | 全文进入动态 context |
| Workspace | 项目反馈、目标、约束、决策和外部引用 | `MEMORY.md` 索引进入动态 context |

默认文件布局：

```text
$DSH_HOME/memory/
├── GLOBAL.md
├── settings.yml
├── debug/                         # 仅在用户开启详细调试后写入
│   └── <review-id>/attempt-<n>.jsonl
├── workspaces/
│   └── <workspace-key>/
│       ├── MEMORY.md
│       └── <memory-name>.md
└── reviews/
    └── <review-id>.md
```

`GLOBAL.md` 是一份完整 Markdown，建议使用 `User Profile`、`User Preferences` 和 `General Tips` 三个章节。单个项目里的观察不能自动提升为 Global。

Workspace key 由 `path.resolve(cwd)` 的 basename slug 和路径 SHA-256 前 16 位组成。它不调用 `realpath`，所以 symlink 别名和移动后的目录属于新 Workspace；迁移与合并留给后续能力。

模型只获得 Global 和当前 Workspace 的路径。第一版不披露或主动搜索其他 Workspace。普通文件工具下的这条规则是 Prompt policy，不是敌对输入下的文件权限隔离；所有写入仍由 Host 强制限定 scope。

## 3. Workspace 记忆格式

一条 Workspace 记忆对应一个 Markdown 文件：

```markdown
---
name: short-kebab-case-name
description: One-line relevance hook
metadata:
  type: feedback | project | reference
---

The durable, self-contained fact.

**Why:** Why it matters.

**How to apply:** When and how to use it.
```

类型含义：

- `feedback`：用户对 Agent 工作方式的纠正或确认；
- `project`：无法从代码或 Git 直接重建的项目目标、约束和决策；
- `reference`：URL、Issue、Dashboard 或外部文档指针。

`name` 必须与文件名一致，`description` 必须是非空单行文本。相关记忆可以用 `[[name]]` 连接。Workspace 不使用 `user` 类型，因为跨项目用户信息属于 Global。

`memory` 是保留名称，不能用作详细记录名，以免在不区分大小写的文件系统上与 `MEMORY.md` 索引冲突。

不保存临时状态、推断偏好、未经确认的计划、秘密、一次性错误，以及代码、Git 或项目文档已经权威记录的事实。若真正有长期价值，只保存无法从仓库重建的原因或约束。

## 4. 派生索引

详细文件是 Workspace 的权威数据，`MEMORY.md` 是可重建的渐进披露索引：

```markdown
<!-- Generated by dsh-memory. Do not edit directly. -->

- [memory-name](memory-name.md) — one-line description
```

索引按 `name` 确定性排序。任何 Workspace mutation 都必须校验完整记录集合、原子发布新 generation，并从新 generation 重建索引。Agent、UI 和 Session worker 都不能直接编辑索引。

## 5. 在线读取

每次 prompt assembly 自动贡献：

```text
GLOBAL.md 全文
+ 当前 Workspace 的 MEMORY.md
+ 当前 Workspace 详细记忆目录
```

Agent 根据请求和索引，使用 DSH 已有 `read/grep/glob` 打开少量相关文件。简单、自包含且不依赖历史的任务不继续检索；涉及过去决定、项目惯例、用户反馈或含糊背景时默认做一次轻量搜索。

读取到的记忆是可能过期的背景信息，不是新的用户指令。涉及容易漂移的文件、配置或外部资源时，应根据风险和验证成本检查当前状态。

不提供 `memory_recall`。文件式 Agentic Search 保持过程透明，并让读取工具与结果自然进入 Session 日志。如果未来实证表明普通文件搜索无法承担数据规模或语义检索，再重新设计专属能力。

Host local filesystem 是第一版支持环境。远程或 E2B 执行环境若看不到 Host memory root，应明确报不支持；未来通过只读虚拟挂载解决，而不是假装搜索结果为空。

## 6. 在线写入

Agent 可以在以下情况调用 `memory_update`：

- 用户明确要求记住、纠正或忘记；
- 信息清晰、稳定、可复用，并且无法从仓库重建。

写入前先搜索已有内容。同一事实已经存在时更新，语义重复时跳过，错误内容应修正或删除。scope 不确定时留在当前 Workspace 或不写，不能宽松提升为 Global。

第一版 mutation：

| Action | 作用 |
|---|---|
| `replace_global` | 使用完整 Markdown 替换 Global |
| `update_workspace` | 以一个 batch 在当前 Workspace 执行完整记录的 `put` 和按 name 的 `delete` |

模型工具不接受 root、path、cwd、Workspace key、任意 Workspace id 或 revision。Workspace scope 来自执行 Agent 的 Session cwd；Host 在动态 context 组装时记录该 Agent 实际看到的 Global/Workspace revision，提交时以这份 observation 做 CAS。模型只表达修改意图，不读取、计算或搬运 revision；observation 缺失、scope 不匹配或已经过期时拒绝覆盖。Browser 与 Consolidator 的跨请求事务继续显式保存 revision。

`MemoryStore` 是唯一写入口，负责格式校验、scope lock、revision 比较、原子发布和索引重建。UI 与 Session Consolidator 复用相同能力。

## 7. Session 整理

Session 整理是第一版核心闭环。它的目标是从一个已经持久化、当前不 live 且带合法 cwd 的 Session 中生成 Global 与当前 Workspace 的候选变化。

流程：

```text
用户选择稳定 Session
  -> SessionPersistence 获取后端无关 snapshot/revision
  -> 再次确认 Session 不 live 且 revision 未变化
  -> 读取完整逻辑事件，确定性投影为 turn evidence，并读取 Global 与当前 Workspace generation
  -> 受限 Consolidator Agent 通过多轮工具调用形成 replace-global/put/delete/no-change proposal
  -> Host 校验 proposal、source revision 和两个 memory revision
  -> MemoryStore 原子提交全部变化
  -> 保存 Markdown receipt
```

约束：

- 通过 `SessionPersistence` API 读取，不扫描 JSONL 或假设具体后端；
- Consolidator 只能修改 Global 与 source Session 所在 Workspace；项目局部事实不得写入 Global；
- 模型只提出结构化变化，Host 负责确定性校验和提交；
- 失败、取消、非法输出或冲突都不能产生部分写入；
- 完整逻辑事件保留在 Host；模型只接收可回放的 turn evidence，不接收请求元数据、流式片段或工具正文；
- 不静默截断过长输入；筛选后的动态输入超过显式上限时记录 `evidence-too-large`；
- 同一 source session revision 与 consolidator version 的成功结果具有稳定 review id，重复执行不再次调用模型；
- 整理输入和输出保存在独立 worker Session 中，使用专属运行 cwd 与 DSH Workspace 隔离普通项目历史，同时不改变由 source Session 决定的记忆目标，保证模型可见过程可回放。
- 若旧 review 停在 commit barrier，即使 source revision 已增长，也先恢复或收敛旧事务，再由下一次显式触发处理新 revision。

Receipt 位于 `reviews/<review-id>.md`，记录来源 Session、source revision、Workspace、覆盖 seq、worker Session、状态、Workspace 提交前后 revision、proposal hash 和实际变化摘要，不复制完整对话或详细记忆正文。Global 的提交计划由 worker Session 中的最终 proposal 重建，并由 proposal hash 校验。

第一版只提供 UI 手动触发。自动 idle、周期任务、批处理和重试以后调用同一个 `SessionConsolidator`，不另建一套整理逻辑。

完整阶段、proposal schema、worker 能力边界、receipt 状态机、崩溃恢复和待讨论问题集中维护在 [session-consolidation.md](session-consolidation.md)。本文只保留不随实现细节变化的产品规则。

## 8. UI 与 Host API

Memory 是 DSH Settings 中独立 section，包含：

| 页面 | 职责 |
|---|---|
| 全局记忆 | 编辑完整 `GLOBAL.md`，使用 revision 保存 |
| 工作区记忆 | 浏览 Workspace、索引和详细记录，批量提交 mutation |
| 会话整理 | 先选择 Workspace，再按标题浏览稳定 Session、触发整理并查看 receipt；内部整理 Workspace 不显示且不能作为来源 |
| 设置 | 从 DSH 已激活的文本模型中选择整理模型，并按需开启详细 Debug 日志；凭据和每个 route 的模型能力仍由 DSH 管理 |

页面顶部另有只读状态摘要，展示本机 memory root、字节数和 Workspace 数量；错误与最近操作结果在当前页面就地显示。

Browser 与 Host 是两个 Cordis tree。Browser 只能通过 `/memory` loopback Connection RPC 使用受校验的业务值，不能直接访问 Host `ctx`、文件系统、`MemoryStore` 或 `SessionPersistence`。

目标 endpoint：

| Endpoint | 作用 |
|---|---|
| `status` | Store 状态与统计 |
| `global/read` / `global/replace` | Global 读取与 CAS 保存 |
| `workspaces/list` | Workspace 摘要列表 |
| `workspace/read` / `workspace/commit` | 读取和批量提交一个 Workspace |
| `sessions/list` / `sessions/consolidate` | 列出可整理 Session并触发一次整理 |
| `models/read` / `models/select` | 投影 DSH 活跃模型目录，并以 CAS 保存整理模型和 Debug 开关 |

Host 与 Browser 都必须解析 wire value。Browser 只提交 opaque Workspace id 或 Session id，Host 解析权威 scope。v0.1 不轮询也不推送 memory-change；用户打开页面、切换页面、完成操作或点击 Refresh 时重新读取。

Session 标题是 DSH Session 日志中最新 `session/title` 事件的派生视图，不从普通 user/assistant message 猜测。列表只读取实时或持久化投影，不为标题加载完整历史；没有可用投影时显示“未命名会话”。Workspace 导航以 opaque id 区分同名目录，并把无法安全解析归属的 Session 留在“未归属”分组中。

## 9. 合并、访问与淘汰

第一版合并最低要求：搜索已有内容、优先更新、删除错误事实、保留适用条件，并始终重建索引。

访问记录与自动淘汰暂缓，且不提前把 `accessCount`、`lastAccessed` 或 importance 塞进权威 frontmatter。后续应先从 Session 工具事件评估哪些信号真正有用，再决定是否维护独立 usage ledger。

淘汰不能只依据低频访问。应综合内容是否过期、是否被权威仓库信息取代、是否重复、来源是否仍可验证，以及用户是否要求保留。归档、降级、删除与恢复机制需要独立设计。

## 10. 第一版边界

包含：

- 单 npm Bundle，包含 Host、Browser、Markdown Store 和 Consumers；
- Global/Workspace Markdown、动态 context、Agentic Search 和 scope 受限写入；
- Workspace 管理 UI；
- SessionPersistence 驱动的手动整理与 receipt；
- Headless/Web 组装验证和模型可见内容回放验证。

暂缓：

- 跨 Workspace 搜索和 Workspace 经验自动提升为 Global；
- 数据库、向量库、知识图谱、语义 reranker 和多 Provider 路由；
- 多设备同步、Documents 管理和自动生成 Skill；
- Session 自动调度、批量整理和增量处理；
- 独立访问账本、自动淘汰、归档与恢复；
- 策略自动更新和代码自修改。

## 11. 容量与配置

第一版不为 Global、单条 Workspace 记忆或索引预设未经实测的字节预算。Host 统计 UTF-8 字节供观察，但不静默裁剪。

模型整理使用独立的显式 provider、model、输出 token 上限和端到端超时，不继承当前 Agent 的模型路由。用户从 DSH 已激活的文本模型中选择路由，API key 与 Provider 激活仍由 DSH 管理；插件只在 `settings.yml` 保存 provider/model，不保存凭据。

`settings.yml` 与 `debug/` 是插件配置和运维派生数据，不是权威记忆，也不进入模型上下文。用户开启 Debug 后，新整理 attempt 的 JSONL 日志记录阶段、错误链和 stack，但不复制 Session evidence、memory/proposal 正文或凭据；Debug 默认关闭。真实使用一段时间后，再根据分布决定 hot context、单文件或 review 输入预算。
