@modusensus/dsh-mneme 0.3.9 → 0.4.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.
- package/README.md +20 -2
- package/lib/config.js +38 -0
- package/lib/dream/decisions.js +49 -1
- package/lib/dream/sleep.js +550 -0
- package/lib/index.js +18 -0
- package/lib/service.js +72 -3
- package/lib/store.js +103 -8
- package/package.json +1 -1
- package/src/config.js +38 -0
- package/src/dream/decisions.js +49 -1
- package/src/dream/sleep.js +550 -0
- package/src/index.js +18 -0
- package/src/service.js +72 -3
- package/src/store.js +103 -8
- package/test/sleep.test.js +365 -0
package/README.md
CHANGED
|
@@ -54,6 +54,22 @@
|
|
|
54
54
|
- **Fail-safe**:非法 LLM 输出(未知 id / 非法 action / 跨类型合并 / 越界 importance)拒绝整单,绝不破坏记忆库
|
|
55
55
|
- **裁决审计**:每次运行写入 `dream_runs` 审计表(输入快照 sha256 digest + 完整输入快照 + 决策清单 + 逐 id 去向 + receipt),可离线回放;merge / conflict / update 幂等应用,重放/并发重复执行无累积副作用;update 记录 `_before` 快照
|
|
56
56
|
|
|
57
|
+
### Sleep Mode 系统级睡眠 💤(v0.4.0,opt-in)
|
|
58
|
+
|
|
59
|
+
从 autoDream 的"被动阈值触发"升级为"主动定时维护 + 分层压缩"。系统空闲 `sleepIdleMinutes` 分钟自动执行深度维护,**默认关闭**(`sleepModeEnabled: false`),开启后行为:
|
|
60
|
+
|
|
61
|
+
- **可中断**:AbortController 实现,用户恢复活动即中止当前周期(`noteWrite` 重置空闲计时 + 中断信号)
|
|
62
|
+
- **串行安全**:睡眠周期走 `service.enqueue` 串行队列,与 autoDream 严格不重叠;`minRefTimeMs` 防止快照后被召回的记忆被误降级
|
|
63
|
+
- **四阶段深度维护**:
|
|
64
|
+
1. `conflict_resolution`:全库冲突消解,strictness 三级可配(gentle 0.92 / normal 0.85 / aggressive 0.75)
|
|
65
|
+
2. `archival_demotion`:按 `last_accessed_at` 分层——30 天未召回压成摘要(原文进 `_full_content`,可无损恢复)、90 天完全归档
|
|
66
|
+
3. `pattern_discovery`:LLM 扫描近期记忆提炼规律,产出 `type=pattern` 记忆,evidence 强校验防伪造
|
|
67
|
+
4. `relation_completion`:检测孤立实体并补全隐含关系(共现 `related_to` / 项目 `part_of` / 技术 `depends_on`)
|
|
68
|
+
- **Fail-safe**:每阶段独立 try/catch,LLM 故障只跳过对应阶段;无 LLM 路由时纯规则降级(demotion/relations)照常执行
|
|
69
|
+
- **审计延续**:睡眠周期写入 `dream_runs`,`run_type='sleep'`,与 autoDream 共用审计表可追溯
|
|
70
|
+
|
|
71
|
+
> 配置详见 `docs/SLEEP.md`;迁移说明见 `docs/MIGRATION.md`。
|
|
72
|
+
|
|
57
73
|
### Web 记忆面板
|
|
58
74
|
|
|
59
75
|
官方设置面板 → 「记忆库设置」→「记忆」标签:按类型浏览、全文搜索;启用向量搜索后可用「语义」切换做向量召回。
|
|
@@ -115,6 +131,7 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
115
131
|
|
|
116
132
|
| 版本 | 亮点 |
|
|
117
133
|
|------|------|
|
|
134
|
+
| **v0.4.0** | 系统级睡眠 Sleep Mode:空闲触发的四阶段深度维护(冲突消解 / 归档降级 / 模式发现 / 关系补全),可中断、串行安全、fail-safe,分层压缩释放冷记忆;471 测试全绿 |
|
|
118
135
|
| **v0.3.9** | 修复第三方审计 4 项 FAIL:CAS 同事务原子化、Mirror 降级回执透传、逐 type 物理终态收敛、Generation 强整数校验与并发初始化稳定化 |
|
|
119
136
|
| **v0.3.8** | audit peer 复验 6 项运行时阻断全部修复:desired generation 同事务原子递增(崩溃窗口不再静默跳过)、同步失败不静默、原子 generation 增量(多进程零丢失)、逐 type committed/failed/pending 回执、读取失败显式 unknown、generation 上界/负数 CHECK |
|
|
120
137
|
| **v0.3.7** | 启动竞态修复:人工编辑 md 镜像后重启向量重建失败(回灌移入 init 就绪后 + scheduleEmbed 就绪门) |
|
|
@@ -129,10 +146,11 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
129
146
|
| v0.3.0 | ✅ 完成 | 记忆基因 | entities/attrs/relations 三表 + 时间轴 + 实体搜索 |
|
|
130
147
|
| v0.3.6–0.3.8 | ✅ 完成 | 镜像一致性 + 审计加固 | generation 同步状态机、audit peer 6 项运行时阻断修复、450 测试全绿 |
|
|
131
148
|
| v0.3.9 | ✅ 完成 | 审计加固 A/B/D/F | compareAndUpdate 同事务原子性、degraded 回执、逐 type 物理终态、整数 fail-closed、并发初始化稳定 |
|
|
132
|
-
| **v0.4.0** |
|
|
149
|
+
| **v0.4.0** | 🚧 开发中 | 系统级睡眠 Sleep Mode | 空闲触发的四阶段深度维护(冲突消解 / 归档降级 / 模式发现 / 关系补全)、分层压缩、可中断串行 fail-safe;471 测试全绿 |
|
|
150
|
+
| **v0.4.1** | 📋 规划 | 反思性成长 | 纠错双向回流(改记忆同时反思"为什么记错/召回错")+ 规则演进(从 failure 提炼规律注入系统提示)+ 自适应参数 |
|
|
133
151
|
| **v0.5.0+** | 🚀 远期 | 自进化记忆 | 兴趣漂移跟踪 + 跨 workspace 记忆共享(等 DSH 支持) |
|
|
134
152
|
|
|
135
|
-
> 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`failure_memories` 表与 autoDream
|
|
153
|
+
> 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`failure_memories` 表与 autoDream 决策引擎已为后续反思性成长铺好路。
|
|
136
154
|
|
|
137
155
|
## 📦 安装
|
|
138
156
|
|
package/lib/config.js
CHANGED
|
@@ -91,4 +91,42 @@ export const Config = z.object({
|
|
|
91
91
|
entityExtractionMaxAttrs: z.natural().min(1).max(50).default(20),
|
|
92
92
|
// Prefix/semantic search over entity names (used by recall).
|
|
93
93
|
entitySearchEnabled: z.boolean().default(true),
|
|
94
|
+
|
|
95
|
+
// --- sleep mode: idle-triggered deep maintenance (v0.4.0) ---------------
|
|
96
|
+
// Opt-in, off by default. Unlike autoDream (threshold-triggered, lightweight)
|
|
97
|
+
// sleep fires when the store has been quiet for sleepIdleMinutes and deep-
|
|
98
|
+
// maintains the whole library: conflict resolution, archival demotion,
|
|
99
|
+
// pattern discovery and entity relation completion. Abortable on user
|
|
100
|
+
// activity, audited into dream_runs (run_type='sleep'), and serialized with
|
|
101
|
+
// autoDream so the two never overlap.
|
|
102
|
+
sleepModeEnabled: z.boolean().default(false),
|
|
103
|
+
// Quiet window before a cycle fires (minutes).
|
|
104
|
+
sleepIdleMinutes: z.natural().min(1).max(60).default(5),
|
|
105
|
+
// Minimum gap between two sleep runs (hours) — a second idle window within
|
|
106
|
+
// this interval does not retrigger.
|
|
107
|
+
sleepMinIntervalHours: z.natural().min(1).max(168).default(8),
|
|
108
|
+
// Conflict adjudication strictness:
|
|
109
|
+
// gentle only high-confidence conflicts (threshold 0.92) are resolved
|
|
110
|
+
// normal standard dream-level (threshold 0.85)
|
|
111
|
+
// aggressive low-confidence pairs are also adjudicated (threshold 0.75)
|
|
112
|
+
sleepConflictStrictness: z.union([
|
|
113
|
+
z.const("gentle"),
|
|
114
|
+
z.const("normal"),
|
|
115
|
+
z.const("aggressive")
|
|
116
|
+
]).default("normal"),
|
|
117
|
+
// Archival demotion tiering (days since last access):
|
|
118
|
+
// >= sleepArchiveDays → shrink to summary, full body kept in _full_content
|
|
119
|
+
// >= sleepCompressDays → archived outright (entity relations preserved)
|
|
120
|
+
sleepArchiveDays: z.natural().min(7).max(365).default(30),
|
|
121
|
+
sleepCompressDays: z.natural().min(7).max(365).default(90),
|
|
122
|
+
// Pattern discovery scan window (most recent memories to scan).
|
|
123
|
+
sleepPatternMinMemories: z.natural().min(10).max(1000).default(100),
|
|
124
|
+
// How far back pattern discovery considers entity attr changes (days).
|
|
125
|
+
sleepPatternLookbackDays: z.natural().min(1).max(90).default(30),
|
|
126
|
+
// Max pattern memories minted per run (0 = disabled).
|
|
127
|
+
sleepMaxPatternPerRun: z.natural().min(0).max(10).default(3),
|
|
128
|
+
// Optional LLM route override for sleep's bulk passes (empty = use dream
|
|
129
|
+
// route / agent default model).
|
|
130
|
+
sleepProvider: z.string().default(""),
|
|
131
|
+
sleepModel: z.string().default(""),
|
|
94
132
|
});
|
package/lib/dream/decisions.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const ACTIONS = new Set(["keep", "merge", "archive", "conflict", "update"]);
|
|
1
|
+
const ACTIONS = new Set(["keep", "merge", "archive", "conflict", "update", "create"]);
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Validate a dream decision list against a snapshot of eligible memories.
|
|
@@ -26,6 +26,25 @@ export function validateDecisions(decisions, snapshot, options = {}) {
|
|
|
26
26
|
errors.push(`${at}: conflict needs distinct winner and loser`);
|
|
27
27
|
continue;
|
|
28
28
|
}
|
|
29
|
+
} else if (d.action === "create") {
|
|
30
|
+
// Mint a fresh memory (sleep pattern discovery). Claims no existing id,
|
|
31
|
+
// so it skips the claiming loop below; evidence is optional provenance
|
|
32
|
+
// (already filtered to real ids by the caller) and is stored in content.
|
|
33
|
+
if (typeof d.title !== "string" || !d.title.trim()) {
|
|
34
|
+
errors.push(`${at}: create needs non-empty title`);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (typeof d.content !== "string" || !d.content.trim()) {
|
|
38
|
+
errors.push(`${at}: create needs non-empty content`);
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (d.importance !== undefined && (!Number.isInteger(d.importance) || d.importance < 1 || d.importance > 5)) {
|
|
42
|
+
errors.push(`${at}: create importance must be an integer 1-5 when provided`);
|
|
43
|
+
}
|
|
44
|
+
if (typeof d.type !== "string" || !d.type.trim()) {
|
|
45
|
+
errors.push(`${at}: create needs non-empty type`);
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
29
48
|
} else if (!Array.isArray(d.ids) || d.ids.length === 0) {
|
|
30
49
|
errors.push(`${at}: ${d.action} needs non-empty ids`);
|
|
31
50
|
continue;
|
|
@@ -95,6 +114,12 @@ export function validateDecisions(decisions, snapshot, options = {}) {
|
|
|
95
114
|
if (updateCount > maxUpdatePerRun) {
|
|
96
115
|
errors.push(`too many update decisions: ${updateCount} > ${maxUpdatePerRun}`);
|
|
97
116
|
}
|
|
117
|
+
// Cap pattern minting per run (sleepMaxPatternPerRun passes through here).
|
|
118
|
+
const createCount = decisions.filter((d) => d.action === "create").length;
|
|
119
|
+
const maxCreatePerRun = options.maxCreatePerRun ?? 5;
|
|
120
|
+
if (createCount > maxCreatePerRun) {
|
|
121
|
+
errors.push(`too many create decisions: ${createCount} > ${maxCreatePerRun}`);
|
|
122
|
+
}
|
|
98
123
|
// Every snapshot id must appear in at least one decision
|
|
99
124
|
for (const id of snapshot.keys()) {
|
|
100
125
|
if (!claimed.has(id)) errors.push(`memory ${JSON.stringify(id)} missing from decisions`);
|
|
@@ -202,10 +227,33 @@ function applyOne(d, service, snapshot, config = {}) {
|
|
|
202
227
|
case "archive": return applyArchive(d, service, snapshot);
|
|
203
228
|
case "merge": return applyMerge(d, service, snapshot, config);
|
|
204
229
|
case "conflict": return applyConflict(d, service, snapshot);
|
|
230
|
+
case "create": return applyCreate(d, service, config);
|
|
205
231
|
default: return applyUpdate(d, service, snapshot, config);
|
|
206
232
|
}
|
|
207
233
|
}
|
|
208
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Mint a fresh memory (pattern discovery). No existing target, so no CAS guard.
|
|
237
|
+
* Evidence ids ride in the content so a pattern stays traceable to its source
|
|
238
|
+
* memories. saveWithDedupe dedupes identical mints (idempotent replay-safe).
|
|
239
|
+
*/
|
|
240
|
+
function applyCreate(d, service, config = {}) {
|
|
241
|
+
const title = String(d.title ?? "").trim();
|
|
242
|
+
const content = String(d.content ?? "").trim();
|
|
243
|
+
const importance = Number.isInteger(d.importance) ? d.importance : 3;
|
|
244
|
+
const type = typeof d.type === "string" ? d.type : "pattern";
|
|
245
|
+
const evidence = Array.isArray(d.evidence)
|
|
246
|
+
? d.evidence.filter((id) => typeof id === "string")
|
|
247
|
+
: [];
|
|
248
|
+
const body = evidence.length > 0
|
|
249
|
+
? `${content}\n\n[证据: ${evidence.join(", ")}]`
|
|
250
|
+
: content;
|
|
251
|
+
const created = service.saveWithDedupe({ type, title, content: body, importance });
|
|
252
|
+
const memory = created?.memory;
|
|
253
|
+
if (!memory) return "skipped"; // deduped/subsumed: nothing minted, clean no-op
|
|
254
|
+
return { applied: 1, committed: { action: "create", id: memory.id, type } };
|
|
255
|
+
}
|
|
256
|
+
|
|
209
257
|
function applyArchive(d, service, snapshot) {
|
|
210
258
|
const targets = d.ids.filter((id) => {
|
|
211
259
|
const mem = service.getById(id);
|