sage-mem 2.4.3 → 2.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +650 -0
- package/README.md +364 -347
- package/lib/client.js +701 -328
- package/package.json +1 -1
- package/README.zh.md +0 -639
package/README.zh.md
DELETED
|
@@ -1,639 +0,0 @@
|
|
|
1
|
-
# sage-mem · DSH 的文件式记忆插件 _(sage-mem)_
|
|
2
|
-
|
|
3
|
-
> English · [README.md](https://github.com/gezi-wen/sage-mem/blob/main/README.md)
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/sage-mem)
|
|
6
|
-
[](https://www.npmjs.com/package/sage-mem)
|
|
7
|
-
[](https://github.com/gezi-wen/sage-mem/actions/workflows/test.yml)
|
|
8
|
-
[](#兼容性)
|
|
9
|
-
[](#兼容性)
|
|
10
|
-
[](https://github.com/gezi-wen/sage-mem/blob/main/LICENSE)
|
|
11
|
-
[](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-contract.md)
|
|
12
|
-
|
|
13
|
-
**给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)装上跨会话记忆:每条记忆都是本地的一个 Markdown 文件。**
|
|
14
|
-
|
|
15
|
-
> File-based cross-session memory for DeepSeek Harness — every memory is a plain markdown file you can open, edit and delete. No database, no worker, no port.
|
|
16
|
-
|
|
17
|
-
**当前发布版本:`2.4.3`** —— 顶部 npm 徽章是动态权威。**契约基线:`1.0.0`** —— 第一个稳定版,公开承诺、能力边界与「明确做不到」的清单冻结在
|
|
18
|
-
[契约冻结](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-contract.md)。
|
|
19
|
-
|
|
20
|
-
如果它对你有用,给个 Star 是最直接的反馈。
|
|
21
|
-
|
|
22
|
-
## 目录
|
|
23
|
-
|
|
24
|
-
- [这是什么](#这是什么)
|
|
25
|
-
- [为什么是文件,不是数据库](#为什么是文件不是数据库)
|
|
26
|
-
- [核心特性](#核心特性)
|
|
27
|
-
- [快速开始](#快速开始)
|
|
28
|
-
- [界面预览](#界面预览)
|
|
29
|
-
- [记忆文件格式](#记忆文件格式)
|
|
30
|
-
- [检索与注入](#检索与注入)
|
|
31
|
-
- [写入安全](#写入安全)
|
|
32
|
-
- [自动做梦](#自动做梦)
|
|
33
|
-
- [从 Claude Code 迁移](#从-claude-code-迁移)
|
|
34
|
-
- [配置参考](#配置参考)
|
|
35
|
-
- [稳定性与版本策略](#稳定性与版本策略)
|
|
36
|
-
- [权限与隐私](#权限与隐私)
|
|
37
|
-
- [兼容性](#兼容性)
|
|
38
|
-
- [架构](#架构)
|
|
39
|
-
- [参与开发](#参与开发)
|
|
40
|
-
- [常见问题](#常见问题)
|
|
41
|
-
- [卸载](#卸载)
|
|
42
|
-
- [维护者](#维护者)
|
|
43
|
-
- [致谢](#致谢)
|
|
44
|
-
- [相关项目](#相关项目)
|
|
45
|
-
- [旧版](#旧版)
|
|
46
|
-
- [License](#license)
|
|
47
|
-
|
|
48
|
-
## 这是什么
|
|
49
|
-
|
|
50
|
-
DSH 原生没有记忆系统。每开一个新会话都是一张白纸:不记得你是谁、不记得昨天做到哪、不记得项目进行到哪一步,于是你每次都要重新交代一遍。
|
|
51
|
-
|
|
52
|
-
sage-mem 补上这一层,做法是:**把该记住的事写成一张张卡片,存在你自己电脑上**。在 DSH 把问题交给模型之前,插件按问题挑出最相关的几张、**贴在模型眼前**。
|
|
53
|
-
|
|
54
|
-
所以准确的说法不是「模型记得你」,而是「**在它开口之前,有人把相关的纸条递进了它手里**」。撤走纸条,它又是那个失忆的同事。
|
|
55
|
-
|
|
56
|
-
记忆的形态是一条记忆一个 `.md` 文件(YAML frontmatter + 正文):用编辑器就能打开、能改、能删,也能直接拿 git 管。**这份记忆始终在你自己手里,不在某个数据库里。**
|
|
57
|
-
|
|
58
|
-
## 为什么是文件,不是数据库
|
|
59
|
-
|
|
60
|
-
大多数记忆方案走「SQLite + 常驻 worker」:记忆写进数据库就**不透明**(想看看存了什么得去查表),事件全捕获又会让库**膨胀**(「用户打了个招呼」也算一条),还要额外养一个进程和一个端口。
|
|
61
|
-
|
|
62
|
-
| | 文件式(sage-mem) | 数据库 + worker |
|
|
63
|
-
|---|---|---|
|
|
64
|
-
| 看一眼存了什么 | 打开目录 | 查表 / 写脚本 |
|
|
65
|
-
| 手工改一条 | 编辑器直接改 | 走 API 或 SQL |
|
|
66
|
-
| 迁移 / 备份 | 拷目录(能跟 git 走) | 导出工具 |
|
|
67
|
-
| 额外进程与端口 | 无 | 通常有 |
|
|
68
|
-
| 存不存谁决定 | 由 agent 按规则判断 | 常见做法是无差别全捕获 |
|
|
69
|
-
|
|
70
|
-
## 核心特性
|
|
71
|
-
|
|
72
|
-
| 特性 | 说明 |
|
|
73
|
-
|---|---|
|
|
74
|
-
| **文件式存储** | 每条记忆一个 `.md`:frontmatter 存元数据,正文存内容。无数据库、无隐藏格式 |
|
|
75
|
-
| **自动注入** | 每条新问题都会重新扫一遍记忆目录,按这条问题挑出最相关的几条,在模型开口之前拼进上下文 —— 不用模型自己去翻文件 |
|
|
76
|
-
| **模型也能自己查** | 另给模型两个检索工具(`search_memory` / `read_memory`),它可以主动翻记忆、按行读原文 |
|
|
77
|
-
| **写入不被覆盖** | 你正在编辑、模型同时在改的时候,后写的不会静默盖掉先写的;冲突留成待处理记录,由你决定保留哪边 |
|
|
78
|
-
| **零额外服务** | 无 worker、无 SQLite、无端口、无常驻进程,不需要额外的 API key 或云服务(运行时唯一的依赖是普通 npm 库 `zod`) |
|
|
79
|
-
| **防膨胀** | 靠规则引导模型判断「值不值得存」:无实质内容的不存,靠读代码 / git 能推出来的不存 |
|
|
80
|
-
| **可整理** | 可选的「自动做梦」把记忆通读一遍:合并重复、修索引与断链,改动前先建回滚点 |
|
|
81
|
-
| **从 Claude Code 迁移** | 人格(`CLAUDE.md` → `AGENTS.md`)照搬;记忆按四类整理成 `.md` 即可(见[下一节](#从-claude-code-迁移)) |
|
|
82
|
-
| **自带使用指南** | 包内携带一份技能(`skills/sage-mem/`):记忆长什么样、四类怎么分、什么不该存、frontmatter 的坑、检索怎么工作、索引坏了怎么修。装上插件就够了 —— Agent 拿到说明书,不需要手工配置 |
|
|
83
|
-
|
|
84
|
-
界面上还有四块能力(都可以不用):记忆管理页、类型与标签筛选、时间线(默认是条目列表,可切星空视角)、体检与归档。
|
|
85
|
-
|
|
86
|
-
**1.8.0 起,记忆管理还能从侧栏直接进**:侧栏多一个「记忆管理」图标,点它把中央主区切到本页(页顶有「返回会话」)。
|
|
87
|
-
设置页「记忆管理 → 自动做梦 → 设置」里可以关掉这个入口(默认开,立即生效、不用重启);
|
|
88
|
-
**1.8.2 起,插件详情页也有同一个开关**(「插件 → sage-mem」那张卡),改完立即生效;
|
|
89
|
-
**关掉只影响侧栏图标,「设置 → 记忆管理」这个入口永远保留**。
|
|
90
|
-
从 1.8.2 开始,sage-mem 在插件列表里有了自己的图标(金色记忆卡片),不再是默认的通用方图标。
|
|
91
|
-
|
|
92
|
-
记忆管理页的「文件列表」头部还有一个「打开记忆文件夹」按钮:用系统文件管理器打开当前记忆目录
|
|
93
|
-
(Windows Explorer / macOS Finder / Linux 文件管理器)。host 侧起一个 detached 进程就返回,不等窗口。
|
|
94
|
-
|
|
95
|
-
记忆目录还是空的时候,空态里有一个「建立索引骨架」按钮:一键写入 `MEMORY.md` 的引导文件
|
|
96
|
-
(索引格式 + 四类记忆说明)。**已经存在 `MEMORY.md` 时只提示、不覆盖**;它不走新的 remote,
|
|
97
|
-
用的是原有的保留名读取 / 写入接口。
|
|
98
|
-
|
|
99
|
-
## 快速开始
|
|
100
|
-
|
|
101
|
-
需要 **DSH `0.1.2-rc.1` 或更高**(见[兼容性](#兼容性))、**Node.js `>= 18`**。
|
|
102
|
-
|
|
103
|
-
```bash
|
|
104
|
-
# 把 web 换成你自己的 profile 名
|
|
105
|
-
dsh plugin --profile web add sage-mem
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
`dsh plugin add` 会把包装进这个 profile 并登记它的 bundle —— 不用手改 `package.json`(手动装法见下)。
|
|
109
|
-
|
|
110
|
-
装完重启 DSH,确认装上了:
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
dsh plugin --profile web list
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
然后**开一个新会话**试三步:
|
|
117
|
-
|
|
118
|
-
1. 跟模型说「记住:我的猫叫芝麻,它喜欢晒太阳」—— 它按规则把这条写进记忆目录
|
|
119
|
-
2. 再开一个**新会话**,问「我的猫叫什么」
|
|
120
|
-
3. 它的第一个回答就该答出「芝麻」—— 记忆由插件自动检索并注入,模型没有主动去翻文件
|
|
121
|
-
|
|
122
|
-
<details>
|
|
123
|
-
<summary>手动安装(不想用 CLI,或要装本地开发版本)</summary>
|
|
124
|
-
|
|
125
|
-
在 profile 的 `package.json` 里加依赖,并把包名写进 `dsh.profile.bundles`:
|
|
126
|
-
|
|
127
|
-
```json
|
|
128
|
-
{
|
|
129
|
-
"dependencies": {
|
|
130
|
-
"sage-mem": "^1.0.0"
|
|
131
|
-
},
|
|
132
|
-
"dsh": {
|
|
133
|
-
"profile": {
|
|
134
|
-
"bundles": ["sage-mem"]
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
本地开发用 clone 出来的源码:
|
|
141
|
-
|
|
142
|
-
```json
|
|
143
|
-
{
|
|
144
|
-
"dependencies": {
|
|
145
|
-
"sage-mem": "link:../sage-mem"
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
改完跑 `pnpm install`,再重启 DSH。
|
|
151
|
-
|
|
152
|
-
</details>
|
|
153
|
-
|
|
154
|
-
### 记忆放在哪
|
|
155
|
-
|
|
156
|
-
默认在 `~/.sage-mem/memory`。想放到别处(比如放进项目仓库、跟着 git 走)就设环境变量:
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
SAGE_MEM_DIR=/path/to/your/memory
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
状态目录(配置、台账、回滚点、冲突队列)默认在记忆目录的上一层:`<记忆目录>/.sage-mem`。全部环境变量见[配置参考](#配置参考)。
|
|
163
|
-
|
|
164
|
-
## 界面预览
|
|
165
|
-
|
|
166
|
-
下面每张图都是**从仓库里的界面代码渲染出来的**(同一份 CSS、同一棵组件树;数据是演示用的假数据,不是真实记忆目录)。
|
|
167
|
-
|
|
168
|
-
1.9.0 起,记忆管理这一块跟随宿主语言(**设置 → 通用 → 语言**);记忆内容本身保持它被写下时的语言,2.1.0 起扩展到**自动做梦**(运行 / 设置 / 记录)与**自动归档**两屏;只剩时间线/星图那一屏仍是中文,等下一批 i18n。
|
|
169
|
-
|
|
170
|
-

|
|
171
|
-
|
|
172
|
-
*文件列表 —— 头部常显「体检 N 个问题」与「待重启生效」,不用点开就知道该不该处理;灰底条目是已归档,随时可以恢复,也可以点「彻底删除」真的删掉(不可恢复,要再确认一次)。*
|
|
173
|
-
|
|
174
|
-

|
|
175
|
-
|
|
176
|
-
*工具抽屉 —— 关着时只占一个按钮,展开后两项各带状态。*
|
|
177
|
-
|
|
178
|
-

|
|
179
|
-
|
|
180
|
-
*锁定 —— 重要记忆点一下就锁上:**自动归档的候选里不再出现它;界面上的归档按钮被禁用,即便绕过界面,归档实现也会以 `locked` 明确拒绝**。锁存在状态目录里、不写进记忆自己的 frontmatter —— 那会被自动整理重写掉,锁一丢它又会回到候选里。*
|
|
181
|
-
|
|
182
|
-

|
|
183
|
-
|
|
184
|
-
*体检 —— 索引悬空、漏索引、断链这类硬问题按类报红;「指向已归档」只作提示、不计入问题,也不提供一键修补(改哪条由你决定)。*
|
|
185
|
-
|
|
186
|
-

|
|
187
|
-
|
|
188
|
-
*更新提示 —— 只在**真有新版本**时出现一条横幅,写明新版本与当前版本;「看更新内容」开新标签页,「×」只是这次不看。*
|
|
189
|
-
|
|
190
|
-

|
|
191
|
-
|
|
192
|
-
*归档候选 —— 每条写清「为什么建议归档」(闲置天数 vs 该类型阈值),逐条或全选后确认才动文件。归档是把文件移进 `archive/`,**不删除**;真要删掉一条归档件,得在档案馆里单独点「彻底删除」并再确认一次。*
|
|
193
|
-
|
|
194
|
-

|
|
195
|
-
|
|
196
|
-
*时间线 —— **打开这个标签先看见的就是这一屏**:按更新时间倒序的条目列表,最近改过的在最上面。想看图,右上角「星空」自己切。*
|
|
197
|
-
|
|
198
|
-

|
|
199
|
-
|
|
200
|
-
*切到「星空」之后 —— 颜色对应类型、大小对应体积、亮度对应新鲜度,暗星是已归档的记忆,点开可看归档时间与理由。*
|
|
201
|
-
|
|
202
|
-

|
|
203
|
-
|
|
204
|
-
*自动做梦 · 设置 —— 运行 / 设置 / 记录 二级分组;五个注入参数两列排开,并写清「当前生效」与「重启后」。*
|
|
205
|
-
|
|
206
|
-

|
|
207
|
-
|
|
208
|
-
*自动归档 —— 它和自动做梦不是一件事:归档是**确定性规则**(按访问台账的闲置天数算,不调模型),所以单独一页。阈值保存即生效,不用重启。*
|
|
209
|
-
|
|
210
|
-

|
|
211
|
-
|
|
212
|
-
*「星空」视角全貌 —— 画布上只画关系与分布;悬停或点开某颗星时才显示那一条的文件名。渲染是按需重绘,画面静止时不发起任何绘制。*
|
|
213
|
-
|
|
214
|
-
## 记忆文件格式
|
|
215
|
-
|
|
216
|
-
````markdown
|
|
217
|
-
---
|
|
218
|
-
name: 可选的短名
|
|
219
|
-
description: 一句话说清这条记忆是什么(检索靠它,务必写准)
|
|
220
|
-
metadata:
|
|
221
|
-
type: user # user / feedback / project / reference
|
|
222
|
-
tags: [项目, 待办] # 可选,供列表页筛选(最多 20 个、每个 40 字符)
|
|
223
|
-
aliases: [待办, todo] # 可选:换一种说法也能被检索到
|
|
224
|
-
baseline: true # 可选:新会话第一步无条件注入(上限 8 条)
|
|
225
|
-
baselinePriority: 10 # 可选:多条 baseline 之间谁先注入(越大越前)
|
|
226
|
-
---
|
|
227
|
-
|
|
228
|
-
记忆正文。
|
|
229
|
-
````
|
|
230
|
-
|
|
231
|
-
四类记忆:
|
|
232
|
-
|
|
233
|
-
| 类型 | 存什么 |
|
|
234
|
-
|---|---|
|
|
235
|
-
| `user` | 用户是谁 |
|
|
236
|
-
| `feedback` | 工作方式指导(建议带 `**Why:**` 与 `**How to apply:**` 两行,方便判断边界) |
|
|
237
|
-
| `project` | 进行中的工作与状态 |
|
|
238
|
-
| `reference` | 外部信息在哪找的指针 |
|
|
239
|
-
|
|
240
|
-
单文件上限 512 KB,**读写两侧都校验**(读的时候先看体积,超了就不读进来)。
|
|
241
|
-
|
|
242
|
-
## 检索与注入
|
|
243
|
-
|
|
244
|
-
透明的东西值得说清楚它怎么选料,因为这直接决定注入多少 token。
|
|
245
|
-
|
|
246
|
-
**两段式检索:先摘要,再正文。** 第一段用 `description` + 文件名 + `aliases` 打分,最多取 **10 条**候选;第二段对这些候选做**正文段落检索**(BM25,中文按双字词元),把最相关的那一段注入。比的是**段落**而不是整份文件,所以一份文件最多贡献一段,长文不会靠段数多霸占候选位。
|
|
247
|
-
|
|
248
|
-
**摘要没写到的,正文能找回来。** 摘要是精准的一句话,代价是正文细节天生检索不到;摘要一条都没命中时改由正文检索兜底。正文检索只在问题**至少 4 个字符**时才跑 —— `继续` 这种两个字的问题拿去做正文匹配,命中的全是「继续」这个词本身。
|
|
249
|
-
|
|
250
|
-
**换个说法也能命中:** 打分是**词面重合**,不是语义的 —— 问「我有什么待办」而文件里写的是「代办清单」,差一个字就完全不命中。给这类记忆加一行 `aliases: [待办, todo]` 就能补上。
|
|
251
|
-
|
|
252
|
-
**注入的是命中段落,不是文件开头。** 命中段落标成 `### 项目_部署.md:40-52`,模型据此能直接跳到那几行核对来源;段落前补一句该记忆的 `description`,说明这段是从哪条记忆切出来的。`baseline` 与没命中正文的文件仍注入开头;单条正文超过字数上限(默认 1500 字符)会截断并标 `…(截断)`。
|
|
253
|
-
|
|
254
|
-
**相邻的重复问题不会重复注入**(按会话只留一个签名),连着问两遍不会又追加一遍。注意这**不是**「整个会话同题只给一次」:问 A → 问 B → 再问 A,第三次会重新注入 —— 再次要求回忆时重新给证据未必是坏事。
|
|
255
|
-
|
|
256
|
-
**单场会话有预算上限**(默认 60 KB),拆成两本账:**正文池** = 总上限 − **状态卡预留**(5%,夹在 1–4 KB)。到顶之后不是「一个字都不注」,而是正文照旧不注、缺口状态卡照发,告诉你漏了什么、下一步怎么查。**压缩或清空上下文之后两本账与卡的签名一起归零**,又能注进来。
|
|
257
|
-
|
|
258
|
-
> ⚠️ **这个上限是软的,不是硬闸门。** `baseline` 记忆被问句命中时会照注不误、不参与降级,所以单场累计正文**可能超出正文池**(评测里量到 9.6% 的超支,全部来自这类记忆)。这是有意的取舍:那类记忆的语义就是「每场都该在场」。别把它当成「到 60 KB 就一刀切断」。
|
|
259
|
-
|
|
260
|
-
**缺口状态卡只在真有缺口时说话**:有候选没读、或有候选被预算省略时才出现,说清范围、条数、最佳线索和**一个可做的动作**;一条都没命中时,只有在你**问自己的历史**、或这条任务**有检查点**时才说(否则提示会被当噪音忽略)。单张卡上限 512 字节,且按内容签名去重。
|
|
261
|
-
|
|
262
|
-
**「这条已经不作数了」会变成文件里可核对的事实(2.0.0 起)。** 记忆的 frontmatter 可以带三个可选字段:`valid_at`(说法从什么时候成立)、`invalid_at`(从什么时候起不再作数)、`superseded_by`(现行口径在哪条记忆里)。**注入侧会读它们**:`invalid_at` 已经到点的那条不再进正文(理由是 `expired_source`);一对**未经裁决的矛盾**(`kind: contradiction` 的 open 记录)**两方都不进正文**(`conflict_pending`),直到你在「记忆管理 → 冲突」页签里裁决。**写失败型的冲突不抑制**(版本过期 / 文件在别处被改过 / 合并到一半)—— 盘上那份仍然是现行内容,把它们也藏起来等于把「这次没写进去」误报成「记忆不可信」。
|
|
263
|
-
|
|
264
|
-
**抑制绝不静默。** 有矛盾待裁决时,状态卡会多一句「另有 N 条矛盾待裁决,相关记忆本轮未注入正文」(带具体条数,所以条数一变就能再发一次),面板头部也有一个常显徽标。**工具不受影响**:`search_memory` / `read_memory` 照旧能搜到、读到失效记忆,结果里会标「已失效」并带 `invalidAt` / `supersededBy` —— 问历史时旧口径是证据,不是噪音。想临时回到旧行为,把 `SAGE_MEM_VALIDITY` 设成 `0`(只关这一层过滤,本版其它修复都还在)。
|
|
265
|
-
|
|
266
|
-
**摘要侧也有相关性门槛。** 中文里「怎么」「什么」这类词元在大量 `description` 里都有,一句无关的「今天天气怎么样」可能靠一个「怎么」命中十几份记忆。门槛按**语料自身的 IDF 尺度**归一,无关问题的候选数因此降到约七分之一;被清掉的主要是本就该落选的弱摘要候选。
|
|
267
|
-
|
|
268
|
-
离线评测集上的对照(门槛前后):**Recall@5 84.1% → 87.0%、误注入(注入了与问题无关的记忆)14.1% → 5.6%**。
|
|
269
|
-
|
|
270
|
-
> ⚠️ **这些是测量,不是承诺**:可迁移的是**相对改进**,绝对水平**不可外推**到真实记忆目录 —— 那 30 份语料的摘要本来就是照着「能被检索到」手写的,真实记忆的摘要往往写得很随意甚至缺失。方法与局限见[评测报告](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-eval-report.md)。
|
|
271
|
-
|
|
272
|
-
**意图识别绝不在三类显式请求上抑制重复**:「上次决定用 A 对吧」(核实)、「不用 A 了」(更正)、「之前为什么没选 A」(问依据)必须给原文;判不出来就不判,不猜。
|
|
273
|
-
|
|
274
|
-
### 模型也能自己翻:两个工具
|
|
275
|
-
|
|
276
|
-
检索的触发权分两半:一半在插件(自动注入,它猜你想看什么),一半在模型(下面这两个工具,它主动查)。
|
|
277
|
-
|
|
278
|
-
| 工具 | 用途 |
|
|
279
|
-
|---|---|
|
|
280
|
-
| `search_memory(query, scope?, limit?, cursor?)` | 先比摘要、再按段落查正文;返回候选、命中片段、行号,以及**覆盖情况**(扫了几份、几段因相关性不够被滤掉、有没有下一页) |
|
|
281
|
-
| `read_memory(name, startLine?, startByte?, maxBytes?, revision?)` | 按行分页读正文。带上你上次读到的 `revision`,内容被人改过时会回 `revision_changed`,**并且不返回内容** —— 否则你会拿着过期行号读错东西 |
|
|
282
|
-
|
|
283
|
-
两个工具走的是**同一条**安全读路径(文件名检查 → 符号链接与保留名身份检查 → 体积闸门),没有为工具另开一条绕过闸门的入口。它们通过宿主提供的工具服务注册 —— **不是硬依赖**:宿主不提供这个服务时,插件照常工作,只是状态卡换一套「直接读记忆目录」的说法。
|
|
284
|
-
|
|
285
|
-
### 长任务的接力棒:任务检查点
|
|
286
|
-
|
|
287
|
-
宿主什么时候压缩上下文,插件控制不了 —— 所以**不承诺「压缩前保存」**。能做的是:如果 `<状态目录>/tasks/<任务 id>/WORKING.md` 存在,注入末尾会给一条短句柄,说清任务、revision 与去哪读。**压缩后账本归零会连去重一起清掉**,句柄因此重新出现 —— 这就是恢复路径。
|
|
288
|
-
|
|
289
|
-
任务 id 取 `SAGE_MEM_TASK_ID`(给跨会话的同一条任务用),否则用会话 id。**绝不用你的问题原文去决定读哪个目录。** 检查点里「已完成」的条目如果没有任何证据标记(路径、命令、测试结果),解析时会如实报出来 —— 格式完美、内容错误的交接材料仍然是错的。
|
|
290
|
-
|
|
291
|
-
## 写入安全
|
|
292
|
-
|
|
293
|
-
记忆是**文件**,所以「你正在编辑」和「模型正在改」完全可能同时发生。默认行为是**后写的赢、先写的悄悄消失** —— 那是最糟的一种,因为你不会发现。
|
|
294
|
-
|
|
295
|
-
sage-mem 给记忆目录的写入加了**版本守卫**:写入前记下当前版本,写入时要求版本没变过。变了就**明确拒绝**,而不是盖掉 —— 把「静默的坏结果」变成「显式的失败」。
|
|
296
|
-
|
|
297
|
-
守卫分两条路,覆盖面**不一样**,说清楚:
|
|
298
|
-
|
|
299
|
-
| 谁在写 | 怎么保护 | 被拒时 |
|
|
300
|
-
|---|---|---|
|
|
301
|
-
| 模型用**内置 `write` / `edit`** 改记忆 | 插件在前置事件里给出「当前版本」,宿主据此拒绝过期覆盖 | 宿主直接拒绝(报 `FS_STALE_VERSION`)。**这条路的拒绝不留冲突记录** —— 写门只提供版本,不做拒绝动作 |
|
|
302
|
-
| **插件自己**写(自动做梦的整理) | 调用方在写入时带上读到的版本 | 落一条待处理记录 `<状态目录>/conflicts/<id>.json`,里面留着**盘上那一版**和**想要写的那一版** —— 只有一边等于没得判 |
|
|
303
|
-
|
|
304
|
-
- **冲突不会自动合并**,保留哪边由你决定(记录是 JSON,需要时直接打开看)。静默重试和静默放弃都是在替你决定。
|
|
305
|
-
- **2.0.0 起注入侧会读这个队列**:只有 `kind: contradiction` 且 `status: open` 的**矛盾型**两方被抑制;另外三种写失败型(`stale_version` / `external_change` / `unresolved_merge`)照常注入 —— 盘上那份才是现行内容。裁决入口与抑制是同一次交付,不再有「只接抑制、没有入口」的状态。
|
|
306
|
-
- **裁决在「记忆管理 → 冲突」页签**(第 6 个页签,面板头部另有常显徽标):逐条看两份正文,动作是「标此条失效」「撤销失效」「删除记录」,危险动作两段式确认。「标失效」只往记忆 frontmatter 加一行 `invalid_at`(可选再写 `superseded_by`)—— **文件留在原位、正文一个字节不动、不改 `updated`**;写盘幂等(连点两次不重写)、可撤销,失败时明确返回错误、记录保持待裁决。
|
|
307
|
-
- **冲突记录字段只增不改**:2.0.0 在尾部追加 `otherFile` / `decision` / `decidedAt` / `appliedVersion` / `invalidatedFile` 五个,记录版本仍是 1;1.9.x 读端忽略不认识的键,双向都不用迁移。
|
|
308
|
-
- **手工编辑不在看守范围**:直接改文件也生效(删掉 `invalid_at` 就恢复注入),插件不拦手改 —— 这是文件式记忆的取舍,见能力边界表 C11。
|
|
309
|
-
- **同一件事不会越积越多**:一次失败如果与队列里某条未裁决记录逐字段相同(同一文件、两份正文、两个版本),不再追加副本。
|
|
310
|
-
- **宿主的文件沙箱**:自动做梦在**会话外**运行,拿不到会话工作区。宿主用 `workspace-write` 时,插件给每次写入带上**收窄到记忆目录**的逐次沙箱策略(比回退根更窄);宿主是 `read-only` 时不传策略、写入照旧被拒,且这一趟会被记成**失败**(不是「成功,没改什么」)。
|
|
311
|
-
- 已知边界:**在 shell 里改记忆不走这条路**。写门守的是「经过工具的记忆写入」,不是「所有对文件的修改」;`pwsh` 里的重定向之类认不出来。认不准就不认,免得把「看不到」误记成事实。
|
|
312
|
-
|
|
313
|
-
### 访问台账:三种「用过」分开记
|
|
314
|
-
|
|
315
|
-
`<状态目录>/access.json` 记三件事:
|
|
316
|
-
|
|
317
|
-
| 字段 | 含义 |
|
|
318
|
-
|---|---|
|
|
319
|
-
| `injected` | 插件**被动**注入了这份记忆 |
|
|
320
|
-
| `read` | 模型**主动**读了它 |
|
|
321
|
-
| `use` | 明确被采用的次数(自报) |
|
|
322
|
-
|
|
323
|
-
分开记是因为这三件事的含义完全不同:**被注入**只说明插件觉得相关,**被主动读**只说明模型选了它,都不等于内容有用、更不等于内容正确。混成一个「热度」就再也分不开了。
|
|
324
|
-
|
|
325
|
-
**这张台账目前只用于诊断与自动归档阈值,不参与检索排序。** 观测有已知盲区:在 shell 里读记忆认不出来,那就不认。
|
|
326
|
-
|
|
327
|
-
### 记忆的新鲜度与引用纪律
|
|
328
|
-
|
|
329
|
-
每条记忆的标题带新鲜度:`(保存于 N 天前)`,**优先读 frontmatter 的 `updated:`,缺失才回落文件 mtime** —— 手写的时间戳是权威,文件系统时间会被复制 / 备份还原 / checkout 整批改掉。
|
|
330
|
-
|
|
331
|
-
理由是:陈旧记忆被当事实引用时,「引用」这个动作本身会让它显得更权威而不是更可疑。所以注入文本开头还带一节**引用纪律**(提到路径先确认存在、提到函数先 grep、要动手先核实当前代码),而且它**独立成节**——它约束的是这一批内容怎么用,压成一条列表项容易和内容混在一起被跳过。
|
|
332
|
-
|
|
333
|
-
**`baseline: true` 的记忆每场会话第一步无条件注入**,最多 8 条 —— 给「你是谁」「待办清单」这类每次开场都该在场的记忆用。⚠️ **「不吃段落替换」不等于整份**:它同样受单条字数上限(默认 1500 字符)截断,**关键内容若落在尾部会看不到**。baseline 适合放短摘要,完整正文留给工具去读。多条 baseline 之间的先后由 `baselinePriority` 决定(数字越大越前,缺省 0),不按文件名排。
|
|
334
|
-
|
|
335
|
-
记忆目录读不到、权限错 → **降级为不注入并写日志**,不会静默失效。frontmatter 坏掉则**宽容处理**:解析不出来的字段回落默认(`type` 缺失按 `reference` 记),文件本身照样能被读到 —— 不会因为一行写错就让整条记忆消失。
|
|
336
|
-
|
|
337
|
-
## 自动做梦
|
|
338
|
-
|
|
339
|
-
可选的记忆整理功能(标识 `autodream`):把记忆目录通读一遍 —— 合并重复的、删掉被推翻的、修索引与断链、把相对日期换成绝对日期,让下一次会话能快速对上位置。
|
|
340
|
-
|
|
341
|
-
**默认关闭**,在设置页「记忆管理 → 自动做梦」里开。三件事各自可选:
|
|
342
|
-
|
|
343
|
-
| 选项 | 取值 | 说明 |
|
|
344
|
-
|---|---|---|
|
|
345
|
-
| 触发 | 只手动 / 满足条件自动跑 | 自动模式走三级门控,**最便宜的先查**:时间门(距上次 ≥ N 小时,一次读写)→ 会话门(期间 ≥ M 个会话有更新,一次目录扫描)→ 锁(防并发)。中间夹一层 10 分钟扫描节流,免得「时间门过了、会话门没过」时每轮白扫 |
|
|
346
|
-
| 改动方式 | 只出报告 / 直接改写 | 「只出报告」模式下**写工具根本不会挂给模型** —— 不是靠一句「请不要写」约束,是它没有手 |
|
|
347
|
-
| 输入源 | 仅记忆目录 / 记忆 + 会话记录 | 后者会解压会话记录(zstd)做定向关键词搜索;代价是私密对话会进入模型上下文,token 成本也更高 |
|
|
348
|
-
|
|
349
|
-
自动触发走插件自己的定时器(默认 30 分钟查一次门控)。整理**刻意不走会话** —— 工具集由插件给、步数有上限、不写进你的会话记录。
|
|
350
|
-
|
|
351
|
-
### 整理用的模型可以单独指定
|
|
352
|
-
|
|
353
|
-
整理是后台批量任务,跟聊天用的不一定是同一个模型。设置页「高级设置 → 模型」可以从**当前已配置的模型路线**里挑一条(选项与「模型选择」里的目录同源)。
|
|
354
|
-
|
|
355
|
-
- 选了就**只认它**:路线在宿主里不存在时**开工前就报错**(只比对宿主已注册的目录、不发任何请求),不会跑到一半才失败,也**不会静默回落到默认模型**。
|
|
356
|
-
- 面板下方始终显示**实际生效的是哪条**;配置里那条路线万一从目录里消失了,下拉框也会把它显示出来而不是悄悄跳回默认。
|
|
357
|
-
- 拿不到模型目录时(宿主差异),退化成手填 `provider` 与 `model`。
|
|
358
|
-
|
|
359
|
-
### 报告写不完的时候
|
|
360
|
-
|
|
361
|
-
整理报告由模型一口气写完,而单次输出有长度上限。**上限、重试、残篇标注**三件事都有明确行为:
|
|
362
|
-
|
|
363
|
-
- **单次输出上限可配**:设置页「自动做梦 → 设置 → 高级设置 → 单次输出上限」(默认 32768 token,范围 1024–131072)。思考型模型的思考与正文**共用**这份额度;收尾那轮思考吃满时正文会一个字不剩 —— 这类报告先把上限调大再跑。
|
|
364
|
-
- **零正文先重试**:一轮额度用尽却**没有任何正文**(只剩思考)时,先要求它「直接输出正文、不要重新分析」重试;最多 2 次,仍为零才标残篇。撞上限但**有正文**的,按「从中断处继续写」续写(最多 3 轮)。
|
|
365
|
-
- **残篇会被指出来**:报告首屏、整理声明、机读记录与设置页「上次运行」那一行都会标「报告被截断(残篇)」—— 不会再把半篇报成「成功」。收尾那一步没写文字时,报告写「模型没有给出文字说明」,**不拿中途旁白顶替**。
|
|
366
|
-
|
|
367
|
-
### 回滚点
|
|
368
|
-
|
|
369
|
-
**只要这次是「直接改写」,开工前就先建一个回滚点**(记忆目录顶层 `.md` 全量快照,每条带 sha256),跑完之后能在设置页看到它、也能一键退回去。
|
|
370
|
-
|
|
371
|
-
- 回滚点与这次运行**绑定**:报告里写着 runId,回滚点目录同名,两边对得上。
|
|
372
|
-
- **回滚本身也能退**:动手之前会先把「现在」再存一份**保护快照**(不参与普通快照的轮转淘汰)。
|
|
373
|
-
- **范围可选**:只恢复这次运行动过的文件,或整目录回到该时点。
|
|
374
|
-
- **全程不删文件**:回滚中「不该存在」的文件一律移进 `archive/`,人工可捞回;有同名冲突就跳过并计数,绝不覆盖。
|
|
375
|
-
- 每次回滚都留痕:报告目录里写一份回滚报告,并在状态目录里追加一条记录。
|
|
376
|
-
|
|
377
|
-
### 整理声明:改了什么、为什么
|
|
378
|
-
|
|
379
|
-
每一次整理都留下一份**声明**,两种形态:人读的 `memory/autodream/<runId>.md`(报告里内嵌)、机读的 `.sage-mem/autodream/runs/<runId>/manifest.json`。
|
|
380
|
-
|
|
381
|
-
逐条记录:**文件 / 操作(新建·改写·归档)/ 缘由 / 字节变化**,外加运行前后的结构审计对比。
|
|
382
|
-
|
|
383
|
-
**2.4.0 起,声明还会逐条列出「被删掉的事实」**:改写与归档都可能让原来写着的几条事实不再存在,而结构审计(断链 / 格式 / 索引)**看不见事实丢失** —— 它能报的每一项都可能是绿的。所以每次删到东西,声明里单开一节,把**不再存在于活动记忆里的原文**逐条列出来(带原文行号,方便回快照对),改写还会写明行数变化。自动归档策略收走的文件同样列。事实由宿主拿「写之前的正文」与「写进去的新正文」对比算出,**不靠模型自述** —— 模型可以漏报,diff 不会。一处真实教训:2026-11-05 那趟整理把一条仍然生效的「到期 2027-10-03、不自动续费」压掉了,自报「审计 2 问题 → 0 问题」全绿。
|
|
384
|
-
|
|
385
|
-
- **`reason` 应当填写。** `write_memory` 不带 `reason` **不会被拒绝** —— 改动照样生效,但声明里会把它标成「未自述」并附上宿主能追溯到的信息(第几轮 / 什么操作 / 字节变化)。理由:为了一句解释而丢掉一次本来正确的修正,代价更大。
|
|
386
|
-
- **`archive_memory` 的 `reason` 仍必填**,缺了直接拒绝;被拒的动作单列一节「未能落地的改动」,不会静默消失。
|
|
387
|
-
|
|
388
|
-
### 三道安全网
|
|
389
|
-
|
|
390
|
-
1. **安全边界在工具集上,不在提示词里** —— 自动做梦时宿主只把只读工具交给模型,改写模式才多给改写工具;而且**没有「删除」这个能力**,归档是把文件移进 `archive/`,可以人工捞回。
|
|
391
|
-
2. **改写前自动建回滚点**,落在 `<记忆目录的上一层>/.sage-mem/autodream/snapshots/<runId>/`,保留最近 N 份。理由是记忆目录**不在任何版本控制下**、删除没有回收站,「可回滚」得自己造。
|
|
392
|
-
3. **跑完自动审计** —— 索引悬空、漏索引、`type` 与文件名前缀不一致、CRLF、双链断链、frontmatter 里被 YAML 当注释吞掉的「空格 + #」、无 frontmatter、归档中途残留的临时文件,外加「读不动」单列一类,结果与前后对比写进报告。它**只报不改**。索引 `MEMORY.md` 是清单而不是一条记忆,因此**不参与**「type 与前缀」和「无 frontmatter」这两项(1.9.1 起)。
|
|
393
|
-
|
|
394
|
-
### 改名说明
|
|
395
|
-
|
|
396
|
-
本功能原名「做梦」(标识 `dream`),0.7.0 起叫 **自动做梦(autodream)**。旧数据不会被丢:老的配置 `.sage-mem/dream.json` 会被读出来并迁移到 `.sage-mem/autodream.json`(**旧文件保留不动**),老报告 `memory/dream/` 仍在面板里列出并标「旧版」,老快照也仍可回滚。
|
|
397
|
-
|
|
398
|
-
## 从 Claude Code 迁移
|
|
399
|
-
|
|
400
|
-
如果你已经在 Claude Code(CC)里养了一个 agent,sage-mem 是「CC → DSH」迁移方案的一部分。**我们自己的记忆就是这么迁过来的** —— 当时两边的记忆都已经是 Markdown 文件,直接拷过来就能用。
|
|
401
|
-
|
|
402
|
-
两样东西的搬法**不一样**,说清楚:
|
|
403
|
-
|
|
404
|
-
| 搬什么 | 从 | 到 | 怎么搬 |
|
|
405
|
-
|---|---|---|---|
|
|
406
|
-
| **人格** | CC 的 `CLAUDE.md` | DSH 的 `AGENTS.md` | 都是纯文本、DSH 原生加载,内容照搬即可 |
|
|
407
|
-
| **记忆** | CC 里沉淀的跨会话语料 | sage-mem 的 `memory/` 目录 | 一条记忆一个 `.md`,带 frontmatter,按 `user` / `feedback` / `project` / `reference` 四类归位 |
|
|
408
|
-
|
|
409
|
-
⚠️ **前提是形状对得上。** 四类划分与 frontmatter 是 sage-mem 自己的约定:如果你在 CC 里的记忆本来就是「一条一个 Markdown + frontmatter」(我们当时是这种),拷过来即可;**如果不是**(比如都堆在一个 `CLAUDE.md` 里),需要先整理一遍 —— 这活儿正好可以交给一个 agent 做:让它读旧语料,按四类落成文件。
|
|
410
|
-
|
|
411
|
-
## 配置参考
|
|
412
|
-
|
|
413
|
-
### 环境变量
|
|
414
|
-
|
|
415
|
-
| 变量 | 默认 | 说明 |
|
|
416
|
-
|---|---|---|
|
|
417
|
-
| `SAGE_MEM_DIR` | `~/.sage-mem/memory` | 记忆目录 |
|
|
418
|
-
| `SAGE_MEM_STATE_DIR` | `<记忆目录的上一层>/.sage-mem` | 状态目录(配置、台账、锁、冲突队列、回滚点) |
|
|
419
|
-
| `SAGE_MEM_MAX_SESSION_BYTES` | `61440`(60 KiB,4096–524288) | 单场会话注入总量上限 |
|
|
420
|
-
| `SAGE_MEM_MAX_RESULTS` | `10`(1–30) | 每轮注入的候选条数上限 |
|
|
421
|
-
| `SAGE_MEM_MAX_CHARS` | `1500`(200–20000) | 单条记忆注入的字符上限 |
|
|
422
|
-
| `SAGE_MEM_MAX_BASELINE` | `8`(1–50) | `baseline` 名额 |
|
|
423
|
-
| `SAGE_MEM_STALE_DAYS` | `1`(0–365) | 标题标「保存于 N 天前」的门槛;0 = 每条都标 |
|
|
424
|
-
| `SAGE_MEM_TASK_ID` | 空 | 任务检查点的作用域;空则回落会话 id |
|
|
425
|
-
| `SAGE_MEM_DEBUG` | 关 | 调试日志;打开后面板「工具 ▾ → 注入调试」显示最近 8 次注入的账(只读、不落盘;同时列出被已失效标注 / 未裁决矛盾挡在正文外的记忆与理由,矛盾还带冲突 id) |
|
|
426
|
-
| `SAGE_MEM_VALIDITY` | 开 | 失效过滤与矛盾抑制的总开关。**只认 `0` / `false` / `off` 为关闭**(空值、拼错都按开启);关掉后回到 1.9.x 的注入行为 |
|
|
427
|
-
|
|
428
|
-
取值语义:**越界夹到边界,非法值(含纯空格)回落默认。**
|
|
429
|
-
|
|
430
|
-
**状态根的范围与迁移(2.1.13 起写清)**:`SAGE_MEM_STATE_DIR` 是**唯一的状态根** —— 设置、自动做梦配置与报告、锁表、访问台账、冲突队列、任务检查点、回滚快照、台账都在它下面,插件内所有模块(含自动做梦引擎与锁表)都从同一个值解析,不再各自按记忆目录的父目录去拼。**更改状态根不会自动迁移旧数据**:新根优先、旧的 `.sage-mem` 原样保留但不再读取,启动时会打印一行提示点名旧根里还有哪些数据;要保留请手动移动。
|
|
431
|
-
|
|
432
|
-
### 设置页里能改的
|
|
433
|
-
|
|
434
|
-
面板可改上面五个上限(写入 `<状态目录>/settings.json`,优先级 **设置页 > 环境变量 > 内置默认**),以及保留名(受保护、设置页不许写删的文件名,写入 `<状态目录>/reserved.json`)。**部分项要重启 DSH 才生效**,面板上会写清「当前生效 / 重启后」。
|
|
435
|
-
|
|
436
|
-
### 状态目录里的文件
|
|
437
|
-
|
|
438
|
-
| 文件 | 用途 |
|
|
439
|
-
|---|---|
|
|
440
|
-
| `access.json` | 访问台账(`injected` / `read` / `use`) |
|
|
441
|
-
| `conflicts/<id>.json` | 待处理的冲突:写入失败,或一对等待裁决的矛盾 |
|
|
442
|
-
| `tasks/<任务 id>/WORKING.md` | 任务检查点 |
|
|
443
|
-
| `settings.json` / `reserved.json` / `locked.json` | 设置、保留名、锁定 |
|
|
444
|
-
| `autodream.json`、`autodream/` | 自动做梦的配置、快照、运行记录 |
|
|
445
|
-
| `dream.json` | 旧版名字,只在迁移时读一次 |
|
|
446
|
-
|
|
447
|
-
除 `access.json`、`conflicts/`、`tasks/` 之外,其余是实现内部状态,**格式可能在小版本里变**,别依赖。
|
|
448
|
-
|
|
449
|
-
## 稳定性与版本策略
|
|
450
|
-
|
|
451
|
-
**1.0.0 是这个项目的稳定线。** 从这一版起,「你可以依赖什么」不再是一段自我评价,而是**可被机器推翻的清单**:
|
|
452
|
-
|
|
453
|
-
- **[契约冻结](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-contract.md)** —— 公开面逐条写清定性,只有四种措辞:**保证**(有断言支撑,回归会红)/ **尽力**(设计如此、有实测依据,但没有会红的回归断言)/ **明确做不到**(能力边界,是取舍不是缺陷)/ **未验证**(我们不知道)。
|
|
454
|
-
- **每条「保证」都指得出一条具体断言**(某个测试套件里的一项),并在每次 `npm test` 里由 `test/contract-binding-test.mjs` 核对「这条断言真的跑过」。指不出断言的承诺,只能标「尽力」或「未验证」。
|
|
455
|
-
- **[能力边界表](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-boundaries.md)** 与契约**逐行交叉引用**,由 `test/doc-consistency-test.mjs` 核对两边定性相同。
|
|
456
|
-
|
|
457
|
-
**版本号怎么涨**(语义化版本):
|
|
458
|
-
|
|
459
|
-
| 改动 | 版本 |
|
|
460
|
-
|---|---|
|
|
461
|
-
| 改契约里标「保证」的任何一条、改工具名或必填参数、去掉老格式台账的读兼容、改注入段名 | **大版本** |
|
|
462
|
-
| 改措辞、改排序细节、改内部状态文件格式、改面板、加新的环境变量、改性能 | 小版本 / 补丁 |
|
|
463
|
-
|
|
464
|
-
**从 0.9.x 升到 1.0.0**:**没有破坏性变更** —— 工具名、参数、环境变量名与默认值、注入段名、台账格式一个都没改。唯一的**行为变更**是:整数型环境变量填**纯空格**时,现在回落默认值(此前会被当成 0 而夹到下限)。逐条清单见 [CHANGELOG](https://github.com/gezi-wen/sage-mem/blob/main/CHANGELOG.md) 的 1.0.0 一节。
|
|
465
|
-
|
|
466
|
-
**审查材料**(给愿意深挖的人):
|
|
467
|
-
|
|
468
|
-
| 文件 | 内容 |
|
|
469
|
-
|---|---|
|
|
470
|
-
| [1.0.0-contract.md](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-contract.md) | 公开承诺逐条定性 + 每条保证指向的断言 |
|
|
471
|
-
| [1.0.0-boundaries.md](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-boundaries.md) | 38 条能力逐条定性(含依据与验证手段) |
|
|
472
|
-
| [1.0.0-eval-report.md](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-eval-report.md) | 检索质量与预算的量化结果,以及这些数字**测不到**什么 |
|
|
473
|
-
| [review-findings.md](https://github.com/gezi-wen/sage-mem/blob/main/docs/review-findings.md) | 外部复审发现的永久账:每条发现、处置、**由哪条断言看着** |
|
|
474
|
-
| [release-checklist.md](https://github.com/gezi-wen/sage-mem/blob/main/docs/release-checklist.md) | 发版纪律(脱敏四查、评测退出码、探针、四锚点) |
|
|
475
|
-
|
|
476
|
-
## 权限与隐私
|
|
477
|
-
|
|
478
|
-
sage-mem 只做三件事,全都在你本机:
|
|
479
|
-
|
|
480
|
-
| 它做什么 | 具体范围 |
|
|
481
|
-
|---|---|
|
|
482
|
-
| **读记忆** | 只读 `SAGE_MEM_DIR`(默认 `~/.sage-mem/memory`)下的顶层 `*.md`,单文件上限 512 KB |
|
|
483
|
-
| **写记忆** | 经模型的文件工具或设置页写同一个目录;`MEMORY.md`、`session-log.md` 等保留名受保护,设置页碰不到 |
|
|
484
|
-
| **注入** | 把选中的记忆作为上下文交给模型(最多 10 条相关 + 8 条 `baseline`,单场会话累计 60 KB —— ⚠️ 这个上限是**软的**,见[检索与注入](#检索与注入)) |
|
|
485
|
-
|
|
486
|
-
**它不做什么**:不开端口、不起常驻进程、不需要任何 API key;**不做事件全捕获**(存不存由模型按规则判断,不是「你说了什么都记」)。
|
|
487
|
-
|
|
488
|
-
**两处出网 / 越界读,说清楚**:
|
|
489
|
-
|
|
490
|
-
1. **版本检查**:打开「记忆管理」页时会静默请求一次
|
|
491
|
-
`https://api.github.com/repos/gezi-wen/sage-mem/releases/latest`(10 秒超时,失败一律不提示)。
|
|
492
|
-
请求里只有插件自己的 `User-Agent`;**不含任何记忆内容、文件路径或使用统计**。环境里有
|
|
493
|
-
`GITHUB_TOKEN` / `GH_TOKEN` 时会带上它(把 GitHub 未认证的 60 次/小时提到 5000)。这是**唯一**的出网请求。
|
|
494
|
-
2. **会话记录**:「自动做梦」的输入源选「记忆 + 会话记录」时,它会额外**读** `$DSH_HOME/sessions`
|
|
495
|
-
下的会话记录做定向搜索 —— 那会解压你的对话原文(含私密内容)并送进模型上下文。**默认关闭**。
|
|
496
|
-
|
|
497
|
-
**唯一会调用模型的功能是「自动做梦」,默认关闭。** 它的模型默认跟随当前会话默认模型,也可以在设置页单独指定一条 —— 指定了就只认它,不会静默回落。
|
|
498
|
-
|
|
499
|
-
记忆就是那个目录里的文件,随时可以打开确认 —— 这也是选文件式的原因。
|
|
500
|
-
|
|
501
|
-
**记忆目录的信任边界**:能写记忆目录的人就能改变注入给模型的内容 —— 这属于**信任模型内部**,不是插件的漏洞。读路径在检查时会拒绝符号链接、保留名别名与超过 512 KB 的文件,读取时还会复核文件身份(检查之后被换掉的文件会被拒绝,而不是照读);但它防不住一个已经被允许修改你记忆的人。把目录权限收到你自己的账号。
|
|
502
|
-
|
|
503
|
-
## 兼容性
|
|
504
|
-
|
|
505
|
-
sage-mem 是纯 DSH 插件,用 `package.json` 的 **`engines.dsh`** 声明宿主要求:`>=0.1.2-rc.1` —— **单边开区间、不设上界**,所以任何未来的 DSH 版本都不会被这道声明挡住。`engines.node` 要求 `>= 18`。
|
|
506
|
-
|
|
507
|
-
**已在 DSH Go(Android 客户端)上验证可用**:从 App 内插件市场安装后,设置分区与侧栏面板正常、`sidebar.panellist` 岛屿可打开;记忆目录用 `SAGE_MEM_DIR` 默认的 `~/.sage-mem/memory` 即可,不需要额外适配。2.1.1 修掉了手机宿主上主按钮文字不可见的问题(前景令牌兜底)。
|
|
508
|
-
|
|
509
|
-
逐版本兼容记录:
|
|
510
|
-
|
|
511
|
-
| DSH 版本 | 状态 |
|
|
512
|
-
| --- | --- |
|
|
513
|
-
| 0.1.2-rc.1 | compatible |
|
|
514
|
-
| 0.1.3-alpha.1 | compatible |
|
|
515
|
-
| 0.1.3-alpha.2 | compatible |
|
|
516
|
-
| 0.1.5-rc.1 | compatible |
|
|
517
|
-
| 0.1.6-alpha.1 | compatible |
|
|
518
|
-
| 0.1.6-alpha.2 | compatible |
|
|
519
|
-
| 0.1.7-rc.1 | compatible |
|
|
520
|
-
| 0.1.7-rc.2 | compatible |
|
|
521
|
-
| 0.2.0-rc.1 | compatible |
|
|
522
|
-
| 0.2.0-rc.2 | compatible |
|
|
523
|
-
| 0.2.1-rc.1 | compatible |
|
|
524
|
-
| 0.2.1-rc.3 | compatible |
|
|
525
|
-
| 0.2.1-rc.4 | compatible |
|
|
526
|
-
| 0.2.1-rc.5 | compatible |
|
|
527
|
-
|
|
528
|
-
**`compatible` 怎么读**:它表示本插件的 codec / manifest 契约在该宿主代上核对过(代码层比对 + 单元/契约测试),**不表示**在那个确切宿主构建上跑过端到端探针。这张表是声明,核对方式总结在下面。真实宿主探针目前跑过 `headless`(2026-10-15)、`web`(2026-10-16)、**0.2.1-rc.3 上的 `web`**(2026-10-27,2.0.0 发布前:客户端激活、冲突面板裁决、注入抑制、回退到 1.9.2,23/23 项通过),以及 **0.2.1-rc.4 上的 DSH Go 手机引擎**(2026-10-31:App 插件市场安装、侧栏面板与设置分区原生呈现、`sidebar.panellist` 岛屿打开);`0.2.1-rc.5` 于同日在本地一个 web 实例上按 dump/启动/面板走查核对;`desktop` profile **还没跑过**(见 [release-checklist.md](https://github.com/gezi-wen/sage-mem/blob/main/docs/release-checklist.md) §4b)。CI 在 Ubuntu 上覆盖 **Node 18.x / 20.x / 24.x**,peer 钉 `0.2.0-rc.1` 一代;macOS 与 Windows 没有 CI 覆盖,desktop 宿主探针也还没有。
|
|
529
|
-
|
|
530
|
-
两点值得说明:
|
|
531
|
-
|
|
532
|
-
- **同一份构建同时覆盖 `0.1.x` 与 `0.2.x` 两条线。** 自 DSH `0.1.6-alpha.2` 起 TypertCodec 契约从「读 `schema`」改为「读 `create()` 工厂」,仍只写 `schema:` 的插件会在注册阶段直接抛错、把整棵插件树拖垮。本插件的每个 codec 同时带 `schema` 与 `create: () => schema`,两个时代的 DSH 都能加载。peer 依赖 `@deepseek-ai/dsh-typert-protocol` 与 `@deepseek-ai/dsh-client-locale` 的范围去掉上界,避免以后某个大版本被这道声明误挡(这两个包在 `0.1.7-rc.2` 与 `0.2.0-rc.1` 之间导出符号零增删,逐字节比对过)。
|
|
533
|
-
- **带一层防御留痕。** 注入链依赖的事件名与字段在日常升级中很稳定;如果哪天 DSH 把它们改成异步或改了名,插件会在**日志里报警**,而不是静默地不再注入记忆。
|
|
534
|
-
|
|
535
|
-
## 架构
|
|
536
|
-
|
|
537
|
-
```
|
|
538
|
-
DSH(Cordis 插件宿主)
|
|
539
|
-
└─ sage-mem 插件(按问题检索 + 注入)← 本仓库
|
|
540
|
-
│ Node fs 直读 / 直写
|
|
541
|
-
▼
|
|
542
|
-
memory/ 目录(markdown 文件,4 类)
|
|
543
|
-
├── user_*.md
|
|
544
|
-
├── feedback_*.md
|
|
545
|
-
├── project_*.md
|
|
546
|
-
├── reference_*.md
|
|
547
|
-
└── MEMORY.md ← 索引
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
没有 worker、没有 SQLite、没有 HTTP 端口、没有常驻进程。
|
|
551
|
-
|
|
552
|
-
模块边界(1.2.0 起):
|
|
553
|
-
|
|
554
|
-
- `lib/index.js` —— **组合根** + 三个 Gateway。typert manifest 的 `sourceLocation` 钉在这个文件上,所以 Gateway 类不搬走(搬 = 契约漂移)
|
|
555
|
-
- `lib/recall/` —— 注入链:选材(`select`)/ 渲染与预算(`compose`)/ 文本(`text`)/ 会话状态(`state`)
|
|
556
|
-
- `lib/assembly/` —— 四块挂载:autodream / 检索工具 / 观测 / 写入门
|
|
557
|
-
- `lib/memory/` —— 记忆层的纯函数与文件操作;`lib/autodream/` —— 自动做梦引擎
|
|
558
|
-
- `lib/shared/` —— 跨层零依赖的小判据:文件名规则 / 本地时间戳 / 两个体积上限(**只许一份**,`test/duplication-test.mjs` 会红)
|
|
559
|
-
- 界面侧:`client/*.js` 按屏分片(`05-shared.js` 放跨屏小基元与类型词表),`npm run build:client` 拼成 `lib/client.js`
|
|
560
|
-
- 依赖方向:`memory` 不认识 `autodream`,`recall` 只依赖 `memory`,`assembly` 不反向依赖组合根(`test/structure-test.mjs` 会红)
|
|
561
|
-
|
|
562
|
-
## 参与开发
|
|
563
|
-
|
|
564
|
-
```bash
|
|
565
|
-
git clone https://github.com/gezi-wen/sage-mem
|
|
566
|
-
cd sage-mem
|
|
567
|
-
npm install
|
|
568
|
-
npm test # 全部断言(含三条守卫)
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
- 断言脚本只依赖 `node:` 内置模块,**不引测试框架**,各脚本手写 `ok()` 并以汇总行收尾。
|
|
572
|
-
- 本仓库统一用 **npm** 安装 / 测试 / CI。不留 `pnpm-lock.yaml`:旧的锁在 fresh clone 里 `--frozen-lockfile` 装不动(它依赖的 workspace 文件未入库),留着只会误导。
|
|
573
|
-
- **三条守卫**盯的是「测试本身站不站得住」:`suite-registry-test.mjs`(有没有写了不跑的套件)、
|
|
574
|
-
`contract-binding-test.mjs`(有没有说了不验的承诺)、`doc-consistency-test.mjs`(两份文档的定性是否逐行一致)。
|
|
575
|
-
- CI 在 `ubuntu-latest` 上跑两遍(第二遍 `TZ=UTC`)—— 区分大小写的卷能逮到 Windows 上看不见的缺陷。
|
|
576
|
-
- 改了**用户可见行为**时,请同时更新 [契约](https://github.com/gezi-wen/sage-mem/blob/main/docs/1.0.0-contract.md) 的对应行与 CHANGELOG;写不出断言的承诺只能标「尽力」。
|
|
577
|
-
|
|
578
|
-
**问题与建议**:开 [Issue](https://github.com/gezi-wen/sage-mem/issues)。小而明确的修复欢迎直接提 PR;改动面较大的先开 Issue 对齐方向,免得白做。
|
|
579
|
-
|
|
580
|
-
## 常见问题
|
|
581
|
-
|
|
582
|
-
**升级到 2.0.0 之后,注入行为变了什么?**
|
|
583
|
-
2.0.0 是**行为变更版**(所以按 MAJOR 发):① 带 `invalid_at` 且已到点的记忆**不再进正文**;② 有未裁决矛盾的记忆**两方都不进正文**,直到你在「记忆管理 → 冲突」页签里裁决;③ **没有标注的旧记忆行为不变**,不需要迁移,也不会有迁移脚本。**回退到 1.9.x 是安全的**:不会损坏任何数据,但「失效」标记会被旧版本忽略(失效记忆重新注入)、冲突页签消失 —— 那是**功能消失,不是数据损坏**。如果只想关掉这层过滤、保留本版其它修复,用 `SAGE_MEM_VALIDITY=0`(只认 `0` / `false` / `off` 为关闭)。
|
|
584
|
-
|
|
585
|
-
**记忆没被注入怎么办?**
|
|
586
|
-
先确认记忆目录对不对(`SAGE_MEM_DIR`,默认 `~/.sage-mem/memory`),再看文件有没有 frontmatter(坏掉的 frontmatter **会被宽容解析**:字段回落默认,文件照样读得到)。面板里的「体检」页会报出索引悬空、漏索引、断链这类结构问题。
|
|
587
|
-
|
|
588
|
-
**为什么换个说法就搜不到了?**
|
|
589
|
-
检索是**词面重合**,不是语义的。给这条记忆加一行 `aliases: [另一种说法]` 即可。这是有意的取舍,不是待修的缺陷。
|
|
590
|
-
|
|
591
|
-
**它会不会把我说的每句话都记下来?**
|
|
592
|
-
不会。存不存由模型按规则判断:无实质内容的不存,靠读代码 / git 能推出来的不存。
|
|
593
|
-
|
|
594
|
-
**我想看看它到底存了什么。**
|
|
595
|
-
打开记忆目录,全是 Markdown。GUI 里的「记忆管理」页也能浏览、编辑、删除。
|
|
596
|
-
|
|
597
|
-
**多个 DSH 实例共用同一个记忆目录安全吗?**
|
|
598
|
-
不。写入串行只保证**进程内**,跨进程没有文件锁 —— 同时用两个实例写同一目录仍可能丢一次。这是已知边界。
|
|
599
|
-
|
|
600
|
-
**能不用自动做梦吗?**
|
|
601
|
-
能,它默认就是关闭的。它和归档是两件事:归档是确定性规则(按闲置天数),不调模型、不花钱。
|
|
602
|
-
|
|
603
|
-
**插件市场报「registry 上的最新版比已装还旧,更新会降级」?**
|
|
604
|
-
那是**下载地区镜像的 `/latest` 缓存滞后**,不是包发错了:镜像的全量元数据(pnpm 读的那份)已经是新版,但 `/<包>/latest` 这个快捷入口还缓存着旧值。先等一两分钟重试;仍然不行就把插件市场的「下载地区」临时切到「全球」(直连 registry.npmjs.org)更新,完成后再切回。核对实际发布版本:`npm view sage-mem dist-tags`。
|
|
605
|
-
|
|
606
|
-
**顶部「月下载量」为什么改成了 endpoint 徽章?**
|
|
607
|
-
npm 官方的 `downloads/point/last-month` 接口从 2026 年 7 月起对所有包都返回 0,`npm/dm` 徽章因此渲染成红色的 `0/month`;现在这个数字由 GitHub Actions 每天从仍然可用的 `downloads/range` 接口求和,发布到 `badges` 分支,再用 [shields.io endpoint badge](https://shields.io/badges/endpoint-badge) 渲染。若它超过约 2 天没变化,通常只是定时任务延迟(或 npm 接口故障,此时会特意保留上一次的正常值)。
|
|
608
|
-
|
|
609
|
-
## 卸载
|
|
610
|
-
|
|
611
|
-
```bash
|
|
612
|
-
dsh plugin --profile web remove sage-mem
|
|
613
|
-
```
|
|
614
|
-
|
|
615
|
-
或手动在 profile 的 `package.json` 里删掉 `sage-mem` 依赖、并从 `dsh.profile.bundles` 移除 `"sage-mem"`;`pnpm install` 后重启 DSH。
|
|
616
|
-
|
|
617
|
-
**记忆 Markdown 文件留在原目录,不受影响。**
|
|
618
|
-
|
|
619
|
-
## 维护者
|
|
620
|
-
|
|
621
|
-
[@gezi-wen](https://github.com/gezi-wen)
|
|
622
|
-
|
|
623
|
-
## 致谢
|
|
624
|
-
|
|
625
|
-
- [@bjvgukv25842-cmyk](https://github.com/bjvgukv25842-cmyk) —— 最早的 Web 记忆管理界面(记忆管理页的起点)。
|
|
626
|
-
- [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) —— 插件宿主与 Cordis 插件体系。
|
|
627
|
-
|
|
628
|
-
## 相关项目
|
|
629
|
-
|
|
630
|
-
- [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) — DSH 本体,一切皆插件
|
|
631
|
-
- [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) — DSH 插件生态清单
|
|
632
|
-
|
|
633
|
-
## 旧版
|
|
634
|
-
|
|
635
|
-
本仓库早期是 SQLite + worker 架构,已归档到 [`sqlite-worker`](https://github.com/gezi-wen/sage-mem/tree/sqlite-worker) 分支。文件式是继任实现。
|
|
636
|
-
|
|
637
|
-
## License
|
|
638
|
-
|
|
639
|
-
Apache-2.0 · 见 [LICENSE](https://github.com/gezi-wen/sage-mem/blob/main/LICENSE)
|