@modusensus/dsh-mneme 0.7.29 → 0.7.31
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 +5 -1
- package/lib/api.js +7 -1
- package/lib/client.js +39 -8
- package/lib/embedding.js +3 -0
- package/lib/index.js +32 -7
- package/lib/local-embedder.js +4 -0
- package/lib/store.js +4 -1
- package/package.json +5 -5
- package/scripts/e2e-dsh.js +6 -4
- package/src/api.js +7 -1
- package/src/embedding.js +3 -0
- package/src/index.js +32 -7
- package/src/local-embedder.js +4 -0
- package/src/store.js +4 -1
- package/test/api.test.js +35 -0
- package/test/local-embedder.test.js +2 -0
- package/test/store.test.js +9 -0
- package/README.en.md +0 -492
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
8
|
-
[](https://github.com/modusensus/dsh-mneme)
|
|
9
9
|
[](https://github.com/modusensus/dsh-mneme/actions)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
11
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
@@ -215,6 +215,8 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
215
215
|
|
|
216
216
|
| 版本 | 亮点 |
|
|
217
217
|
|------|------|
|
|
218
|
+
| **v0.7.31** | peerDependencies 宿主版本声明修复(#121):原 `^0.1.0-rc.6` 按 node-semver 预发布元组规则不匹配 `0.1.5-rc.1` 等中间预发布版本(当前 dsh 用户安装会 ERESOLVE),也违反 awesome-dsh-plugin 的 peer-range 预发布分支规范;改显式三段式预发布分支 `>=0.1.0-rc.6 <0.2.0 \|\| >=0.1.5-rc.0 <0.3.0 \|\| >=0.2.0-rc.0 <0.3.0`——覆盖全部已发布 0.1.x(含 `0.1.5-rc.1`)、0.2 预发布留显式分支、0.3+ 待验证后放开;745 测试全绿 |
|
|
219
|
+
| **v0.7.30** | 状态页向量卡修复(issue #118):改读开放的 `/semantic`——不再吃 `/vector-config` 的 401「加载失败」,ollama/local 模式不再恒显「未启用」;新增「初始化中(embedder 不可达,正在重试)」与「已索引 N / M 条」回填进度显示;legacy OpenAI 兼容 embedder「Object · 0D」显示修复(embedder 显式 `name: "OpenAI"` + 维度回退 `index.dimension`);「已索引 N / M」分子分母同口径(`embeddedCount` 剔除归档/遗忘,与 `count()` 默认过滤对齐);embedder init 失败有界重试(共 5 次,不再一次性永久降级,unload 清理定时器)+ `OllamaEmbedder.ready` 生命周期位 + `/semantic` 新增 `ready` 字段;745 测试全绿 |
|
|
218
220
|
| **v0.7.29** | 实体抽取路由契约修复 + 面板控件 + 帮助与反馈:`streamEntityText` 抽取为可测试导出的 `createEntityStreamAdapter`——显式 `provider`/`model` 优先、缺省兜底读默认路由选择;实体抽取思考强度 `entityExtractionReasoning` 被模型拒绝时自动去 effort 重试(与 autoDream/sleep 同一降级策略);设置面板新增实体抽取 `provider`/`model`/思考强度三控件;面板底部新增「帮助与反馈」卡片(GitHub issue 预填 + mailto `work@modusensus.space` + 浏览已知问题)+ 配套 `GET /api/dsh-mneme/info`;744 测试全绿 |
|
|
219
221
|
| **v0.7.28** | 连通性测试三连修:`POST /api/dsh-mneme/test-model` 最小调用 `maxTokens` 16→1024——思考模型的推理过程足以耗尽 16 token 预算致 `reply` 恒为空(「真的答了 ok 而非只报通」对思考模型不成立;按实际用量计费,手动按钮无放大成本);连通性测试状态按巩固/睡眠路由各持一份——此前共享单份,点任一「测试连通性」按钮两组同显「测试中/结果」且互相禁用;面板挂载草稿补 `sleepProvider`/`sleepModel`——后端一直保存正常,但重挂载(切页签/退出重进)后下拉恒显「跟随默认路由」,看起来像设置被重置,补键后正常回填(历史已存值无需重填);733 测试全绿 |
|
|
220
222
|
| **v0.7.27** | v0.7.26 端点遗漏补齐:v0.7.26 发版树未含 `fix/dream-effort-trap` 分支,CHANGELOG/Release 宣告的 `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` 两个端点实际不在包内(面板「级联下拉 + 测试连通性」会 404);本版将分支 rebase 后合入(PR #100),端点落地,面板连通性测试可用;733 测试全绿 |
|
|
@@ -315,6 +317,8 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
315
317
|
| **v0.7.27** | ✅ 完成 | v0.7.26 端点遗漏补齐 | v0.7.26 发版树未含 `fix/dream-effort-trap` 分支:CHANGELOG/Release 宣告的 `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` 两个端点实际不在包内(面板「级联下拉 + 测试连通性」会 404);本版将分支 rebase 合入(PR #100),端点落地、面板连通性测试可用;733 测试全绿 |
|
|
316
318
|
| **v0.7.28** | ✅ 完成 | 连通性测试三连修 | `POST /api/dsh-mneme/test-model` 最小调用 `maxTokens` 16→1024(思考模型推理过程即可耗尽 16 token 预算,`reply` 恒空);连通性测试状态按巩固/睡眠路由各持一份(按钮不再串扰);面板挂载草稿补 `sleepProvider`/`sleepModel`(重挂载后睡眠路由不再显示回「跟随默认路由」,值一直有存);733 测试全绿 |
|
|
317
319
|
| **v0.7.29** | ✅ 完成 | 实体抽取路由契约修复 + 面板控件 + 反馈入口 | 实体抽取 LLM 路由契约修复(#108/#109):`createEntityStreamAdapter` 显式 provider/model 优先 + 兜底默认路由 + effort 拒绝自动去重试;设置面板新增实体抽取 provider/model/思考强度三控件;「帮助与反馈」卡片(GitHub issue 预填 + mailto + 浏览已知问题)+ `GET /api/dsh-mneme/info`;744 测试全绿 |
|
|
320
|
+
| **v0.7.30** | ✅ 完成 | 状态页向量卡修复 + embedder 有界重试 | 卡片改读 `/semantic`(不再 401「加载失败」、ollama/local 不再恒显「未启用」)+「初始化中 / 已索引 N / M 条」显示(issue #118);legacy OpenAI 兼容 embedder 显式 `name: "OpenAI"` + 维度回退 `index.dimension`(「Object · 0D」修复);`embeddedCount` 对齐 `count()` 默认口径、剔除归档/遗忘(「已索引 269 / 36」修复);embedder init 失败 5 次有界重试不再一次性永久降级 + `OllamaEmbedder.ready` + `/semantic` `ready` 字段;745 测试全绿 |
|
|
321
|
+
| **v0.7.31** | ✅ 完成 | peerDependencies 宿主版本声明修复 | 原 `^0.1.0-rc.6` 按 node-semver 预发布元组规则不匹配 `0.1.5-rc.1` 等中间预发布版本(当前 dsh 用户安装 ERESOLVE),也违反 awesome-dsh-plugin 的 peer-range 预发布分支规范;改显式三段式预发布分支(覆盖 0.1.0-rc.6 至 0.1.5-rc.1 全部已发布 0.1.x 含预发布 + 0.2 预发布留显式分支 + 0.3+ 待验证后放开);745 测试全绿 |
|
|
318
322
|
| **v0.8.0** | 🚧 计划中(9 月末) | 图谱增强 | 兴趣漂移可视化 + scope 隔离(issue #17)+ 跨 workspace 记忆共享 |
|
|
319
323
|
|
|
320
324
|
> 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`failure_memories` 表与 autoDream 决策引擎已为后续反思性成长铺好路。
|
package/lib/api.js
CHANGED
|
@@ -605,7 +605,13 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
605
605
|
try {
|
|
606
606
|
const stats = semantic?.vectorIndex?.getStats?.() ?? null;
|
|
607
607
|
sendJson(res, 200, {
|
|
608
|
-
|
|
608
|
+
// #118: expose readiness so the status card can tell "initializing /
|
|
609
|
+
// unreachable" from "disabled". Legacy OpenAI embedder has no `ready`
|
|
610
|
+
// prop and is immediately usable, so treat that as ready.
|
|
611
|
+
ready: embedder ? ("ready" in embedder ? embedder.ready === true : true) : null,
|
|
612
|
+
// Explicit display name first: the legacy OpenAI-compatible embedder
|
|
613
|
+
// is an object literal, so constructor.name would be "Object".
|
|
614
|
+
embedProvider: embedder ? (embedder.name ?? embedder.constructor?.name ?? "unknown") : null,
|
|
609
615
|
modelHash: embedder?.modelHash ?? null,
|
|
610
616
|
dimension: embedder?.dimension ?? null,
|
|
611
617
|
reranker: semantic?.reranker ? "ready" : null,
|
package/lib/client.js
CHANGED
|
@@ -424,8 +424,10 @@ window.__ModuleLoader__.load({
|
|
|
424
424
|
"memory.status.memories": "记忆总数",
|
|
425
425
|
"memory.status.entities": "实体",
|
|
426
426
|
"memory.status.vector": "向量索引",
|
|
427
|
-
"memory.status.vectorOn": "已启用",
|
|
428
427
|
"memory.status.vectorOff": "未启用",
|
|
428
|
+
"memory.status.vectorInit": "初始化中",
|
|
429
|
+
"memory.status.vectorInitHint": "embedder 不可达,正在重试",
|
|
430
|
+
"memory.status.vectorIndexed": "已索引 {n} / {m} 条",
|
|
429
431
|
"memory.status.llm": "LLM 消耗",
|
|
430
432
|
"memory.status.llmCalls": "近 7 天 · {n} 次调用",
|
|
431
433
|
"memory.status.error": "加载失败"
|
|
@@ -727,8 +729,10 @@ window.__ModuleLoader__.load({
|
|
|
727
729
|
"memory.status.memories": "Total memories",
|
|
728
730
|
"memory.status.entities": "Entities",
|
|
729
731
|
"memory.status.vector": "Vector index",
|
|
730
|
-
"memory.status.vectorOn": "Enabled",
|
|
731
732
|
"memory.status.vectorOff": "Disabled",
|
|
733
|
+
"memory.status.vectorInit": "Initializing",
|
|
734
|
+
"memory.status.vectorInitHint": "embedder unreachable, retrying",
|
|
735
|
+
"memory.status.vectorIndexed": "Indexed {n} / {m} items",
|
|
732
736
|
"memory.status.llm": "LLM Usage",
|
|
733
737
|
"memory.status.llmCalls": "Last 7 days · {n} calls",
|
|
734
738
|
"memory.status.error": "Failed to load"
|
|
@@ -2583,22 +2587,49 @@ window.__ModuleLoader__.load({
|
|
|
2583
2587
|
|
|
2584
2588
|
// 向量索引 — whether semantic recall is switched on.
|
|
2585
2589
|
function VectorStatusCard({ t }) {
|
|
2586
|
-
const [state, setState] = useState({ loading: true, error: false,
|
|
2590
|
+
const [state, setState] = useState({ loading: true, error: false, provider: null, ready: null, dimension: 0, embedded: 0, total: 0 });
|
|
2587
2591
|
useEffect(() => {
|
|
2588
2592
|
let cancelled = false;
|
|
2589
|
-
|
|
2593
|
+
// #118: /vector-config is secret-bearing (401 without a stored token →
|
|
2594
|
+
// "加载失败") and its `enabled` only reflects the OpenAI-compat external
|
|
2595
|
+
// service, so ollama/local mode always showed "未启用". /semantic is open
|
|
2596
|
+
// and covers every embedder provider + index stats.
|
|
2597
|
+
apiFetch("/api/dsh-mneme/semantic")
|
|
2590
2598
|
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
2591
|
-
.then((j) => {
|
|
2592
|
-
|
|
2599
|
+
.then((j) => {
|
|
2600
|
+
if (cancelled) return;
|
|
2601
|
+
setState({
|
|
2602
|
+
loading: false,
|
|
2603
|
+
error: false,
|
|
2604
|
+
provider: j?.embedProvider || null,
|
|
2605
|
+
ready: j?.ready ?? null,
|
|
2606
|
+
// Legacy OpenAI embedder exposes dimension only after its first
|
|
2607
|
+
// successful embed — fall back to the index stats' dimension.
|
|
2608
|
+
dimension: Number(j?.dimension ?? j?.index?.dimension ?? 0),
|
|
2609
|
+
embedded: Number(j?.index?.embeddedCount ?? 0),
|
|
2610
|
+
total: Number(j?.index?.totalCount ?? 0)
|
|
2611
|
+
});
|
|
2612
|
+
})
|
|
2613
|
+
.catch(() => { if (!cancelled) setState({ loading: false, error: true, provider: null, ready: null, dimension: 0, embedded: 0 }); });
|
|
2593
2614
|
return () => { cancelled = true; };
|
|
2594
2615
|
}, []);
|
|
2616
|
+
const off = !state.provider;
|
|
2617
|
+
const pending = !off && state.ready !== true;
|
|
2618
|
+
const num = off
|
|
2619
|
+
? t("memory.status.vectorOff")
|
|
2620
|
+
: pending
|
|
2621
|
+
? t("memory.status.vectorInit")
|
|
2622
|
+
: `${state.provider.replace(/Embedder$/, "")} · ${state.dimension}D`;
|
|
2623
|
+
const cap = pending
|
|
2624
|
+
? t("memory.status.vectorInitHint")
|
|
2625
|
+
: off ? "" : t("memory.status.vectorIndexed").replace("{n}", state.embedded).replace("{m}", state.total);
|
|
2595
2626
|
return h(StatusCard, {
|
|
2596
2627
|
t,
|
|
2597
2628
|
title: t("memory.status.vector"),
|
|
2598
2629
|
loading: state.loading,
|
|
2599
2630
|
error: state.error,
|
|
2600
|
-
num
|
|
2601
|
-
cap
|
|
2631
|
+
num,
|
|
2632
|
+
cap
|
|
2602
2633
|
});
|
|
2603
2634
|
}
|
|
2604
2635
|
|
package/lib/embedding.js
CHANGED
|
@@ -97,6 +97,9 @@ export function createEmbedder({ store, settings, logger, vectorIndex }) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
return {
|
|
100
|
+
// Display name for /semantic: a literal's constructor.name is "Object",
|
|
101
|
+
// which the status card would render verbatim.
|
|
102
|
+
name: "OpenAI",
|
|
100
103
|
/** Fire-and-forget re-embed of a memory after any write. */
|
|
101
104
|
schedule(memory) {
|
|
102
105
|
if (!memory?.id) return;
|
package/lib/index.js
CHANGED
|
@@ -199,6 +199,8 @@ export const apply = (ctx, config) => {
|
|
|
199
199
|
|
|
200
200
|
let embedder = null;
|
|
201
201
|
let reranker = null;
|
|
202
|
+
// #118: pending embedder-init retry timer, cleared on unload.
|
|
203
|
+
let embedRetryTimer = null;
|
|
202
204
|
if (lightMode) {
|
|
203
205
|
// Light mode: the whole vector pipeline stays off — no embedder (nothing
|
|
204
206
|
// pulls in ONNX/transformers), no reranker, no boot backfill (the preset
|
|
@@ -226,13 +228,32 @@ export const apply = (ctx, config) => {
|
|
|
226
228
|
service.setEmbedder(embedder);
|
|
227
229
|
// issue #6: wait for extractor init before applying human edits, so
|
|
228
230
|
// scheduled embeddings see a ready embedder.
|
|
229
|
-
embedder.init()
|
|
230
|
-
.then(() => applyHumanEdits())
|
|
231
|
-
.catch((
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
231
|
+
const bootEmbedder = () => embedder.init()
|
|
232
|
+
.then(() => { applyHumanEdits(); return true; })
|
|
233
|
+
.catch(() => false);
|
|
234
|
+
// #118: the old one-shot probe permanently degraded search to keyword
|
|
235
|
+
// when Ollama was briefly unreachable at boot (recoverable only by
|
|
236
|
+
// restart). Retry briefly (5 attempts total: 1 initial + 4 × 15s);
|
|
237
|
+
// search degrades to keyword meanwhile because per-query embed failures
|
|
238
|
+
// are swallowed.
|
|
239
|
+
bootEmbedder().then((ok) => {
|
|
240
|
+
if (ok) return;
|
|
241
|
+
let tries = 4;
|
|
242
|
+
const retry = () => {
|
|
243
|
+
if (tries-- <= 0) {
|
|
244
|
+
ctx.logger?.warn?.("[dsh-mneme] embedder init retries exhausted, search degrades to keyword");
|
|
245
|
+
service.setEmbedder(null);
|
|
246
|
+
applyHumanEdits();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
embedRetryTimer = setTimeout(async () => {
|
|
250
|
+
if (await bootEmbedder()) return;
|
|
251
|
+
retry();
|
|
252
|
+
}, 15_000);
|
|
253
|
+
};
|
|
254
|
+
ctx.logger?.warn?.("[dsh-mneme] embedder init failed, retrying");
|
|
255
|
+
retry();
|
|
256
|
+
});
|
|
236
257
|
} catch (error) {
|
|
237
258
|
ctx.logger?.warn?.(`[dsh-mneme] embedder unavailable, search degrades to keyword: ${String(error)}`);
|
|
238
259
|
applyHumanEdits();
|
|
@@ -396,6 +417,10 @@ export const apply = (ctx, config) => {
|
|
|
396
417
|
|
|
397
418
|
const disposers = [];
|
|
398
419
|
|
|
420
|
+
// #118: never let a pending embedder init retry fire after unload and touch
|
|
421
|
+
// a torn-down context.
|
|
422
|
+
disposers.push(() => { if (embedRetryTimer !== null) clearTimeout(embedRetryTimer); });
|
|
423
|
+
|
|
399
424
|
ctx.inject(["systemPrompt"], (promptCtx) => {
|
|
400
425
|
if (cfg.autoInject) disposers.push(createInjector(promptCtx, service, settings, cfg));
|
|
401
426
|
});
|
package/lib/local-embedder.js
CHANGED
|
@@ -139,6 +139,9 @@ export class OllamaEmbedder {
|
|
|
139
139
|
this.model = String(opts.model ?? "nomic-embed-text").trim();
|
|
140
140
|
this.logger = opts.logger ?? null;
|
|
141
141
|
this._dimension = null;
|
|
142
|
+
// #118: async-init embedders expose `ready` so the service queues re-embeds
|
|
143
|
+
// until init lands and the status card can show "initializing".
|
|
144
|
+
this.ready = false;
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
async _post(body) {
|
|
@@ -157,6 +160,7 @@ export class OllamaEmbedder {
|
|
|
157
160
|
const body = await res.json();
|
|
158
161
|
if (!Array.isArray(body?.embedding)) throw new Error(`Ollama ${this.model} returned no embedding`);
|
|
159
162
|
this._dimension = body.embedding.length;
|
|
163
|
+
this.ready = true;
|
|
160
164
|
this.logger?.info?.(
|
|
161
165
|
`[dsh-mneme] ollama embedder ready: ${this.model} (dim=${this._dimension})`
|
|
162
166
|
);
|
package/lib/store.js
CHANGED
|
@@ -1023,8 +1023,11 @@ export function createStore(path) {
|
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
1025
|
function embeddedCount() {
|
|
1026
|
+
// Active rows only — getStats pairs this with count() as the status card's
|
|
1027
|
+
// "indexed N / M" denominator, and count() defaults exclude forgotten and
|
|
1028
|
+
// archived memories. Unfiltered, archived rows inflate N past M.
|
|
1026
1029
|
return db.prepare(
|
|
1027
|
-
"SELECT count(*) AS c FROM memories WHERE embedding IS NOT NULL AND embedding != ''"
|
|
1030
|
+
"SELECT count(*) AS c FROM memories WHERE embedding IS NOT NULL AND embedding != '' AND forgotten = 0 AND archived = 0"
|
|
1028
1031
|
).get().c;
|
|
1029
1032
|
}
|
|
1030
1033
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modusensus/dsh-mneme",
|
|
3
3
|
"description": "Cross-session memory plugin for DeepSeek Harness with autoDream consolidation: SQLite store, Markdown mirrors, 7 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.31",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
50
|
-
"@deepseek-ai/dsh-host-webserver": "
|
|
51
|
-
"@deepseek-ai/dsh-llm": "
|
|
52
|
-
"@deepseek-ai/dsh-system-prompt": "
|
|
53
|
-
"@deepseek-ai/dsh-tools": "
|
|
50
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
51
|
+
"@deepseek-ai/dsh-llm": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
52
|
+
"@deepseek-ai/dsh-system-prompt": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
53
|
+
"@deepseek-ai/dsh-tools": ">=0.1.0-rc.6 <0.2.0 || >=0.1.5-rc.0 <0.3.0 || >=0.2.0-rc.0 <0.3.0",
|
|
54
54
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
55
55
|
"dsh-better-sidebar": "^0.18.0"
|
|
56
56
|
},
|
package/scripts/e2e-dsh.js
CHANGED
|
@@ -104,14 +104,16 @@ console.log(`记忆目录:${memDir}\n`);
|
|
|
104
104
|
// 1. 装载检查
|
|
105
105
|
console.log("【1】插件装载");
|
|
106
106
|
const checks = [];
|
|
107
|
-
|
|
107
|
+
// 硬编码精确数会随插件演进过时(v0.7 起已有 8 工具 / 26+ 路由,早期写死的
|
|
108
|
+
// 7/14 导致新旧 dsh 上 e2e 都误报失败);改用「下限」检查 + 打印实际数量。
|
|
109
|
+
checks.push(["注册 ≥7 个模型工具", registeredTools.length >= 7]);
|
|
108
110
|
checks.push(["注册 2 个注入上下文", injectContexts.length === 2 && injectContexts[0].name === "memory"]);
|
|
109
|
-
// 契约是 14 条 exact 路由(v0.3 实体清单接口后由 13 条扩到 14 条);
|
|
110
111
|
// prefix fallback(/api/dsh-mneme → 404)是兜底,不计入路由数。
|
|
111
|
-
checks.push(["注册 14 条 API 路由", apiRoutes.filter((r) => r.kind === "exact").length
|
|
112
|
+
checks.push(["注册 ≥14 条 API 路由", apiRoutes.filter((r) => r.kind === "exact").length >= 14]);
|
|
112
113
|
for (const [label, ok] of checks) console.log(` ${ok ? "✅" : "❌"} ${label}`);
|
|
113
114
|
if (!checks.every(([, ok]) => ok)) { console.log("\n装载检查失败,中止。"); process.exit(1); }
|
|
114
|
-
console.log(`
|
|
115
|
+
console.log(` 工具(${registeredTools.length}):${registeredTools.map((t) => t.name).join(", ")}`);
|
|
116
|
+
console.log(` exact 路由:${apiRoutes.filter((r) => r.kind === "exact").length} 条\n`);
|
|
115
117
|
|
|
116
118
|
// 2. 保存记忆(工具执行)
|
|
117
119
|
console.log("【2】memory_save 保存记忆");
|
package/src/api.js
CHANGED
|
@@ -605,7 +605,13 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
605
605
|
try {
|
|
606
606
|
const stats = semantic?.vectorIndex?.getStats?.() ?? null;
|
|
607
607
|
sendJson(res, 200, {
|
|
608
|
-
|
|
608
|
+
// #118: expose readiness so the status card can tell "initializing /
|
|
609
|
+
// unreachable" from "disabled". Legacy OpenAI embedder has no `ready`
|
|
610
|
+
// prop and is immediately usable, so treat that as ready.
|
|
611
|
+
ready: embedder ? ("ready" in embedder ? embedder.ready === true : true) : null,
|
|
612
|
+
// Explicit display name first: the legacy OpenAI-compatible embedder
|
|
613
|
+
// is an object literal, so constructor.name would be "Object".
|
|
614
|
+
embedProvider: embedder ? (embedder.name ?? embedder.constructor?.name ?? "unknown") : null,
|
|
609
615
|
modelHash: embedder?.modelHash ?? null,
|
|
610
616
|
dimension: embedder?.dimension ?? null,
|
|
611
617
|
reranker: semantic?.reranker ? "ready" : null,
|
package/src/embedding.js
CHANGED
|
@@ -97,6 +97,9 @@ export function createEmbedder({ store, settings, logger, vectorIndex }) {
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
return {
|
|
100
|
+
// Display name for /semantic: a literal's constructor.name is "Object",
|
|
101
|
+
// which the status card would render verbatim.
|
|
102
|
+
name: "OpenAI",
|
|
100
103
|
/** Fire-and-forget re-embed of a memory after any write. */
|
|
101
104
|
schedule(memory) {
|
|
102
105
|
if (!memory?.id) return;
|
package/src/index.js
CHANGED
|
@@ -199,6 +199,8 @@ export const apply = (ctx, config) => {
|
|
|
199
199
|
|
|
200
200
|
let embedder = null;
|
|
201
201
|
let reranker = null;
|
|
202
|
+
// #118: pending embedder-init retry timer, cleared on unload.
|
|
203
|
+
let embedRetryTimer = null;
|
|
202
204
|
if (lightMode) {
|
|
203
205
|
// Light mode: the whole vector pipeline stays off — no embedder (nothing
|
|
204
206
|
// pulls in ONNX/transformers), no reranker, no boot backfill (the preset
|
|
@@ -226,13 +228,32 @@ export const apply = (ctx, config) => {
|
|
|
226
228
|
service.setEmbedder(embedder);
|
|
227
229
|
// issue #6: wait for extractor init before applying human edits, so
|
|
228
230
|
// scheduled embeddings see a ready embedder.
|
|
229
|
-
embedder.init()
|
|
230
|
-
.then(() => applyHumanEdits())
|
|
231
|
-
.catch((
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
231
|
+
const bootEmbedder = () => embedder.init()
|
|
232
|
+
.then(() => { applyHumanEdits(); return true; })
|
|
233
|
+
.catch(() => false);
|
|
234
|
+
// #118: the old one-shot probe permanently degraded search to keyword
|
|
235
|
+
// when Ollama was briefly unreachable at boot (recoverable only by
|
|
236
|
+
// restart). Retry briefly (5 attempts total: 1 initial + 4 × 15s);
|
|
237
|
+
// search degrades to keyword meanwhile because per-query embed failures
|
|
238
|
+
// are swallowed.
|
|
239
|
+
bootEmbedder().then((ok) => {
|
|
240
|
+
if (ok) return;
|
|
241
|
+
let tries = 4;
|
|
242
|
+
const retry = () => {
|
|
243
|
+
if (tries-- <= 0) {
|
|
244
|
+
ctx.logger?.warn?.("[dsh-mneme] embedder init retries exhausted, search degrades to keyword");
|
|
245
|
+
service.setEmbedder(null);
|
|
246
|
+
applyHumanEdits();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
embedRetryTimer = setTimeout(async () => {
|
|
250
|
+
if (await bootEmbedder()) return;
|
|
251
|
+
retry();
|
|
252
|
+
}, 15_000);
|
|
253
|
+
};
|
|
254
|
+
ctx.logger?.warn?.("[dsh-mneme] embedder init failed, retrying");
|
|
255
|
+
retry();
|
|
256
|
+
});
|
|
236
257
|
} catch (error) {
|
|
237
258
|
ctx.logger?.warn?.(`[dsh-mneme] embedder unavailable, search degrades to keyword: ${String(error)}`);
|
|
238
259
|
applyHumanEdits();
|
|
@@ -396,6 +417,10 @@ export const apply = (ctx, config) => {
|
|
|
396
417
|
|
|
397
418
|
const disposers = [];
|
|
398
419
|
|
|
420
|
+
// #118: never let a pending embedder init retry fire after unload and touch
|
|
421
|
+
// a torn-down context.
|
|
422
|
+
disposers.push(() => { if (embedRetryTimer !== null) clearTimeout(embedRetryTimer); });
|
|
423
|
+
|
|
399
424
|
ctx.inject(["systemPrompt"], (promptCtx) => {
|
|
400
425
|
if (cfg.autoInject) disposers.push(createInjector(promptCtx, service, settings, cfg));
|
|
401
426
|
});
|
package/src/local-embedder.js
CHANGED
|
@@ -139,6 +139,9 @@ export class OllamaEmbedder {
|
|
|
139
139
|
this.model = String(opts.model ?? "nomic-embed-text").trim();
|
|
140
140
|
this.logger = opts.logger ?? null;
|
|
141
141
|
this._dimension = null;
|
|
142
|
+
// #118: async-init embedders expose `ready` so the service queues re-embeds
|
|
143
|
+
// until init lands and the status card can show "initializing".
|
|
144
|
+
this.ready = false;
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
async _post(body) {
|
|
@@ -157,6 +160,7 @@ export class OllamaEmbedder {
|
|
|
157
160
|
const body = await res.json();
|
|
158
161
|
if (!Array.isArray(body?.embedding)) throw new Error(`Ollama ${this.model} returned no embedding`);
|
|
159
162
|
this._dimension = body.embedding.length;
|
|
163
|
+
this.ready = true;
|
|
160
164
|
this.logger?.info?.(
|
|
161
165
|
`[dsh-mneme] ollama embedder ready: ${this.model} (dim=${this._dimension})`
|
|
162
166
|
);
|
package/src/store.js
CHANGED
|
@@ -1023,8 +1023,11 @@ export function createStore(path) {
|
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
1025
1025
|
function embeddedCount() {
|
|
1026
|
+
// Active rows only — getStats pairs this with count() as the status card's
|
|
1027
|
+
// "indexed N / M" denominator, and count() defaults exclude forgotten and
|
|
1028
|
+
// archived memories. Unfiltered, archived rows inflate N past M.
|
|
1026
1029
|
return db.prepare(
|
|
1027
|
-
"SELECT count(*) AS c FROM memories WHERE embedding IS NOT NULL AND embedding != ''"
|
|
1030
|
+
"SELECT count(*) AS c FROM memories WHERE embedding IS NOT NULL AND embedding != '' AND forgotten = 0 AND archived = 0"
|
|
1028
1031
|
).get().c;
|
|
1029
1032
|
}
|
|
1030
1033
|
|
package/test/api.test.js
CHANGED
|
@@ -435,6 +435,41 @@ test("no apiToken configured keeps all endpoints open", async () => {
|
|
|
435
435
|
assert.equal(res.statusCode, 200, "open when apiToken is unset");
|
|
436
436
|
});
|
|
437
437
|
|
|
438
|
+
// --- #118: /semantic exposes embedder readiness for the status card ----------
|
|
439
|
+
|
|
440
|
+
test("GET /api/dsh-mneme/semantic reports ready state per embedder", async () => {
|
|
441
|
+
const fetchSem = async (embedder) => {
|
|
442
|
+
const { routes } = setup(embedder);
|
|
443
|
+
const sem = routes.find((r) => r.path === "/api/dsh-mneme/semantic");
|
|
444
|
+
const res = new FakeRes();
|
|
445
|
+
await sem.handler(req("/api/dsh-mneme/semantic"), res);
|
|
446
|
+
assert.equal(res.statusCode, 200);
|
|
447
|
+
return JSON.parse(res.body);
|
|
448
|
+
};
|
|
449
|
+
// no embedder → everything null
|
|
450
|
+
assert.equal((await fetchSem(undefined)).embedProvider, null);
|
|
451
|
+
assert.equal((await fetchSem(undefined)).ready, null, "no embedder → ready null");
|
|
452
|
+
|
|
453
|
+
// Ollama-style embedder mid-init (ready:false) → ready false
|
|
454
|
+
class OllamaEmbedder { constructor() { this.ready = false; } }
|
|
455
|
+
const mid = await fetchSem(new OllamaEmbedder());
|
|
456
|
+
assert.equal(mid.embedProvider, "OllamaEmbedder");
|
|
457
|
+
assert.equal(mid.ready, false, "not-yet-ready embedder → ready false");
|
|
458
|
+
|
|
459
|
+
// ready after init
|
|
460
|
+
const ready = new OllamaEmbedder(); ready.ready = true;
|
|
461
|
+
assert.equal((await fetchSem(ready)).ready, true);
|
|
462
|
+
|
|
463
|
+
// legacy OpenAI embedder has no `ready` prop → treated as ready
|
|
464
|
+
assert.equal((await fetchSem({ embed() {} })).ready, true, "no ready prop → assumed ready");
|
|
465
|
+
|
|
466
|
+
// legacy embedder carries an explicit display name (constructor.name of a
|
|
467
|
+
// literal is "Object", which the status card must not render verbatim)
|
|
468
|
+
const named = await fetchSem({ name: "OpenAI", embed() {} });
|
|
469
|
+
assert.equal(named.embedProvider, "OpenAI", "explicit name beats constructor.name");
|
|
470
|
+
assert.equal(named.ready, true);
|
|
471
|
+
});
|
|
472
|
+
|
|
438
473
|
// --- Bug8: llm-audit API (pagination + stats) --------------------------------
|
|
439
474
|
|
|
440
475
|
test("GET /api/dsh-mneme/semantic/llm-audit returns paginated rows", async () => {
|
|
@@ -116,7 +116,9 @@ test("OllamaEmbedder init probes server and infers dimension", async () => {
|
|
|
116
116
|
});
|
|
117
117
|
try {
|
|
118
118
|
const e = new OllamaEmbedder({ baseUrl: "http://localhost:11434", model: "nomic-embed-text" });
|
|
119
|
+
assert.equal(e.ready, false, "not ready before init");
|
|
119
120
|
await e.init();
|
|
121
|
+
assert.equal(e.ready, true, "ready after init");
|
|
120
122
|
assert.equal(e.dimension, 768);
|
|
121
123
|
} finally {
|
|
122
124
|
restore();
|
package/test/store.test.js
CHANGED
|
@@ -242,6 +242,15 @@ test("setEmbedding, embeddedCount, needsEmbedding and threshold filtering", () =
|
|
|
242
242
|
assert.equal(store.embeddedCount(), 0);
|
|
243
243
|
store.setEmbedding(m2.id, [0, 1]);
|
|
244
244
|
assert.equal(store.embeddedCount(), 1);
|
|
245
|
+
// Status-card denominator alignment: count() defaults exclude archived and
|
|
246
|
+
// forgotten rows, so embeddedCount must drop them too.
|
|
247
|
+
store.setArchived(m2.id, true);
|
|
248
|
+
assert.equal(store.embeddedCount(), 0, "archived rows leave the embedded count");
|
|
249
|
+
store.setArchived(m2.id, false);
|
|
250
|
+
store.setForget(m2.id, true);
|
|
251
|
+
assert.equal(store.embeddedCount(), 0, "forgotten rows leave the embedded count");
|
|
252
|
+
store.setForget(m2.id, false);
|
|
253
|
+
assert.equal(store.embeddedCount(), 1);
|
|
245
254
|
const missing = store.needsEmbedding(10);
|
|
246
255
|
assert.equal(missing.length, 1);
|
|
247
256
|
assert.equal(missing[0].id, t1.id, "only the non-embedded row is listed");
|
package/README.en.md
DELETED
|
@@ -1,492 +0,0 @@
|
|
|
1
|
-
<p align="center"><strong>中文 | <a href="docs/SEMANTIC.md">English (Semantic)</a></strong></p>
|
|
2
|
-
|
|
3
|
-
# dsh-mneme
|
|
4
|
-
|
|
5
|
-
English | [简体中文](README.md)
|
|
6
|
-
|
|
7
|
-
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
8
|
-
[](LICENSE)
|
|
9
|
-
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
10
|
-
[](https://github.com/modusensus/dsh-mneme)
|
|
11
|
-
|
|
12
|
-
> A cross-session memory plugin for DeepSeek Harness: it lets the Agent remember you, remember your projects, and organize memories automatically. **Mneme** (Μνήμη) — named after Mnemosyne, the Greek goddess of memory who presides over memory and dreams, just as autoDream consolidates memories in the background.
|
|
13
|
-
|
|
14
|
-
`dsh-mneme` is a [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) plugin that gives Agents persistent cross-session memory. It draws on Claude's **Dream mechanism** and the **autoDream** implementation ideas from cc-haha / Claude Code — it does not just **store** memories, it also **consolidates them automatically** (deduplication, merging, conflict adjudication, summary generation), so the memory store keeps getting more refined with use.
|
|
15
|
-
|
|
16
|
-
## ✨ Features
|
|
17
|
-
|
|
18
|
-
### Memory Storage (SQLite + Markdown Mirror)
|
|
19
|
-
|
|
20
|
-
- **SQLite primary storage**: `~/.dsh/memory/memory.db`, built-in `node:sqlite`, zero native dependencies
|
|
21
|
-
- **Markdown mirror**: `preferences.md` / `projects.md` / `decisions.md` / `history.md` / `summary.md` — human-readable and hand-editable (**manual edits take priority** when merged back into the store)
|
|
22
|
-
- **9 memory types**: `preference` / `project` / `decision` / `history` / `summary` / `pattern` + coding-retrospect types `rejected_solution` / `pitfall` / `constraint` (v0.7.13, `codingRetrospect` opt-in; the `user`/`fact` types were dropped in the v0.7.12 rewrite)
|
|
23
|
-
- **Mirror sync state machine (v0.3.6+)**: the mirror is strongly consistent with the primary store, with sync debt modeled via `generation` (desired round) / `applied_generation` (applied round)
|
|
24
|
-
- Business write operations **atomically increment** the desired generation **within their own transaction** — even a crash after COMMIT but before rendering recovers on restart from the durable debt, never silently skipped (v0.3.8)
|
|
25
|
-
- `generation` is incremented with atomic SQLite statements — zero loss under multi-process concurrency; a `CHECK` upper bound rejects negative values/overflow
|
|
26
|
-
- Per-type `committed / failed / pending` receipts; the health endpoint distinguishes `ok / degraded / unknown`
|
|
27
|
-
- State write failures are never silent: sync failures are logged and leave debt behind, converging automatically on restart
|
|
28
|
-
|
|
29
|
-
### Model Tools (8)
|
|
30
|
-
|
|
31
|
-
| Tool | Function |
|
|
32
|
-
|------|------|
|
|
33
|
-
| `memory_save` | Save a memory (automatic dedup and merge by title) |
|
|
34
|
-
| `memory_search` | Full-text search (Chinese-substring friendly; vector semantic search can be enabled) |
|
|
35
|
-
| `memory_list` | Paginated listing by type (`include_archived=true` to view archived items) |
|
|
36
|
-
| `memory_get` | Read a single memory's full body by id (v0.7.25; read the full text after a search/list hit) |
|
|
37
|
-
| `memory_update` | Modify an existing memory |
|
|
38
|
-
| `memory_delete` | Delete a memory |
|
|
39
|
-
| `memory_forget` | Suppress injection (down-weighted rather than deleted; recoverable) |
|
|
40
|
-
| `memory_archive` | Archive/restore memories (v0.2.5; archived items are hidden from listing/search/injection/consolidation, `archived=false` restores them) |
|
|
41
|
-
|
|
42
|
-
### Auto Injection + Session Summary
|
|
43
|
-
|
|
44
|
-
- **Auto injection**: at the start of a new session, a memory digest is injected (the `summary` first, plus a few high-importance items)
|
|
45
|
-
- **Session summary**: at `turn/end`, an LLM distills the preferences/decisions/lessons of the session and stores them automatically (plugin-injected context is filtered out to avoid contamination)
|
|
46
|
-
|
|
47
|
-
### autoDream Automatic Memory Consolidation 🧠
|
|
48
|
-
|
|
49
|
-
- **Trigger**: fires asynchronously and automatically once memory count > 10 or total characters > 5000 (never blocks writes)
|
|
50
|
-
- **Decision-list consolidation**: the LLM outputs a decision list of `keep` / `merge` / `archive` / `conflict` / `update` decisions, which the server validates and then applies item by item
|
|
51
|
-
- `merge`: merges entries with similar topics, keeping the most information-complete one
|
|
52
|
-
- `archive`: archives outdated/redundant entries (recoverable, never physically deleted)
|
|
53
|
-
- `conflict`: adjudicates contradictory information — the winner is kept, the loser is archived with a provenance note appended
|
|
54
|
-
- `update` (v0.2.1): directly corrects outdated/incorrect content of a single memory (single id / must actually change / not `summary` / 24h protection / ≤2 per run)
|
|
55
|
-
- **Failure tracking (v0.2.1)**: when the user corrects a memory, it is written to the `failure_memories` table (old value/new value), accumulating data for future self-evolution
|
|
56
|
-
- **Summary generation**: after consolidation, a "memory store overview" (single instance) is generated, injected with priority in the next session
|
|
57
|
-
- **Fail-safe**: "individually invalid" decisions (unknown id / invalid action / cross-type merge / out-of-range importance, Issue #26) are skipped and the valid subset applied — the run is marked `degraded` and the memory store is never corrupted; only global errors (coverage shortfall / update overflow) reject the whole list
|
|
58
|
-
- **Adjudication audit**: every run writes to the `dream_runs` audit table (input snapshot sha256 digest + full input snapshot + decision list + per-id disposition + receipt), replayable offline; merge / conflict / update are applied idempotently — replays and concurrent duplicate runs have no cumulative side effects; `update` records a `_before` snapshot
|
|
59
|
-
|
|
60
|
-
#### dreamMaxTokens Tuning Guide
|
|
61
|
-
|
|
62
|
-
The default `32768` reserves headroom for reasoning models, where reasoning alone may consume 8k+ tokens before any body output. For **larger memory stores** scale it up by size:
|
|
63
|
-
|
|
64
|
-
| Memory store size | Recommended `dreamMaxTokens` |
|
|
65
|
-
|-----------|----------------------|
|
|
66
|
-
| Ordinary (<10k chars) | `32768` (default) |
|
|
67
|
-
| Medium (10k–50k chars) | `65536` |
|
|
68
|
-
| Large (>50k chars) | `131072` (cap) |
|
|
69
|
-
|
|
70
|
-
> With **reasoning models** (e.g. DeepSeek-R1-like), the model may spend the entire budget on reasoning and return an empty body (the log shows `no json array in llm output`). Resolution order: ① set `dreamReasoningEffort` to `low` to suppress reasoning overhead (v0.7.26+: if the model doesn't support that tier, `resolveDreamEffort` auto-falls back to the model's default/first supported tier or omits the field — the rejection reason lands in `llm_audit`); ② if the retry still returns an empty body under the model's default reasoning behavior, raise `dreamMaxTokens` (reasoning and body share this budget) or route `dreamProvider`/`dreamModel` to a non-reasoning model. The sleep side has the corresponding `sleepReasoningEffort`.
|
|
71
|
-
|
|
72
|
-
**Consolidation model classification** (settings panel "consolidation model" = `dreamProvider`/`dreamModel`; sleep side: `sleepProvider`/`sleepModel`):
|
|
73
|
-
|
|
74
|
-
| Model kind | Examples | Notes |
|
|
75
|
-
|-----------|----------|-------|
|
|
76
|
-
| **Non-reasoning (recommended)** | glm-5-2-class | No reasoning declaration; even if an effort is configured and the harness rejects it, the fallback strips the field and the retry succeeds. Lowest risk of empty-body runs |
|
|
77
|
-
| **Reasoning (test first)** | deepseek-v4-flash-ga and other v4-flash-ga family | Reasons by default and may burn the whole token budget on an empty body; some SKUs (e.g. v4-flash-ga) are additionally declared by the harness as accepting **no reasoning effort at all** (the no-effort retry gets rejected through the harness `defaultEffort`, `UNSUPPORTED_REASONING_EFFORT`). Fixed in v0.7.26+: `resolveDreamEffort` probes the model's supported effort tiers before streaming — an unsupported configured tier auto-falls back to the model's default/first supported tier, and the field is omitted for models that declare no reasoning capability. If you use one, set `dreamReasoningEffort` and test; switch to a non-reasoning model otherwise |
|
|
78
|
-
|
|
79
|
-
### Sleep Mode: System-Level Sleep 💤 (v0.4.0, opt-in)
|
|
80
|
-
|
|
81
|
-
Upgrades autoDream's "passive threshold triggering" into "proactive scheduled maintenance + tiered compression". Once the system has been idle for `sleepIdleMinutes` minutes, deep maintenance runs automatically. **Off by default** (`sleepModeEnabled: false`); once enabled:
|
|
82
|
-
|
|
83
|
-
- **Interruptible**: implemented with AbortController — user activity aborts the current cycle (`noteWrite` resets the idle timer + the abort signal)
|
|
84
|
-
- **Serially safe**: sleep cycles go through the `service.enqueue` serial queue, strictly non-overlapping with autoDream; `minRefTimeMs` prevents memories recalled after the snapshot from being mistakenly demoted
|
|
85
|
-
- **Four-phase deep maintenance**:
|
|
86
|
-
1. `conflict_resolution`: store-wide conflict resolution, strictness configurable in three levels (gentle 0.92 / normal 0.85 / aggressive 0.75)
|
|
87
|
-
2. `archival_demotion`: tiered by `last_accessed_at` — not recalled for 30 days → compressed into a summary (original text kept in `_full_content`, losslessly restorable); 90 days → fully archived
|
|
88
|
-
3. `pattern_discovery`: the LLM scans recent memories to distill patterns, producing `type=pattern` memories, with strict evidence validation to prevent fabrication
|
|
89
|
-
4. `relation_completion`: detects orphaned entities and completes implicit relations (co-occurrence `related_to` / project `part_of` / technology `depends_on`)
|
|
90
|
-
- **Fail-safe**: each phase has its own try/catch — an LLM failure only skips the corresponding phase; without an LLM route, the pure-rule fallbacks (demotion/relations) still run
|
|
91
|
-
- **Audit continuity**: sleep cycles write to `dream_runs` with `run_type='sleep'`, sharing the audit table with autoDream for traceability
|
|
92
|
-
|
|
93
|
-
> See `docs/SLEEP.md` for configuration; see `docs/MIGRATION.md` for migration notes.
|
|
94
|
-
|
|
95
|
-
### Web Memory Panel
|
|
96
|
-
|
|
97
|
-
Official settings panel → "Memory Store Settings" → "Memory" tab: browse by type, full-text search; once vector search is enabled, the "Semantic" toggle becomes available for vector recall.
|
|
98
|
-
|
|
99
|
-
### User Settings (Profile / Rules) and Custom Commands ⚙️
|
|
100
|
-
|
|
101
|
-
Official settings panel → "Memory Store Settings" tab:
|
|
102
|
-
|
|
103
|
-
- **User profile**: a free-text passage describing the user (role, background, preferences), **injected into the system prompt every turn** so the Agent always follows it
|
|
104
|
-
- **Rules**: a list of behavioral rules the Agent must obey (e.g. "give the conclusion first"), likewise injected every turn
|
|
105
|
-
- **Custom commands**: register slash commands (`/name`); when triggered, the user-defined instruction content is handed to the Agent. Commands persist to SQLite, are automatically registered into the DSH command table at startup, and adding/removing takes effect in real time
|
|
106
|
-
|
|
107
|
-
> Profile and rules are injected through a separate `[用户设置]` (User Settings) block (higher priority than the memory store), and are injected even when memories are empty.
|
|
108
|
-
|
|
109
|
-
### Vector Search (Semantic Search) 🔎
|
|
110
|
-
|
|
111
|
-
An optional capability: connect an OpenAI-compatible embeddings API so search can hit memories that are **literally different but semantically close**.
|
|
112
|
-
|
|
113
|
-
**Configuration**: official settings → "Memory Store Settings" → scroll to the bottom "Vector Search" section:
|
|
114
|
-
|
|
115
|
-
| Field | Description |
|
|
116
|
-
|------|------|
|
|
117
|
-
| `启用向量搜索` (Enable vector search) | Master switch; once on, a "Semantic" toggle appears in the memory panel |
|
|
118
|
-
| `API 地址 (Base URL)` | OpenAI-compatible endpoint, e.g. `https://api.openai.com/v1`; also supports SiliconFlow, Zhipu, local Ollama, etc. |
|
|
119
|
-
| `API Key` | The key for the corresponding service |
|
|
120
|
-
| `模型名` (Model name) | Embedding model, e.g. `text-embedding-3-small`, `text-embedding-v3`, `bge-m3`, etc. |
|
|
121
|
-
|
|
122
|
-
After saving, click "重建索引" (Rebuild Index) to backfill vectors for existing memories in bulk (newly written memories are embedded automatically). Then enter a query in the memory panel and click "语义" (Semantic) to recall semantically related results via vectors; if the vector service is unavailable, it automatically falls back to full-text search.
|
|
123
|
-
|
|
124
|
-
> ⚠️ The key is stored only in the local `user_settings` table of `~/.dsh/memory/memory.db`; it is never uploaded and never written into the code repository.
|
|
125
|
-
> You need an embedding model, not a rerank model: e.g. Alibaba Cloud's `text-embedding-v3` works, while `qwen3-vl-rerank` is a rerank model (it does not go through `/embeddings`).
|
|
126
|
-
|
|
127
|
-
### Semantic Enhancement 🧠
|
|
128
|
-
|
|
129
|
-
Since v0.2, a **fully offline semantic memory engine** (local models + reranking + clustering):
|
|
130
|
-
|
|
131
|
-
- **Local embedding**: three selectable backends — ONNX (`Xenova/bge-small-zh-v1.5`, offline) / Ollama / OpenAI-compatible; failures fall back level by level automatically, with keyword search as the last resort
|
|
132
|
-
- **Rerank fine-ranking**: `Xenova/bge-reranker-base` cross-encodes recalled candidates for reranking, improving Top-K accuracy
|
|
133
|
-
- **autoDream semantic enhancement**: clusters memory vectors (`clusterMemories`) to automatically discover topically similar / potentially contradictory memories, making consolidation more precise
|
|
134
|
-
- **Search pipeline**: hybrid recall (keywords + vectors) → Rerank → Top-K
|
|
135
|
-
|
|
136
|
-
Configuration only requires setting `embedProvider` in `cordis.patch.yml` (default `openai`, preserving v0.1 behavior; switch it to `local` for offline). Upgrading requires no data migration.
|
|
137
|
-
|
|
138
|
-
### Entity-Structured Memory (Entity Gene) 🧬
|
|
139
|
-
|
|
140
|
-
Since v0.3.0, a new **memory gene** layer: extracts **named entities**, **attributes with a timeline**, and **relations between entities** from memories, upgrading search from "literal keywords" to "precise recall by entity/attribute".
|
|
141
|
-
|
|
142
|
-
- **Three tables**: `entities` / `entity_attrs` (`valid_until` snapshot-style timeline) / `entity_relations`; opening an older database creates the tables automatically — idempotent, zero migration cost
|
|
143
|
-
- **Automatic extraction**: once `entityExtractionEnabled=true`, newly written memories fire-and-forget trigger LLM extraction (same-name entity dedup, attributes stored on the timeline, relations appended; failures never block writes)
|
|
144
|
-
- **Entity search** (`searchMemories` prefix routing, `entitySearchEnabled` on by default):
|
|
145
|
-
- `entity:阿尔托` → memories precisely associated via attributes (`_score 1.0`) rank ahead of keyword mentions (`_score 0.7`)
|
|
146
|
-
- `attr:国籍=芬兰` → memories exactly matching that attribute value
|
|
147
|
-
- `attr:国籍` → **all** currently valid memories for that attribute key (empty-value contract)
|
|
148
|
-
- **autoDream integration**: `update` decisions write a `supersedes` self-reference (the attribute version is superseded); `merge` decisions migrate the loser's attribute ownership to the keeper (void if the keeper already has a current value for the same key)
|
|
149
|
-
|
|
150
|
-
> 📖 See also: [Entity-Structured Memory Design](docs/ENTITIES.md) · [Semantic Enhancement Architecture](docs/SEMANTIC.md) · [Local Model Deployment Guide](docs/LOCAL_MODEL.md) · [Upgrading from v0.1](docs/MIGRATION.md)
|
|
151
|
-
|
|
152
|
-
### Memory Quality Filter 🧼 (v0.4.6, on by default)
|
|
153
|
-
|
|
154
|
-
Before writing to the store, every memory receives a **heuristic quality score** (a pure function — no I/O, no shared state): meta-memory vocabulary (talking about the memory system itself), self-referential type tags, overly short content, high repetition, and near-duplication of recent memories all deduct points (0-100):
|
|
155
|
-
|
|
156
|
-
- `score ≥ 60`: stored normally
|
|
157
|
-
- `30 ≤ score < 60`: `quality_score` is persisted and injection ranking is down-weighted by `importance × quality/100` (degraded)
|
|
158
|
-
- `score < 30`: archived and flagged `low_quality` — still recallable via explicit search, but **never auto-injected**
|
|
159
|
-
|
|
160
|
-
`memoryQualityFilter.enabled` turns the feature off entirely; `archiveThreshold` / `degradeThreshold` / `minContentLength` are tunable.
|
|
161
|
-
|
|
162
|
-
### LLM Usage Audit 📊 (v0.4.6, on by default)
|
|
163
|
-
|
|
164
|
-
Every **background LLM call** (autoDream consolidation + summary, autoSummarize compression) is written to the `llm_audit_logs` table: `tokens` / `duration` / `status` / `source` (which trigger produced it). Failed calls are recorded as `status=error` and never block the feature itself; `retentionDays` (default 90) purges expired rows at startup. Two new read-only APIs:
|
|
165
|
-
|
|
166
|
-
- `GET /api/dsh-mneme/semantic/llm-audit?page=&pageSize=&source=` — paginated query + filter by source
|
|
167
|
-
- `GET /api/dsh-mneme/semantic/llm-audit/stats?days=` — budget aggregated by source over the last N days (tokens / calls / failures)
|
|
168
|
-
|
|
169
|
-
> Read-only endpoints; like list/search/semantic, they remain open even after `apiToken` is set.
|
|
170
|
-
|
|
171
|
-
## 🆕 Recent Release Highlights
|
|
172
|
-
|
|
173
|
-
> ⚠️ **Archival note**: pre-v0.7.12 rows below record experimental features later removed in the **v0.7.11** rewrite (Wiki-Link, tag system/directory/tag-boost, user/fact layered types, prefix-id resolution, /stats and /directory endpoints). v0.7.11 and v0.7.12 shipped the same day; historical docs credited v0.7.12, corrected here to v0.7.11. Unrecorded removals in the same rewrite: session lifecycle (`session_disposed_at` soft-hide), provenance birth-session tracking (`session_id`), and the decision-field normalizer (`normalizeDecisions`, superseded by strict prompt schema + `extractJsonArray`). They are version history only — **not current capability**. (The heat model was fully restored in v0.7.20 from v0.7.10, see below.) Current features are what this README's body and the [config table](#-configuration) describe.
|
|
174
|
-
|
|
175
|
-
| Version | Highlights |
|
|
176
|
-
|------|------|
|
|
177
|
-
| **v0.7.28** | Connectivity test triple fix: the `POST /api/dsh-mneme/test-model` minimal call raised `maxTokens` 16→1024 — a thinking model's reasoning alone can exhaust a 16-token budget, leaving `reply` always empty ("actually answered ok, not just connected" never held for thinking models; billed on actual usage, manual button, no amplification cost); connectivity test state is now per-route (dream/sleep each hold their own) — previously a single shared state made both groups show "testing/result" and disable each other when either button was clicked; the panel's mount-time draft now includes `sleepProvider`/`sleepModel` — values were always saved server-side, but after a remount (tab switch / re-enter) the dropdowns showed "follow default route" as if the setting had been reset; the saved values restore without re-entering them; 733 tests green |
|
|
178
|
-
| **v0.7.27** | v0.7.26 endpoint backfill: the v0.7.26 release tree did not include the `fix/dream-effort-trap` branch, so the `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` endpoints announced in its CHANGELOG/Release were absent from the package (the panel's cascading dropdown + connectivity test would 404); this release rebases and merges the branch (PR #100), landing the endpoints — panel connectivity test now works; 733 tests green |
|
|
179
|
-
| **v0.7.26** | Consolidation `UNSUPPORTED_REASONING_EFFORT` root-cause fix + dream/sleep model connectivity test: root-caused the **defaultEffort trap** — the harness injects `reasoning.defaultEffort` when effort is omitted, so if that default tier itself is unsupported, retrying without effort never helps (whatever effort is sent gets rejected); fix: new `resolveDreamEffort` proactively queries `ctx.llm.resolveModelInfo()` for the model's supported effort tiers and sends a supported one — an unsupported configured tier auto-falls back to the model's default/first supported tier, and the field is omitted when the model declares no reasoning capability. Settings panel adds selection hints on 6 dream/sleep model fields (guiding toward non-thinking models so they don't burn the token budget on reasoning). New `GET /api/dsh-mneme/llm-providers` (host-side provider/model discovery; keys never touch the plugin side) + `POST /api/dsh-mneme/test-model` (connectivity test; empty body resolves via the consolidation route and returns `modelId`); 732 tests green |
|
|
180
|
-
| **v0.7.25** | Tool-compat hardening + `memory_get`/render content preview + consolidation-model guide: new `memory_get` tool (8th model tool, read a single memory's full body by id) + `memory_search`/`memory_list` render now embeds title/metadata/body preview (the model reads actual memory content even when the host only forwards render text) + consolidation-model selection guide (config comments/README classification, non-thinking vs thinking). Fixed `memory_get` execute nested inside output crashing with `userExecute is not a function` (previous tests only counted tool names, never executed it) + tool de-dup on live patch reload + Standalone API port-collision retry (EADDRINUSE) + client inject declaration alignment + better-sidebar hardening; 718 tests green |
|
|
181
|
-
| **v0.7.24** | Fixed DSH Desktop plugin-tree load crash (v0.7.23 regression): cordis 4's ctx is a Proxy — accessing a property not declared in `inject` throws `cannot get property "webServer" without inject` (not `undefined`), and removing webServer from inject meant cordis no longer waited for the host service, so Desktop crashed on restart; fix: restored webServer to inject (cordis applies the plugin only after the host service is ready) + apply/register guard switched to `ctx.reflect.get` (inject-free read, returns `undefined` when absent, never throws); verified with a real cordis + dsh-host-webserver plugin (API routes 200, unknown path 404, headless silently inactive); 714 tests green |
|
|
182
|
-
| **v0.7.23** | Root-caused "memory consolidation keeps failing": a legal empty decision array `[]` from consolidation is no longer treated as a failure (CONSOLIDATION_PROMPT explicitly allows "no output when nothing needs changing", so a model with a healthy, non-redundant memory legitimately returns `[]` — yet `validateDecisions` hard-rejected it as `decision list must be a non-empty array`, failing the whole run and flooding the audit with failures; **model-agnostic** — ChatGPT/Claude hit the same trap; fix: empty array short-circuits to `ok:true` no-op instead of tripping the implicit-keep coverage check). Plus: empty-body fix part 2 (`dreamMaxTokens` default 8192→32768 so thinking models don't burn the whole budget on reasoning) + skipInvalid splice residue bug (length equality ≠ content equality, skipped decisions leaked into apply/audit); 712 tests green |
|
|
183
|
-
| **v0.7.22** | Restored the v0.6.9 skipInvalid tolerant-validation path (issue #89 regression, lost in the v0.7.11 rewrite): `dreamSkipInvalid` (default true) skips individual invalid decisions, applies the valid subset, and marks the run degraded; `allowCrossTypeMerge` (default false) explicitly relaxes cross-type merging — weak models (e.g. qwen3.8-flash) with jittery schema compliance no longer fail the whole batch and burn LLM calls. Strict mode and the sleep path behave unchanged; global caps/coverage floors still reject the whole run (running over cap = broken model, not minor schema drift). New `dreamMinIntervalMinutes` (0–10080, default 0 = unlimited) minimum autoDream trigger interval — failed/degraded runs also consume the interval (throttling exists to stop back-to-back failing calls); feature_flags whitelist now 34 keys; 696 tests green |
|
|
184
|
-
| **v0.7.21** | Fixed autoDream/sleep effort fallback being dead code on the stream path (the catch-based retry from v0.7.16 never fired): dsh-llm rc.1 turns adapter-stage failures (incl. `UNSUPPORTED_REASONING_EFFORT`) into a terminal error finish chunk instead of a throw; `streamText` now captures the finish-chunk failure cause (`describeStreamFailure` normalizes `{code,message}`) + `withEffortFallback` gains a `getStreamError` accessor (retries without effort when rejected) + `runAuditedLlm` supports `spec.streamError` (audit `error_message` carries the real cause; `run.error` stays a stable `"llm failed"`); 688 tests green |
|
|
185
|
-
| **v0.7.20** | Heat model restored (issue #87): v0.7.0 self-evolving memory back (`src/heat.js` power-law decay `H=1/(1+λΔt)^α` + per-type half-lives), sleep demotion dual protection (cold time-window + heat<0.05 + importance<5), touchRecalled gating back on `heatEnabled`, entity heat projection (ego node heat → size/opacity), recall_runs recording on by default; **default OFF** (matches v0.7.12 behavior out of the box) + feature_flags whitelist rollback switch + lightMode linkage + sleep demotion audit counts exposed (workbench can show "N demoted") + phase-two frontend (/list heat projection, HeatBadge three-tier badge, self-gated status heat-distribution card, order=heat page-local sort, all self-gated); better-sidebar fix (issue #88: soft integration moved to an inner dynamic sub-plugin, no more startup failure without bs); 685 tests green |
|
|
186
|
-
| **v0.7.18** | Ecosystem step one + query convergence: better-sidebar soft integration (inject declaration + optional peer `dsh-better-sidebar` + registerTab reusing the four views, safe skip when absent; narrow-container `@container` adaptation) + `/list?deposited=only` view (receipt_chain ∪ source=dream) + deposited/archived filter chips in the library + status-page dashboard (server totals + view-all jumps) + drawer restore for archived memories; 667 tests green |
|
|
187
|
-
| **v0.7.17** | Panel polish: sidebar entry continuously aligned with the host (MutationObserver syncs the New-Session class + `width:100%` + native centering restored, resilient to async skin rewrites) + importance rendered as Lucide star glyphs (`ImportanceStars` filled/hollow component replacing text ★) + toolbar dropdown stacking fix (z-index moved to the container; export/import menu no longer painted under the sticky month header); 664 tests green |
|
|
188
|
-
| **v0.7.16** | Fixed autoDream empty-body failures on thinking models (`no json array in llm output`): restored config-first routing (settings "consolidation model" wins, Issue #25) + reasoningEffort auto-retry without effort on rejection + honest `llm_audit` error on parse failure; backfilled API-route tests (/delete, /entities, /external-api) + lib runtime smoke; 662 tests green |
|
|
189
|
-
| **v0.7.15** | Desktop adaptation: library panel redesign + 30-key feature-flag UI (features API) + status dashboard + import/export (mirror-isomorphic md golden loop) + token masking by default; 645 tests green |
|
|
190
|
-
| **v0.7.14** | Security fix (CWE-200): distillation no longer collects private `reasoning` blocks — only public `text`; 617 tests green |
|
|
191
|
-
| **v0.7.13** | Coding-memory distillation `codingRetrospect` (opt-in: full-transcript atomic memories, 3 new types) + 429 governor (global serial queue + exponential backoff); 616 tests green |
|
|
192
|
-
| **v0.7.12** | Near-rewrite: inlined panel replaced by a pure HTTP API (127.0.0.1:8790 Bearer auth) + standalone zero-dep CLI `dsh-mneme` + lightMode; memory TYPES narrowed 8→6 (user/fact dropped) |
|
|
193
|
-
| **v0.7.11** | Library panel redesign: monthly pagination + infinite scroll + global search + 30s silent refresh + two-step delete + issues #72/#59 fixes; 595 tests green |
|
|
194
|
-
| **v0.7.10** | Web panel UX: memory-type color dots + graph canvas pan/zoom + settings re-grouping + sidebar tab-conflict fix + read-only `/entities` endpoint; 815 tests green |
|
|
195
|
-
| **v0.7.9** | Issue #65 fix: the snapshotEvents shim only landed in src/, never the npm-loaded lib/ — synced lib + pre-publish src↔lib consistency gate (check-sync.js) + lib smoke tests; 815 tests green |
|
|
196
|
-
| **v0.7.8** | DSH 0.1.2-rc.1 compatibility (issues #58 #59): `Session.events` → `snapshotEvents()` shim; autoSummarize & hot-context injection restored; 812 tests green |
|
|
197
|
-
| **v0.7.5** | Layered memory types (user/fact) + Overview view + stats endpoint; 790 tests green |
|
|
198
|
-
| **v0.7.0** | Self-evolving memory: heat power-law decay + per-type half-life + sleep dual-protection + entity heat projection; 757 tests green |
|
|
199
|
-
| **v0.6.0** | Session lifecycle: `session_disposed_at` soft-hide (orthogonal to archived, recoverable) + `memory_delete` description delete; 628 tests green |
|
|
200
|
-
| **v0.5.0** | Recall fusion & memory graph: BM25 three-way recall fusion + ego-graph API + zero-dep SVG force-directed graph + hot memory; 593 tests green |
|
|
201
|
-
| **v0.4.2** | autoSummarize custom model: the `summarizeProvider`/`summarizeModel` config options let you independently designate a lightweight model (e.g. qwen3.6-plus) for session summaries, saving main-model tokens; 473 tests green |
|
|
202
|
-
| **v0.4.0** | System-level Sleep Mode: idle-triggered four-phase deep maintenance (conflict resolution / archival demotion / pattern discovery / relation completion), interruptible, serially safe, fail-safe; tiered compression releases cold memories; 471 tests green |
|
|
203
|
-
| **v0.3.9** | Fixed 4 FAILs from the third-party audit: CAS made atomic within the same transaction, mirror degraded-receipt passthrough, per-type physical terminal-state convergence, strict integer validation for generation and stabilized concurrent initialization |
|
|
204
|
-
| **v0.3.8** | All 6 runtime-blocking findings from the audit peer re-review fixed: desired generation atomically incremented within the same transaction (the crash window no longer skips silently), sync failures not silent, atomic generation increments (zero loss across processes), per-type committed/failed/pending receipts, explicit unknown on read failure, generation upper-bound/negative CHECK |
|
|
205
|
-
| **v0.3.7** | Startup race fix: vector rebuild failing after a restart following manual edits to the md mirror (backfill moved to after init readiness + scheduleEmbed readiness gate) |
|
|
206
|
-
| **v0.3.6** | Mirror sync state machine: generation/applied_generation debt modeling, F-NEW-03 mirror health status, persistent dirty + recoverMirror at startup |
|
|
207
|
-
| **v0.3.0** | Memory gene: entity/attribute/relation three tables + timeline + entity search + autoDream supersedes |
|
|
208
|
-
|
|
209
|
-
## 🗺️ Evolution Roadmap
|
|
210
|
-
|
|
211
|
-
| Version | Status | Theme | Description |
|
|
212
|
-
|------|------|------|------|
|
|
213
|
-
| v0.2.x | ✅ Done | Semantic enhancement + reflection updates | Local embedding/rerank/clustering, `failure_memories` failure tracking |
|
|
214
|
-
| v0.3.0 | ✅ Done | Memory gene | entities/attrs/relations three tables + timeline + entity search |
|
|
215
|
-
| v0.3.6–0.3.8 | ✅ Done | Mirror consistency + audit hardening | generation sync state machine, 6 audit-peer runtime-blocking fixes, 450 tests green |
|
|
216
|
-
| v0.3.9 | ✅ Done | Audit hardening A/B/D/F | compareAndUpdate same-transaction atomicity, degraded receipts, per-type physical terminal state, integer fail-closed, stable concurrent initialization |
|
|
217
|
-
| **v0.4.0** | ✅ Done | System-level Sleep Mode | Idle-triggered four-phase deep maintenance (conflict resolution / archival demotion / pattern discovery / relation completion), tiered compression, interruptible serial fail-safe; 471 tests green |
|
|
218
|
-
| **v0.4.2** | ✅ Done | autoSummarize custom model | `summarizeProvider`/`summarizeModel` config options, letting you independently designate a lightweight model (e.g. qwen3.6-plus) for session summaries and save main-model tokens; 473 tests green |
|
|
219
|
-
| **v0.4.3** | ✅ Done | autoDream large-memory fix | issue#9 B+A: `dreamMaxTokens` cap raised 32768→131072 + `dreamReasoningEffort`/`sleepReasoningEffort` reasoning toggles (`none` by default, main conversation unaffected); 478 tests green |
|
|
220
|
-
| **v0.4.4** | ✅ Done | autoDream decision coverage fix | issue#9 plan C: sliding window `dreamMaxSnapshotSize` (default 200, truncated by updated_at descending) + implicit keep `dreamImplicitKeep` (default true) + coverage floor `dreamMinExplicitCoverage` (default 50%) + fixed decision schema; 487 tests green |
|
|
221
|
-
| **v0.4.5** | ✅ Done | Epistemic trust + recall eval | Memory credibility grading `trustEpistemicWeighting` (observation>inferred>subjective: retrieval ranking favors high-credibility memories, injection tags `[verified]`, dream merge/conflict favors the more credible side; opt-in, off by default) + retrieval evaluation `evaluateRetrieval` persisted to `recall_evals` (`evalPersistTestResults` opt-in, off by default; production retrieval always goes through `recall_runs`, unconditionally isolated); 518 tests green |
|
|
222
|
-
| **v0.4.6** | ✅ Done | 8 fixes (vector pipeline + injection/quality/audit) | Vector pipeline fixes (embedSingle adaptation / `autoReindexOnBoot` backfill of existing data / `vector_meta` metadata) + injection semantic recall `hybridInject` + same-title append `content_history` + injection length caps (300 per item / 1500 per block) + memory quality filter `memoryQualityFilter` + LLM usage audit `llmAudit` (table + instrumentation + read-only APIs); 553 tests green |
|
|
223
|
-
| **v0.4.7** | ✅ Done | Idempotent schema migrations | When the same db is opened concurrently, the `PRAGMA table_info` check and ALTER are non-atomic and may repeat `ADD COLUMN`, failing with a duplicate column name; switched to an `addColumn` helper that swallows the race (try/catch), unifying all 12 migration sites |
|
|
224
|
-
| v0.5.0 | ✅ Done | Recall fusion & memory graph | BM25 three-way recall fusion + ego-graph API + zero-dep SVG force-directed graph + hot memory + recall benchmark; 593 tests green |
|
|
225
|
-
| v0.6.0 | ✅ Done | Session lifecycle | `session_disposed_at` soft-hide (orthogonal to archived, recoverable) + `memory_delete` description delete + event circuit-breaker; 628 tests green |
|
|
226
|
-
| v0.6.x | ✅ Done | Panel enhancements + fixes | 7 further 0.6.x releases: Wiki-Link/tag/directory experiments (later removed in v0.7.12), #25/#26 fixes, allowCrossTypeMerge, ID-exposure hardening, version-sync discipline; up to 735 tests green |
|
|
227
|
-
| v0.7.0 | ✅ Done | Self-evolving memory | heat power-law decay + per-type half-life + sleep dual-protection + entity heat projection + recall_runs marking + 90-day cleanup; 757 tests green (heat model later removed in v0.7.12) |
|
|
228
|
-
| v0.7.1–0.7.8 | ✅ Done | Issue fixes + graph backfill | tags↔entity_attrs bridge, inline-confirm delete, sidebar trigger toggle, brace escaping, user/fact layered types + stats endpoint (later removed), prefix-id resolution (later removed), sleep batch entity extraction, snapshotEvents() DSH compat; 764→812 tests green |
|
|
229
|
-
| v0.7.9 | ✅ Done | lib-sync gate | Issue #65: src-only shim silently killed the shipped lib — synced lib + pre-publish src↔lib consistency check + lib smoke tests; 815 tests green |
|
|
230
|
-
| v0.7.10–0.7.12 | ✅ Done | Panel redesign + near-rewrite | UX upgrades (color dots, pan/zoom, pagination, global search), then near-rewrite: inlined panel → pure HTTP API + standalone CLI + lightMode; TYPES narrowed 8→6 |
|
|
231
|
-
| v0.7.13–0.7.15 | ✅ Done | Distillation, security, desktop | codingRetrospect + 429 governor; private reasoning blocks dropped (CWE-200); desktop panel redesign + feature flags + status dashboard + import/export |
|
|
232
|
-
| v0.7.16 | ✅ Done | autoDream thinking-model fix | config-first routing restored (Issue #25) + reasoningEffort auto-retry + honest audit on parse failure; backfilled API-route & lib smoke tests; 662 tests green |
|
|
233
|
-
| v0.7.17 | ✅ Done | Panel polish | Sidebar entry continuously aligned with the host (MutationObserver syncs the New-Session class + `width:100%` + native centering, resilient to async skin rewrites) + Lucide star glyphs (`ImportanceStars`) + toolbar dropdown stacking fix; 664 tests green |
|
|
234
|
-
| v0.7.18 | ✅ Done | Ecosystem step one + query convergence | better-sidebar soft integration (inject declaration + optional peer + registerTab reusing the four views, safe skip when absent; narrow-container `@container`) + `/list?deposited=only` view + deposited/archived filter chips + status-page dashboard + drawer restore; 667 tests green |
|
|
235
|
-
| v0.7.20 | ✅ Done | Heat restore + phase-two frontend + better-sidebar fix | Heat model fully restored (issue #87, backported from v0.7.10: power-law decay + TYPE_DECAY + sleep heat-combined dual protection + entity heat projection) + acceptance checklist landed (heatEnabled default OFF / feature_flags 31 keys / lightMode linkage / sleep demotion audit / updated_at⊥last_accessed_at contract) + phase-two frontend (/list heat projection, HeatBadge three-tier, order=heat page-local sort) + better-sidebar fix (issue #88: inner dynamic sub-plugin); 685 tests green |
|
|
236
|
-
| v0.7.21 | ✅ Done | effort-fallback stream fix | Catch-based effort fallback was dead code on the stream path (dsh-llm rc.1 turns adapter failures into a terminal error finish chunk instead of a throw) → `streamText` captures the finish-chunk cause (`describeStreamFailure`) + `withEffortFallback` gains a `getStreamError` accessor (retries without effort when rejected) + `runAuditedLlm` supports `spec.streamError` (real cause in audit); 688 tests green |
|
|
237
|
-
| v0.7.22 | ✅ Done | skipInvalid tolerant-validation restore (issue #89) + autoDream throttle | Restored v0.6.9 skipInvalid dual-track structure (lost in the v0.7.11 rewrite): `dreamSkipInvalid` skips individual invalid decisions + applies the valid subset + marks the run degraded; `allowCrossTypeMerge` explicitly relaxes cross-type merging; weak models (qwen3.8-flash) with jittery schema compliance no longer fail the whole batch. New `dreamMinIntervalMinutes` (0–10080, default 0) minimum trigger interval — failed/degraded runs also consume it. Strict mode / sleep path unchanged; feature_flags whitelist 34 keys; 696 tests green |
|
|
238
|
-
| v0.7.23 | ✅ Done | "Consolidation keeps failing" root fix + empty-body part 2 + skipInvalid splice fix | A legal empty decision array `[]` no longer fails the whole run (CONSOLIDATION_PROMPT explicitly allows no output when nothing needs changing, yet `validateDecisions` hard-rejected it as non-empty — model-agnostic, ChatGPT/Claude hit the same trap; fix: empty array short-circuits to `ok:true` no-op instead of tripping the implicit-keep coverage check); `dreamMaxTokens` default 8192→32768 (thinking models burn the whole budget on reasoning); skipInvalid splice residue bug (length equality ≠ content equality, skipped decisions leaked into apply/audit); 712 tests green |
|
|
239
|
-
| v0.7.24 | ✅ Done | DSH Desktop plugin-tree load crash fix (v0.7.23 regression) | cordis 4's ctx is a Proxy — accessing a property not declared in `inject` throws `cannot get property "webServer" without inject` (not `undefined`), and removing webServer from inject made Desktop crash on restart; fix: restored webServer to inject (cordis applies the plugin only after the host service is ready) + apply/register guard switched to `ctx.reflect.get` (inject-free read, returns `undefined` when absent, never throws); verified with a real cordis + dsh-host-webserver plugin (API routes 200 / unknown path 404 / headless silently inactive); 714 tests green |
|
|
240
|
-
| v0.7.25 | ✅ Done | Tool-compat hardening + content preview + consolidation-model guide | New `memory_get` tool (8th model tool, full-body read by id) + `memory_search`/`memory_list` render embeds title/metadata/body preview + consolidation-model selection guide; fixed `memory_get` execute nested inside output (`userExecute is not a function`) + tool de-dup on live patch reload + Standalone API port-collision retry + client inject alignment + better-sidebar hardening; 718 tests green |
|
|
241
|
-
| **v0.7.26** | ✅ Done | Consolidation effort-trap root fix + LLM connectivity test | `UNSUPPORTED_REASONING_EFFORT` root cause (defaultEffort trap: harness injects `reasoning.defaultEffort` when effort is omitted, so an unsupported default can never be retried) → `resolveDreamEffort` proactively queries `ctx.llm.resolveModelInfo()` for the model's supported effort tiers (unsupported configured tier falls back to the model's default/first supported tier; field omitted when the model declares no reasoning capability); settings panel adds selection hints on 6 dream/sleep model fields; new `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` (connectivity test; empty body resolves via the consolidation route; keys never touch the plugin side); 732 tests green |
|
|
242
|
-
| **v0.7.27** | ✅ Done | v0.7.26 endpoint backfill | The v0.7.26 release tree did not include the `fix/dream-effort-trap` branch, so the `GET /api/dsh-mneme/llm-providers` + `POST /api/dsh-mneme/test-model` endpoints announced in its CHANGELOG/Release were absent from the package (the panel's cascading dropdown + connectivity test would 404); this release rebases and merges the branch (PR #100), landing the endpoints — panel connectivity test now works; 733 tests green |
|
|
243
|
-
| **v0.7.28** | ✅ Done | Connectivity test triple fix | `POST /api/dsh-mneme/test-model` minimal call `maxTokens` 16→1024 (a thinking model's reasoning alone can exhaust a 16-token budget, leaving `reply` always empty); connectivity-test state now per-route (dream/sleep each hold their own — buttons no longer cross-disable); mount-time draft now includes `sleepProvider`/`sleepModel` (values were always saved server-side, but after a remount the dropdowns showed "follow default route" as if reset); 733 tests green |
|
|
244
|
-
| **v0.8.0** | 🚧 Planned (late Sep) | Graph enhancement | Interest-drift visualization + scope isolation (issue #17) + cross-workspace sharing |
|
|
245
|
-
|
|
246
|
-
> All new capabilities ship as **toggleable features** (enabled/disabled via configuration), conservatively on by default and never breaking existing behavior. The `failure_memories` table and the autoDream decision engine have already paved the way for future reflective growth.
|
|
247
|
-
|
|
248
|
-
## 📦 Installation
|
|
249
|
-
|
|
250
|
-
### Prerequisites
|
|
251
|
-
|
|
252
|
-
- [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH)
|
|
253
|
-
- Node 24+ (`node:sqlite`)
|
|
254
|
-
|
|
255
|
-
### Installation Steps
|
|
256
|
-
|
|
257
|
-
#### Option 1: npm install (recommended)
|
|
258
|
-
|
|
259
|
-
dsh-mneme is a **bundle** (it declares a `dsh.bundle` manifest); installation activates it automatically, no manual configuration required:
|
|
260
|
-
|
|
261
|
-
```bash
|
|
262
|
-
# 1. 安装插件(自动注册 bundle 层)
|
|
263
|
-
dsh plugin --profile web add @modusensus/dsh-mneme
|
|
264
|
-
|
|
265
|
-
# 2. 重启
|
|
266
|
-
dsh web
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
> For custom configuration (thresholds, delays, etc.), override the defaults under `id: dsh-mneme` in `~/.dsh/profiles/web/cordis.patch.yml` (see the configuration table below).
|
|
270
|
-
|
|
271
|
-
#### Option 2: Install from source
|
|
272
|
-
|
|
273
|
-
```bash
|
|
274
|
-
git clone https://github.com/modusensus/dsh-mneme.git
|
|
275
|
-
cd dsh-mneme
|
|
276
|
-
dsh plugin --profile web add .
|
|
277
|
-
dsh web
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
#### Custom configuration (optional)
|
|
281
|
-
|
|
282
|
-
It works out of the box with the defaults. To adjust, override in `~/.dsh/profiles/web/cordis.patch.yml`:
|
|
283
|
-
|
|
284
|
-
```yaml
|
|
285
|
-
- id: dsh-mneme
|
|
286
|
-
name: '@modusensus/dsh-mneme'
|
|
287
|
-
config:
|
|
288
|
-
memoryDir: ~/.dsh/memory
|
|
289
|
-
autoInject: true
|
|
290
|
-
autoSummarize: true
|
|
291
|
-
maxInjectedItems: 5
|
|
292
|
-
importanceThreshold: 3
|
|
293
|
-
autoDream: true
|
|
294
|
-
dreamThresholdCount: 10
|
|
295
|
-
dreamThresholdChars: 5000
|
|
296
|
-
dreamDelayMs: 2000
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
## ⚙️ Configuration
|
|
300
|
-
|
|
301
|
-
| Key | Default | Description |
|
|
302
|
-
|----|--------|------|
|
|
303
|
-
| `memoryDir` | `~/.dsh/memory` | Memory storage directory (SQLite + Markdown) |
|
|
304
|
-
| `autoInject` | `true` | Automatically inject memories at session start |
|
|
305
|
-
| `autoSummarize` | `true` | Automatically distill a summary at session end |
|
|
306
|
-
| `summarizeProvider` / `summarizeModel` | empty | LLM route override for summaries (empty = use the current session model); a lightweight model is recommended to save main-model tokens |
|
|
307
|
-
| `maxInjectedItems` | `5` | Maximum number of memories to inject |
|
|
308
|
-
| `importanceThreshold` | `3` | Minimum importance for injection (1-5) |
|
|
309
|
-
| `autoDream` | `true` | Automatic memory consolidation switch |
|
|
310
|
-
| `dreamThresholdCount` | `10` | Memory count threshold that triggers consolidation |
|
|
311
|
-
| `dreamThresholdChars` | `5000` | Total character threshold that triggers consolidation |
|
|
312
|
-
| `dreamDelayMs` | `2000` | Asynchronous consolidation delay (debounce) |
|
|
313
|
-
| `dreamProvider` / `dreamModel` | empty | Explicit dream LLM route — config wins over the agent's default model (config-first, v0.7.16); left empty, the agent's default model is used |
|
|
314
|
-
| `dreamMaxTokens` | `32768` | Maximum tokens per dream LLM call (cap 131072; reasoning and body share this budget on reasoning models — raise it when the body comes back empty, see the tuning guide below) |
|
|
315
|
-
| `dreamReasoningEffort` | `none` | Reasoning-effort passthrough for the dream LLM: `low` / `medium` / `high` / `none` (`none` = omit the field and use the model default; set `low` when a reasoning model exhausts its budget on reasoning and produces an empty body; v0.7.26+ auto-falls back to the model's default/first supported tier when the configured tier is unsupported, and omits the field for models without reasoning capability) |
|
|
316
|
-
| `apiToken` | empty | Optional API auth token; once set, write operations and key endpoints require `Authorization: Bearer <apiToken>` |
|
|
317
|
-
| `embedProvider` | `openai` | Semantic backend: `openai` (default, v0.1-compatible) / `local` (ONNX offline) / `ollama` |
|
|
318
|
-
| `localEmbedModel` | `Xenova/bge-small-zh-v1.5` | Local ONNX embedding model |
|
|
319
|
-
| `localEmbedDimension` | `512` | Local embedding vector dimension |
|
|
320
|
-
| `localEmbedDevice` | `cpu` | Local inference device: `cpu` / `gpu` |
|
|
321
|
-
| `localEmbedBatchSize` | `8` | Local embedding batch size (1-64) |
|
|
322
|
-
| `ollamaBaseUrl` | `http://localhost:11434` | Ollama service address |
|
|
323
|
-
| `ollamaModel` | `nomic-embed-text` | Ollama embedding model |
|
|
324
|
-
| `embedModelCacheDir` | empty | Model cache directory (empty = user-level `~/.dsh/mneme/models`) |
|
|
325
|
-
| `embedModelMirror` | `https://hf-mirror.com` | Mirror source for model downloads |
|
|
326
|
-
| `vectorSearchTopK` | `20` | Top-K returned by vector search |
|
|
327
|
-
| `vectorSearchThreshold` | `0.65` | Vector search similarity threshold |
|
|
328
|
-
| `hybridSearchVectorWeight` | `0.6` | Vector weight in hybrid search |
|
|
329
|
-
| `hybridSearchKeywordWeight` | `0.4` | Keyword weight in hybrid search |
|
|
330
|
-
| `rerankEnabled` | `false` | Whether to enable rerank fine-ranking (the local onnxruntime model loads only when explicitly enabled) |
|
|
331
|
-
| `rerankProvider` | `none` | Rerank backend: `local` / `none` (default `none`) |
|
|
332
|
-
| `rerankModel` | `Xenova/bge-reranker-base` | Rerank cross-encoding model |
|
|
333
|
-
| `rerankBatchSize` | `8` | Rerank batch size |
|
|
334
|
-
| `rerankMaxCandidates` | `30` | Maximum number of rerank candidates |
|
|
335
|
-
| `rerankScoreThreshold` | `0.1` | Rerank score threshold (candidates below it are dropped) |
|
|
336
|
-
| `reflectionUpdateEnabled` | `true` | Master switch for `update` decisions |
|
|
337
|
-
| `reflectionFailureTracking` | `true` | Master switch for failure tracking |
|
|
338
|
-
| `reflectionUpdateMaxPerRun` | `2` | Maximum `update` decisions per consolidation run |
|
|
339
|
-
| `reflectionUpdateMinAgeHours` | `24` | Protection period for newly created memories (hours) |
|
|
340
|
-
| `entityExtractionEnabled` | `false` | Master switch for entity extraction (v0.3.0; always available at the storage layer) |
|
|
341
|
-
| `entityExtractionModel` | empty | Dedicated extraction model (empty = use the agent's default model) |
|
|
342
|
-
| `entityExtractionMaxEntities` | `10` | Maximum entities per extraction |
|
|
343
|
-
| `entityExtractionMaxAttrs` | `20` | Maximum attributes per entity |
|
|
344
|
-
| `entitySearchEnabled` | `true` | Switch for `entity:` / `attr:` prefix search |
|
|
345
|
-
| `trustEpistemicWeighting` | `false` | Memory credibility weighting (v0.4.5, opt-in, off by default): memories are graded by source as `observation` > `inferred` > `subjective`; when enabled, retrieval ranking favors high-credibility memories, injection tags observation entries `[verified]`, and dream merge/conflict favors the more credible side; when off, `epistemic_status` is only persisted on save and does not participate in behavior |
|
|
346
|
-
| `evalPersistTestResults` | `false` | Retrieval evaluation persistence (v0.4.5, opt-in, off by default): when enabled, `evaluateRetrieval` writes precision/recall/mrr snapshots into `recall_evals`; when off it only returns them to the caller without persisting. Production `searchMemories` auditing always goes through `recall_runs`, unconditionally never touching `recall_evals` |
|
|
347
|
-
| `autoReindexOnBoot` | `true` | When existing memories lack embeddings and vectors are configured, a delayed, rate-limited background backfill rebuild starts after boot (set to `false` for manual rebuild only) |
|
|
348
|
-
| `hybridInject` | `true` | Semantic-recall-first injection (v0.4.6, Bug4): when `injectCandidates` receives a non-empty query, it first recalls candidates via the vector index semantically, then fills in/deduplicates with rule-based filtering; empty query / no vectors falls back to the old logic |
|
|
349
|
-
| `heatEnabled` | `false` | Heat-model master switch (v0.7.0 / restored in v0.7.20, **off by default** — v0.7.12+ users expect no-heat behavior): when on, provides heat fields / sleep heat-combined demotion protection / frontend heat projection without changing recall ranking; when off, skips all heat computation and touch, and sleep demotion reverts to pure time-tiering. Also on the feature_flags whitelist (panel-toggleable rollback switch); light-mode preset forces it off |
|
|
350
|
-
| `memoryQualityFilter` | `{enabled:true, archiveThreshold:30, degradeThreshold:60, minContentLength:10}` | Memory quality filter (v0.4.6, on by default): heuristic 0-100 scoring before write; meta-memory vocabulary/self-reference/overly short/duplicate/near-duplicate content deduct points; ≥60 stored normally, 30-60 down-weighted (injection ranked by importance×quality/100), <30 archived and flagged `low_quality` (still recallable via explicit search, never auto-injected) |
|
|
351
|
-
| `llmAudit` | `{enabled:true, retentionDays:90}` | LLM usage audit (v0.4.6, on by default): every background LLM call (autoDream/autoSummarize) writes `llm_audit_logs` (tokens/duration/status/source); failures are recorded as error without blocking; read-only APIs `/api/dsh-mneme/semantic/llm-audit` + `/llm-audit/stats` |
|
|
352
|
-
|
|
353
|
-
> 🔐 **API security**: DSH has no built-in authentication and by default listens only on `127.0.0.1`. The plugin API is open by default (so the web panel works out of the box). For protection (e.g. when exposed to a LAN), set `apiToken` in the configuration: write operations (profile/rules/commands) and key endpoints (`vector-config`, `vector-reindex`) require `Authorization: Bearer <token>` (the frontend settings panel accepts the same token), while the read-only `list` / `search` / `semantic` endpoints remain open. The `apiKey` returned by `/api/dsh-mneme/vector-config` is masked (`sk-***…`), while the stored plaintext is kept for actual calls; the frontend sending back an empty or masked value means "do not change the key".
|
|
354
|
-
|
|
355
|
-
## External API & CLI
|
|
356
|
-
|
|
357
|
-
Besides DSH's internal port, the plugin can also run a **standalone HTTP external API** (default `http://127.0.0.1:8790`, Bearer token auth) so other plugins, CLI scripts, or desktop tools can read and write memories without depending on DSH's internal port.
|
|
358
|
-
|
|
359
|
-
### Enabling & Authentication
|
|
360
|
-
|
|
361
|
-
- Enable the external API in the plugin settings (it listens on `127.0.0.1:8790` by default, local machine only);
|
|
362
|
-
- The access token can be found in the plugin settings / the panel under "Settings → External Access";
|
|
363
|
-
- Except for `GET /health` (no auth), all routes require an `Authorization: Bearer <token>` header; an invalid token returns `401 {"error":"unauthorized"}`.
|
|
364
|
-
|
|
365
|
-
Main routes:
|
|
366
|
-
|
|
367
|
-
| Method | Route | Description |
|
|
368
|
-
|--------|-------|-------------|
|
|
369
|
-
| `GET` | `/health` | Health check (no auth), returns `{ok:true}` |
|
|
370
|
-
| `GET` | `/status` | Version, memory stats, entity count, uptime |
|
|
371
|
-
| `GET` | `/memories?limit&offset&type&minImportance&source&order=chrono` | List memories with pagination |
|
|
372
|
-
| `GET` | `/memories/:id` | A single memory |
|
|
373
|
-
| `POST` | `/memories` | Create a memory `{type,title,content,importance?,tags?,source?}` |
|
|
374
|
-
| `DELETE` | `/memories/:id` | Delete a memory |
|
|
375
|
-
| `GET` | `/search?q&mode=keyword\|vector\|auto&topK` | Search (keyword / vector / auto) |
|
|
376
|
-
|
|
377
|
-
### curl Examples
|
|
378
|
-
|
|
379
|
-
```bash
|
|
380
|
-
# Service status
|
|
381
|
-
curl -s -H "Authorization: Bearer $DSH_MNEME_TOKEN" http://127.0.0.1:8790/status
|
|
382
|
-
|
|
383
|
-
# List the 5 most recent memories
|
|
384
|
-
curl -s -H "Authorization: Bearer $DSH_MNEME_TOKEN" \
|
|
385
|
-
"http://127.0.0.1:8790/memories?limit=5"
|
|
386
|
-
|
|
387
|
-
# Add a decision memory
|
|
388
|
-
curl -s -X POST http://127.0.0.1:8790/memories \
|
|
389
|
-
-H "Authorization: Bearer $DSH_MNEME_TOKEN" \
|
|
390
|
-
-H "Content-Type: application/json" \
|
|
391
|
-
-d '{"type":"decision","title":"Adopt SQLite","content":"Storage layer uses node:sqlite","importance":4,"tags":["storage"]}'
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### Installing the CLI
|
|
395
|
-
|
|
396
|
-
The plugin ships a zero-dependency CLI (published with the npm package):
|
|
397
|
-
|
|
398
|
-
```bash
|
|
399
|
-
npm i -g @modusensus/dsh-mneme
|
|
400
|
-
dsh-mneme --help
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
On first use, configure the server URL and token (you can also use the `DSH_MNEME_URL` / `DSH_MNEME_TOKEN` environment variables, or override temporarily with `--url` / `--token`):
|
|
404
|
-
|
|
405
|
-
```bash
|
|
406
|
-
dsh-mneme config set http://127.0.0.1:8790 <your-token>
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### Common CLI Commands
|
|
410
|
-
|
|
411
|
-
```bash
|
|
412
|
-
dsh-mneme status # Service status
|
|
413
|
-
dsh-mneme list --type project --limit 10 # List memories
|
|
414
|
-
dsh-mneme search "deploy pipeline" --mode vector --topk 5 # Semantic search
|
|
415
|
-
dsh-mneme add --type decision --title "Adopt SQLite" \
|
|
416
|
-
--content "Storage layer uses node:sqlite" --importance 4 --tags storage
|
|
417
|
-
dsh-mneme get 42 # Show one memory
|
|
418
|
-
dsh-mneme delete 42 # Delete
|
|
419
|
-
dsh-mneme config show # Show current config (token masked)
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
> All read/write commands support `--json` for raw JSON output; `config path` prints the config file location (`~/.dsh-mneme/cli.json`).
|
|
423
|
-
|
|
424
|
-
## 🏗️ Architecture
|
|
425
|
-
|
|
426
|
-
```
|
|
427
|
-
┌─────────────────────────────────────────────────┐
|
|
428
|
-
│ 存储层:SQLite (archived/forgotten 状态) │
|
|
429
|
-
│ + Markdown 镜像(人工可编辑,双向同步) │
|
|
430
|
-
├─────────────────────────────────────────────────┤
|
|
431
|
-
│ 服务层:saveWithDedupe / injectCandidates │
|
|
432
|
-
│ / mergeHumanEdits / onWrite 钩子 │
|
|
433
|
-
├─────────────────────────────────────────────────┤
|
|
434
|
-
│ 模型接口:8 个工具 + 自动注入 + 会话摘要 │
|
|
435
|
-
├─────────────────────────────────────────────────┤
|
|
436
|
-
│ autoDream:阈值调度 → LLM 决策清单 │
|
|
437
|
-
│ → 校验(fail-safe)→ 应用 → 摘要 │
|
|
438
|
-
├─────────────────────────────────────────────────┤
|
|
439
|
-
│ Web 面板:设置面板内嵌 + 浏览/搜索(含向量) │
|
|
440
|
-
└─────────────────────────────────────────────────┘
|
|
441
|
-
```
|
|
442
|
-
|
|
443
|
-
**Source layout**:
|
|
444
|
-
|
|
445
|
-
```
|
|
446
|
-
src/
|
|
447
|
-
├── store.js # SQLite 存储(CRUD、搜索、归档/遗忘、schema 迁移)
|
|
448
|
-
├── mirror.js # Markdown 镜像(渲染/解析,人工优先)
|
|
449
|
-
├── service.js # 领域逻辑(去重合并、注入筛选、写入钩子)
|
|
450
|
-
├── config.js # schemastery 配置 schema
|
|
451
|
-
├── tools.js # 8 个模型工具(defineTool)
|
|
452
|
-
├── inject.js # systemPrompt.context 动态注入
|
|
453
|
-
├── summarize.js # 会话结束 LLM 摘要
|
|
454
|
-
├── dream.js # autoDream 调度 + runDream(LLM 决策 + 摘要)
|
|
455
|
-
├── dream/decisions.js# 决策校验(fail-safe)+ 决策应用
|
|
456
|
-
├── entities/extractor.js # 实体抽取器(v0.3.0:LLM JSON 抽取 + 去重 + fail-safe)
|
|
457
|
-
├── embedding.js # OpenAI 兼容 embeddings 客户端 + 向量检索
|
|
458
|
-
├── api.js # HTTP 路由(Web 面板数据通道)
|
|
459
|
-
└── index.js # 插件接线
|
|
460
|
-
lib/
|
|
461
|
-
├── client.js # Web 面板(手写 ModuleLoader bundle)
|
|
462
|
-
└── *.js # src 的同步分发产物
|
|
463
|
-
test/ # 714 node:test tests (audit + three-axis stress invariants)
|
|
464
|
-
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
## 🧪 Development
|
|
468
|
-
|
|
469
|
-
```bash
|
|
470
|
-
cd dsh-mneme
|
|
471
|
-
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
|
|
472
|
-
npm test # 运行 714 个测试
|
|
473
|
-
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
|
|
474
|
-
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
> The stress test (`npm run stress`) covers three axes: **long-session retrieval** (Recall@k, stale-residual rate), **conflict adjudication** (a replayable adjudication set: audit snapshot hash + receipt + idempotent replay), and **multi-Agent concurrency** (lost updates, duplicate merges, transaction/crash recovery). Every autoDream run writes to the `dream_runs` audit table (input snapshot digest + decision list + per-id disposition + receipt), so silent errors can be pinpointed even when the pass rate is high.
|
|
478
|
-
|
|
479
|
-
> `lib/` is the synced distribution artifact of `src/` (`npm run sync`); `lib/client.js` is the hand-written web panel source and is unaffected by the sync.
|
|
480
|
-
|
|
481
|
-
## 📄 Design Documents
|
|
482
|
-
|
|
483
|
-
> Design documents live in `docs/` at the repository root; the links point there via the `../docs/` relative path (they resolve correctly on GitHub when opened from this directory).
|
|
484
|
-
|
|
485
|
-
- [Entity-Structured Memory Design](docs/ENTITIES.md)
|
|
486
|
-
- [Semantic Enhancement Architecture](docs/SEMANTIC.md)
|
|
487
|
-
- [Local Model Deployment Guide](docs/LOCAL_MODEL.md)
|
|
488
|
-
- [Upgrading from v0.1](docs/MIGRATION.md)
|
|
489
|
-
|
|
490
|
-
## 📜 License
|
|
491
|
-
|
|
492
|
-
MIT
|