# 记忆库格式规范（Memory Store Format v1）

> 本规范由本仓库（`dsh-engramory-recall`）定义并维护，版本 **v1**（2026-10 冻结）。
> 目标读者：想让自己的 agent 记住事情的任何人，以及想读写这份库的其它工具。
> 设计参考过社区里的「一事一文 + 索引」风格（见文末「来源与致谢」）；**规范的措辞与判定以本文件为准**。

---

## 0. 一句话

一份**纯 markdown 的记忆库**：一个索引文件 + 若干一事一文的小文件。索引每次会话整篇读，所以有硬上限；正文按需检索后只注入片段。

```
~/memory/engramory/
  MEMORY.md                  ← 索引：一行一条指针（≤200 行 / ≤25KB，超限拒绝写入）
  user-profile-and-preferences.md     ← 一篇笔记 = 一件事
  project-memory-system-choice.md
  project-by-yffto-cicd-history-2-history.md   ← 归档产物（追加式历史）
  .engramory-recall.jsonl    ← 使用账：每轮注入了什么、后来有没有被打开
  .engramory-audit.jsonl     ← 写入审计：谁在什么时候改了什么、被拒的也留痕
```

**真相源只有这些 markdown 文件。** 检索索引、向量、缓存都是可重建的投影，删掉不影响内容。

---

## 1. 索引 `MEMORY.md`

- 第一行标题：`# Memory Index`（可读性用，解析不强依赖）。
- 按 **类型分节**：`## user`、`## feedback`、`## project`、`## reference`（顺序不限，缺节合法）。
- 一行一条指针，推荐写法：

  ```markdown
  - [一行标题](文件名.md) — 一句话摘录，说明什么时候该打开它
  ```

  也接受 `- 一句话摘录 — 文件名.md` 这种没有链接的写法（解析时按文件名识别）。
- **一行只指向一篇笔记**；同一篇不要在多行里重复出现。
- 上限：**200 行 / 25 600 字节**。这是硬上限 —— 由守卫在**写入那一刻**拒绝越限的写入（不是软截断、不是自动回退）。理由：索引每次会话都要整篇读进上下文，超限部分会被静默地不再召回，软处理救不了这件事。

## 2. 一篇笔记 `<slug>.md`

- 文件名：小写字母、数字、连字符，`[a-z0-9-]+\.md`；一个主题一个文件。`MEMORY.md` 不是笔记。
- 正文以一级标题开头（`# …`），随后的正文用 `##` 分节；一篇笔记讲一件事。
- 开头是 **frontmatter**（`---` 包起来的 YAML 子集）：

  | 字段 | 必填 | 说明 |
  |---|---|---|
  | `name` | 是 | 笔记名（通常与 slug 一致） |
  | `description` | 是 | 一句话说明「什么时候该打开它」，会被检索与索引展示 |
  | `type` | 是 | `user` / `feedback` / `project` / `reference` 之一 |
  | `scope` | 否 | `global` / `local` 之类的作用域标记 |
  | `created` / `updated` | 否 | `YYYY-MM-DD` |
  | `aliases` | 否 | 别名数组，检索时当同义词走桥（中文口语与英文缩写尤其有用） |
  | `confidence` | 否 | 置信度（`高` / `中` / `低`），供人判断，不参与打分 |

- 解析宽容：BOM 会被剥掉；`CRLF` / `CR` 一律归一成 `LF`；缺 frontmatter 的笔记按「没有元数据」处理而不是报错。
- 单篇大小：**超过 4MB 的笔记会被检索跳过**（防止一篇病态文件把整库读成空）；超过 6KB 的笔记按 `##` 边界**切桶**参与检索，内容不丢。

## 3. 归档 `<slug>-history.md`

- 归档 = 把主笔记里**追加式的旧章节整段搬到**同名的 `-history.md`，**不截断、不改写**。
- 搬之前有备份；搬完可再分层；过程可回滚；执行必须显式确认（两段式）。
- 目的：让主笔记保持「小而易读」，历史仍然可检索、可 diff、可手改。

## 4. 两份账本（可删可重建）

| 文件 | 记什么 | 谁写 |
|---|---|---|
| `.engramory-recall.jsonl` | 每轮：注入了哪几条（`hit`）或为什么没注入（`skip` + 原因码），带分数、预算用量、被截断的是哪几条；后来哪条真被 `read`/`edit`（`used`） | 插件 |
| `.engramory-audit.jsonl` | 每次写入的裁决（`hit` / `deny` …）、工具、文件、时间；序列化失败的也留一行 | 插件 |

两份都**只追加**、按行滚动；删掉只损失历史统计，不影响记忆内容。

## 5. 写入纪律（给人和 agent 的约定）

1. 只记**settled 的事实**：决定、踩过的坑与解法、用户偏好。
2. 不记**会腐烂的当前状态**：版本号、tip commit、测试通过数 —— 记「去哪里读」。
3. 一个主题一个文件；就地更新，不写日期序列。
4. 绝不写入密钥、token、凭据 —— 只记「密钥在哪里」。
5. 未完成的任务只留**一条**活的 `project` 笔记。

## 6. 上限与治理一览

| 项 | 值 | 超限时 |
|---|---|---|
| 索引行数 | 200 行 | **拒绝写入**（guard，fail-closed） |
| 索引字节 | 25 600 | 同上 |
| 单篇字节 | 4 MiB | 检索时跳过该篇（其余照常） |
| 单篇切桶 | 6 KiB / 桶，最多 400 桶 | 切桶，不丢内容 |
| 笔记篇数 | 2 000 | 超出部分不参与检索 |

## 7. 版本与兼容

- **v1 冻结**：字段与上限不再变；新增可选字段视为 v1 的向后兼容扩展。
- 破坏性改动只会在 **v2**，并随附迁移脚本；本插件承诺继续**兼容读取**上述风格的历史库（含字段缺失、frontmatter 不完整、CRLF 等历史形态）。
- 别的工具想读写这份库：读的时候按上面的宽容规则；写的时候请遵守第 5 节与第 6 节的上限 —— 上限不是建议，是召回正确性的边界。

---

## 来源与致谢

「一事一文 + 索引文件 + 索引有上限」这套风格来自社区的 Engramory 协议：<https://github.com/tinqiao-oss/engramory>（MIT，`Copyright (c) 2026 Beijing Tinqiao Technology Co., Ltd.`；其 DSH 适配器是 npm 包 `dsh-engramory` v0.2.5，本仓库对齐的基线）。

**本规范文本、差异判定与这里的取值（含 200 行 / 25KB 的拒绝写入口径、6KB 切桶、账本字段、治理流程）由本仓库定义并维护**，实现见 `store.js`、`index-guard.js`、`guard.js`、`audit.js`、`archive.js`、`usage.js`，测试见 `test/store.test.mjs`、`test/index-guard.test.mjs`、`test/guard.test.mjs` 等；移植范围与许可声明见仓库根目录 `THIRD-PARTY-NOTICES.md`。

### 与上游规范的差异（不声称二进制兼容）

| 项 | 本规范 v1 | 上游 Engramory |
|---|---|---|
| 笔记文件名 | `<kind>-<slug>.md`，`type` 必须等于前缀 | `<slug>.md`，`type` 只在 frontmatter 里 |
| `created` / `updated` | 可选 | 必填 |
| 索引上限 | 200 行 / 25 600 字节，**拒绝写入**（守卫实现自研） | 软警告 150 行 / 20 KB，硬上限 200 行 / 25 KB，只拒绝增长 |
| 额外字段 | 支持 `aliases`（检索桥）、`confidence` | 明确不提供 `source` / `confidence` / `last_verified` / 过期机制 |
| 召回 | 检索后按预算注入片段（`maxPointers` / `maxChars`） | 加载索引，模型自己开文件 |
| 度量 | 注入/打开账本 + 判据（可复算） | 无召回度量 |
| 版本与迁移 | v1 冻结，破坏性改动进 v2 并附迁移脚本 | 无 `schema_version`、无升级路径 |

读别人的 Engramory 风格库时，本插件按宽容规则解析（见第 2 节）；**写**的时候请按本规范第 5、6 节。
