dsh-lite-memory 0.16.2 → 0.17.0

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.
@@ -0,0 +1,143 @@
1
+ # 触发、写入、更新、读取、删除的判定约定
2
+
3
+ 这份文档写的是**代码现在真正执行的规则**,不是产品愿望。改行为先改这里,再改实现和测试。
4
+
5
+ 入口按顺序只有五个:
6
+
7
+ 1. **自动注入**:每轮真人消息后,`index.js` 在 provider 准备阶段判断是否把 1–3 张卡片塞进上下文。
8
+ 2. **工具检索**:`memory_search`,模型主动查。
9
+ 3. **工具读取**:`memory_read`,按 id 取正文;关系用它的 `view:"links"`,没有单独的 `memory_links` 工具。
10
+ 4. **工具写入**:`memory_save`,新建或更新同一条。
11
+ 5. **面板与路由**:`/lite-memory/*`,人点「记住 / 忽略 / 归档 / 搬移 / 撤销 / 移除」。
12
+
13
+ 五个入口共用 `view.eligibility(record, runtime, purpose, now)` 这一道可用性闸门。任何入口都不得绕开它自行判断「这条能不能用」。
14
+
15
+ ## 一、判定先后的硬顺序
16
+
17
+ 任何一条记录,从「存在」到「能被用」,顺序固定:
18
+
19
+ 1. 记录存在且未归档(`status !== 'archived'`)。
20
+ 2. 归属项目匹配:`ownerProjectId` 有值时必须等于当前 `projectId`;全局层不设。
21
+ 3. 实验来源(`origin === 'project_evolution'`)额外的三件事:来源没被关(`runtime.experimental !== false`)、学习状态仍在有效期内(`active`,或未到期的 `trial`)、没有 `paused` / `blockedReason`。
22
+ 4. 旧库迁移且未复核的记录(`reviewState === 'legacy_unreviewed'`):自动注入一律不碰;人工打开面板可以看。
23
+ 5. 条件与任务状态:`statusOf(record, context)` 必须为 `ok`。条件读不出来算 `needs_context`,条件明确不符算 `not_applicable`。
24
+ 6. 自动注入额外要求状态严格为 `ok`;工具检索也只把 `ok` 的条目放进结果,`needs_context` 的整条挡在外面,只在「有命中但全都不可用」时给一句统一说明,不递标题和 id。
25
+ 7. 预算:请求级预算与自动注入预算是两道,先准入再记账(见第二、三节)。
26
+
27
+ 前六步任何一步失败,错误状态只能是 `gone / not_applicable / needs_context` 三者之一;不要再造新状态,也不要把「不适用」包装成「没有记忆」。
28
+
29
+ ## 二、自动注入
30
+
31
+ **触发点**:每轮新真人消息(`freshHumans` 有新增)且 `recallMode() === 'adaptive'`。工具模式、面板操作、同一请求内的模型调用都不触发。
32
+
33
+ **取什么**:
34
+
35
+ - 项目层 + 全局层当前投影;同一 id 两层都有时按 `project` 优先,只留一条。
36
+ - 归档条目、实验来源(普通自动召回不碰,实验规则单独走 `experimentFor`)、`legacy_unreviewed` 全部排除。
37
+ - 同一 `topicKey` 槽位只让胜出的记录进;槽位内冲突或条件未知的整组不进自动召回,不猜哪条对。
38
+ - 默认值 `__tasks.defaults` 会参与任务上下文,但只是「范围提示」,不是把某条记忆设成必用。
39
+
40
+ **预算**:普通卡片与实验规则**共用**一份账本:最多 3 张卡片、最终拼接文本最多 1200 字节。字节量的是 provider 最终看到的完整文本(含标题行、ID、换行、两段之间的空行),不是单张正文。超预算时跳过这张,不截半句、不把标题留在模型侧。
41
+
42
+ **失败**:
43
+
44
+ - 准备超过 `RECALL_DEADLINE_MS`、被取消、投影期间开关/代次变了:整批回退,预算退还,下一轮重新来。绝不发布半旧卡片。
45
+ - 召回为空是正常结果,不是错误;不弹提示、不占用额度。
46
+ - 已发布但用户随后关闭记忆:从下一轮起不再注入;当轮已发出的内容不追回。
47
+
48
+ **可解释性要求**:卡片标题行必须能看出「这是历史笔记,不是本轮指令」;引用条只展示被真正读进上下文的条目,不把「检索到但没用上」的算作引用。
49
+
50
+ ## 三、工具检索与读取
51
+
52
+ **`memory_search`**
53
+
54
+ 1. 当前请求的 `contextId` 是可选的:不给就按这次调用已经路由到的运行时上下文判定;显式 `bindForRequest` 却拿不到 id、给了过期/跨项目的 id,才返回 `needs_context`。
55
+ 2. 先判范围是否登记过(栏目、任务类型、系列)。没登记过就明确说「本轮没有匹配的记忆范围」,不按关键词硬凑。
56
+ 3. 按 `eligibility(..., 'search')` 过滤;再用 `status === 'ok'` 截一遍。`needs_context` 条目不进结果;全都被挡时只回一句「有相关的项目记忆,但适用条件还没确认 / 当前任务明确不适用」,不带标题、编号。
57
+ 4. 结果条数受请求级 `reads` / `bytes` 限制:先按字节退条数,还剩至少一条就按 `ok` 返回已缩的部分;一条都放不下才 `budget_exhausted`,此时正文为空。
58
+ 5. `reasonCode` 只在三种情况出现:`no_match`、`not_applicable`、`applicability_unresolved`。`budget_exhausted` / `memory_disabled` / `needs_context` 是 `status`,这些分支不带原因码。
59
+
60
+ **`memory_read`**
61
+
62
+ - 只认 `mem_` 开头且已过格式校验的 id;未知 id 返回 `gone`,不猜路径。
63
+ - 读正文同样要过 `eligibility` 与请求级 `bodies` 预算;读到的正文按当前版本返回,不返回历史版本冒充当前。
64
+ - 冷存(归档正文)按需读;冷存损坏就明确报 `cold_corrupt` / `cold_missing`,禁止用空正文顶上。
65
+ - `memory_read` 的 `view:"links"` 只返回真实关系;目标条目不可用的关系不返回,避免把死链当线索。
66
+
67
+ ## 四、写入与更新
68
+
69
+ **先搜再写**:工具说明要求模型先 `memory_search`,能更新就不新建。服务端仍会独立做重复判定,不能只靠模型自律。
70
+
71
+ **身份**:`id` 是唯一身份;同一件事更新时带 `id`。相似度只在「没带 id」时用于拦截:文本相近但标题不同,返回 `needsResolution` 和 1–2 条相近条目,等模型确认「同一条」或带 `resolution="distinct"` 再存。不允许「看起来像就自动覆盖」。
72
+
73
+ **字段语义**:
74
+
75
+ - 带 `id` 更新时,没给的字段保持原值;给了就整段替换(正文不是追加,旧正文只留作内部撤回,不展示、不参与检索)。
76
+ - `title` / `summary` / `when` 是身份的一部分;`when` 为空表示「无条件限制」,不是「待补充」。
77
+ - `topicKey` 是槽位;同一槽位只允许一条当前胜出记录,冲突时先让人选,不自动合并。
78
+ - `kind` 只影响展示分组,不参与检索和可用性判断。
79
+
80
+ **范围路由**:模型给的 `scope` 是提案,不是命令。设备事实必须带设备条件(`machine` / `targetOs` / `runtime` / `shell` 至少一个);正文或依据里出现本机路径、回环地址、端口的「通用流程」,先留在项目层或问一次条件,不直接写全局。
81
+
82
+ **限额与腾位**:每层活跃条目软上限 200、硬上限 1000;单条正文 4096 字节;单层总字节 1 MiB。到硬上限时把最久没被读的活跃条目移进归档,并在结果里写明「已归档「标题」腾位」,不拒绝新记忆;单条正文超限只报错、不截断。
83
+
84
+ **写入安全**:所有写都在作用域锁内、走 `transact` 的「意图文件 → 原子替换 → 发布」,失败按原状态回滚;不允许只改内存不落盘。工具返回 `changed:false` 时不得声称「已记住」。
85
+
86
+ ## 五、条件与范围
87
+
88
+ 条件只有登记过的键能用:`targetOs`、`shell`、`runtime`、`machine`、`projectRoot`、`pathPrefix`、`artifactKey`、`taskKind`、`seriesId`、`phase`。自由措辞不算条件。
89
+
90
+ - `conditions` 最多 8 条;同一标题但前置条件不同,是两条记录,不合并。
91
+ - 条件判定结果只有三种:`ok`、`not_applicable`、`needs_context`。读不出来时不许当成 `ok`。
92
+ - `applicability`:`all_in_scope` 普遍适用;`conditional` 必须给条件;`unresolved` 是还没定,自动注入不用,检索也不会把它的标题递出来。
93
+ - 项目根目录未确认(`confirmed:false`)时没有项目层身份,不能凭 cwd 新建命名空间;写入要么先确认项目,要么显式 `scope="global"`。
94
+
95
+ ## 六、引用与展示
96
+
97
+ - 引用条只记「这次真的读进上下文」的条目:`memory_search` 的命中、`memory_read` 的正文、`memory_save` / `memory_forget` 造成的变更。
98
+ - 同一条在多个工具结果里出现只留一条,位置取最早、内容取最新。
99
+ - 展示分组固定三组:搜索命中、读过正文、本轮更新。没有内容且没有原因时整组不渲染。
100
+ - 引用是**展示**,不是新的事实来源;点开只读当前正文,当前已删除/移出就返回空,不回退旧缓存。
101
+ - 面板任何「整理 / 修复」动作只动展示与索引元数据,不改学习状态机、不改内容、不占召回额度;记忆总开关关闭时面板只读。
102
+
103
+ ## 七、自进化(默认关闭)
104
+
105
+ 链路只有一条:可信的 `agent/pre-step` 反馈 → `rememberFeedback` → `candidateFrom` → pending → 整理器 LLM → `parseOutput` → `commitLearning` / `admitChange` / `nextLearningState`。
106
+
107
+ - 只有实验/学习相关动作依赖 `scope.confirmed`;普通记忆的采集与使用不因它整体失效。
108
+ - 助手自己的声明不算支持证据,只有用户原话/确认算。
109
+ - 项目层规则从 `pending` 起,到 `trial`,再到 `active`;`paused` 只能被显式长期规则或重置证据解除,弱证据不会把 `active` 降级。
110
+ - 未改规范(`specDigest` 相同)保持现有状态;旧整理结果不能覆盖新纠正(`feedbackRevisions` 复查)。
111
+ - 整理失败只留回执,不产生规则副作用;配额已扣不退,界面显示失败原因。
112
+
113
+ ## 八、关闭、删除与恢复
114
+
115
+ - **总开关关闭是权限变化,不是内容删除**:`memory_save` 返回 `changed:false, id:'', needsScope:true, reason:'memory_disabled'`;搜索/读取同样拒绝。面板只读。
116
+ - **`memory_forget`**:默认移入归档(可人工找回),只有显式 `purge` 才彻底删除正文与受管副本;删除不可复活。
117
+ - **撤销**:按 `id + revision` 精确撤销;版本对不上就不猜,避免撤掉别人后来的修改。
118
+ - **冷存隔离**:损坏的冷存文件可以被隔离或恢复;恢复前先校验,校验不过不进主库。
119
+ - **锁**:同一作用域写锁 500 ms 等不到就 `busy`;持锁进程已死的残留锁会自动回收,活进程占用才报忙。错误信息给「等一会儿重试」这一条动作,不暴露内部路径细节。
120
+
121
+ ## 九、失败语义(模型与界面共同遵守)
122
+
123
+ | 状态 | 含义 | 下一步 |
124
+ | --- | --- | --- |
125
+ | `ok` | 可用 | 正常使用 |
126
+ | `gone` | 不存在或已归档 | 不重试;要找回走面板 |
127
+ | `not_applicable` | 条件明确不符 | 不重试;换任务/换条件 |
128
+ | `needs_context` | 当前请求无法核对(过期、跨项目、未确认) | 先重新问一次当前任务,或补 contextId |
129
+ | `budget_exhausted` | 本轮额度用完 | 下一轮再查,不绕预算 |
130
+ | `memory_disabled` | 本项目记忆关闭 | 让用户先在面板打开;不得自行打开 |
131
+ | `busy` | 锁竞争/残留 | 退避重试;持续失败提示清理锁 |
132
+ | `cold_corrupt` / `cold_missing` | 冷存损坏/缺失 | 明确报错,不空正文顶上;走隔离/恢复 |
133
+
134
+ 错误文案必须包含「发生了什么 + 下一步动作」,不能只抛内部名词。同一失败不要在多处重复提示;一轮只留一条。
135
+
136
+ ## 十、改规则时的检查单
137
+
138
+ 1. 先改本文档,再改 `view.eligibility` / `store.js` / `index.js` / 测试。
139
+ 2. 新状态必须在 `eligibility` 里有唯一归属,不能靠调用方各自 if。
140
+ 3. 涉及落盘:`writeDurable` / `publish` / 冷存 fsync 三条路径都要过;旧 `state.json` 必须还能读。
141
+ 4. 涉及预算:量最终文本,不量中间对象;先准入再记账,失败退还。
142
+ 5. 涉及展示:只展示真实发生的事;没有原因的空结果也要能解释为什么空。
143
+ 6. 跑 `npm test`;仓库没有 lint 脚本,界面改动另按全局设计规范跑文案与样式检查。契约测试里的字段和状态是硬约束,不靠「包含」放宽。
@@ -0,0 +1,8 @@
1
+ # 0.17.0
2
+
3
+ - 新增记忆宫殿视图:四条固定房间(习惯与规范 / 方法与经验 / 资料与入口 / 其他记录),空房间保留位置,每页 24 个槽位,点展品交给现有详情入口;同一时刻只展开一间,再点收起,减少动态时不做动画。
4
+ - L1 建议行的按钮从 `keep` 改为「保留」,写完直接消失、行变成 `memory · kept`;仍然是同源直写,不产生消息、不动输入框。
5
+ - `state.json` 读不出来时不再让整层不可用:原始字节先隔离成 `state.corrupt-<时间>.json` 副本再重建,同内容副本只留一份,写不进副本才报失败。
6
+ - 锁文件按 pid 判活并回收:`EPERM` 视为「进程存在但不属于当前用户」,绝不回收;遗留的死锁不再需要人工删。
7
+ - 落盘统一走「写临时文件 + fsync + rename」,机器掉电不会留下半截索引。
8
+ - TRIGGERS.md 与 README 对齐当前实现,新增 `docs/CONVENTIONS.md`(写的是代码真正执行的判定规则)。