dsh-engram 0.4.0 → 0.5.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 CHANGED
@@ -23,6 +23,21 @@ with one goal: **save tokens**.
23
23
  integration**: a completed or blocked goal (`goal/change` session event)
24
24
  auto-sediments as a `handoff`/`error` memory (`tag:goal`), so the outcome of
25
25
  a goal survives its session (see the `GET /api/dsh-engram/goals` read surface).
26
+ - **User-correction / preference learning (`autoCapturePreferences`, default on,
27
+ zero LLM)** — the lesson that "when the AI makes a mistake and gets corrected,
28
+ that lesson should persist" ([correx](https://github.com/ozoz5/correx),
29
+ [PAHF](https://arxiv.org/abs/2602.16173), Mem0 preference extraction): a
30
+ `session/event` listener watches `user/message` and turns deterministic
31
+ correction/preference cues into a dedicated `preference` memory kind —
32
+ Chinese (`不要用X`, `改用X`, `以后统一用X`…) and English (`don't use X`,
33
+ `switch to X`, `prefer X`, `never use X`…) patterns, three directions
34
+ (`avoid` / `switch-to` / `prefer`), with interrogative/one-shot-instruction/
35
+ memory-tool-meta-talk filters to keep false positives out. `preference`
36
+ memories outrank every other kind in the `[ENGRAM]` block (`[R]` prefix) so a
37
+ standing user rule is the first line the model sees each session; per-session
38
+ cap + recent-window dedup mirror the auto-capture discipline. Zero LLM, zero
39
+ embeddings, deterministic and pure — extracted clauses are bounded, and every
40
+ write still goes through the secret redactor.
26
41
  - **Symbolic index + progressive disclosure** — a compact `[ENGRAM]` block (default
27
42
  budget 700 chars ≈ 175 tokens; one line per memory) is injected at prompt
28
43
  assembly and **frozen per session**, keeping the request prefix byte-stable for
@@ -51,6 +66,12 @@ with one goal: **save tokens**.
51
66
  Pure rule, zero LLM, pure read (never bumps `hits`); superseded stale truth does
52
67
  not occupy an injection slot (still fetchable via `engram_detail`); frozen per
53
68
  session together with `[ENGRAM]` for prefix stability.
69
+ - **Read-path activation (D)** — a hit whose one-line preview is truncated
70
+ (>140 chars) carries a `…` marker and the block's trailing guidance names that
71
+ concrete id: `#<id> truncated — engram_detail <id> (full) · engram_recall <query>`.
72
+ So the model knows the full record is one deterministic `engram_detail` call
73
+ away instead of assuming the preview is the whole memory (proven gap in real
74
+ use: 66 sessions, zero `engram_detail` calls — the detail path was invisible).
54
75
  - **Memory-to-memory semantics** — `engram_store` accepts optional
55
76
  `supersedes` / `contradicts` memory ids (validated against the same
56
77
  workspace). A superseded ("stale truth") memory is demoted to the tail of
@@ -243,8 +264,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
243
264
  <!-- dsh-market compatibility block — keep labels bilingual and in sync with
244
265
  docs/RELEASE-NOTES.TEMPLATE.md (market README convention) -->
245
266
 
246
- - **DSH 版本 / DSH versions**: `>=0.1.2-alpha.2 <0.2.0-0` (dsh-engram `0.3.7`; releases `0.2.0`–`0.3.6` support the old generation only — see matrix below)
247
- - **最后验证日期 / Last verified**: 2026-09-10 — full regression on `dsh-v0.1.5-alpha.1`; real-machine settings GET/PUT/conflict/reset/restart-persistence on `dsh 0.1.5-rc.1` (isolated profile)
267
+ - **DSH 版本 / DSH versions**: `>=0.1.2-alpha.2 <0.3.0-0` (本版 `0.5.0`; 已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` — 见下方矩阵)
268
+ - **最后验证日期 / Last verified**: 2026-10-15 — 设置模型迁移(entry-id volatile Config,兼容 DSH `0.2.x`)落地,npm test 329/329 通过;真实 `dsh 0.2.0-rc.2` 隔离 profile 运行烟测通过(见下方“当前发布”)
248
269
  - **安装方式已验证 / Verified install**: `dsh plugin --profile web add dsh-engram`
249
270
 
250
271
  > **Short version.** Every **published** release (`0.2.0` … `0.3.6`) supports
@@ -257,7 +278,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
257
278
  | dsh-engram release | Status | DSH support | Declared via |
258
279
  | --- | --- | --- | --- |
259
280
  | `0.2.0` … `0.3.6` (npm, GitHub v0.3.3–v0.3.6) | published | **old DSH only** — `0.1.0-rc.7` / `0.1.0-rc.8` / `0.1.1-rc.1` / `0.1.1-rc.2` | peers `@deepseek-ai/dsh-*@^0.1.0-rc.7` |
260
- | `0.3.7` (npm, GitHub v0.3.7) | published | **new DSH only** — `>=0.1.2-alpha.2 <0.2.0-0` | `engines.dsh` + peers `>=0.1.2-alpha.2 <0.2.0-0` |
281
+ | `0.3.7` / `0.4.0` (npm) | published | **new DSH, settings `installSection`** — `>=0.1.2-alpha.2 <0.2.0-0` | `engines.dsh` + peers `>=0.1.2-alpha.2 <0.2.0-0` |
282
+ | `0.5.0` (本期 / this release) | pre-release | **DSH `0.1.2-alpha.2` … `<0.3.0-0`** — 设置卡迁移到 entry-id volatile Config(`0.1.7+`/`0.2.x`),`≤0.1.5` 自动回退 `installSection` | `engines.dsh` + peers `>=0.1.2-alpha.2 <0.3.0-0` |
261
283
 
262
284
  ### Where the API family broke — `0.1.2-alpha.2`
263
285
 
@@ -282,36 +304,72 @@ HTTP route (`/api/dsh-engram/settings`, same-origin fetch like the memory
282
304
  viewer), so the card is generation-agnostic on both sides of the
283
305
  `0.1.2-alpha.2` boundary.
284
306
 
307
+ **One more settings break, at `0.1.7` / `0.2.0`** — and the reason `0.4.0` was
308
+ not actually loadable on the current DSH: `@deepseek-ai/dsh-settings` **removed
309
+ `settings.installSection` again** in `0.1.7-alpha.2` (and keeps it removed in
310
+ `0.2.x`). The new settings model is **entry-keyed**: editable fields are
311
+ `.volatile()` fields of the plugin's own `Config` (validated + hot-committed by
312
+ the loader), and the settings form is keyed by the profile entry id (`engram`),
313
+ so nothing needs "mounting". `0.5.0` feature-detects the two generations in
314
+ `installEngramSettings`:
315
+
316
+ - DSH `≥0.1.7` / `0.2.x` — the 27 GUI knobs are the volatile `Config` fields
317
+ (`lib/settings.js` `makeEngineConfigSchema`, exported as `Config` from
318
+ `lib/index.js`); the card reads/writes entry `engram` through the same
319
+ `/api/dsh-engram/settings` HTTP route, and the `service.settings` seam
320
+ re-syncs `resolved` on `loader/volatile-update` so card edits land live on
321
+ new sessions.
322
+ - DSH `≤0.1.5` — the legacy `settings.installSection` namespace (`dsh-engram`)
323
+ is registered as before; nothing changes.
324
+
325
+ The declared range `>=0.1.2-alpha.2 <0.3.0-0` covers both, and the HTTP wire
326
+ namespace stays `dsh-engram` on both generations (the seam's active namespace
327
+ drives mutations).
328
+
285
329
  So: keep DSH on `0.1.1-rc.2` or older while running a release ≤ `0.3.6`, and
286
330
  upgrade the plugin to `>=0.3.7` before moving DSH to `0.1.2-alpha.2`.
287
331
 
288
- ### Current release — `0.4.0`
332
+ ### Current release — `0.5.0`
289
333
 
290
- This release declares support for DSH `>=0.1.2-alpha.2 <0.2.0-0` and was fully
291
- validated against the `dsh-v0.1.5-alpha.1` source and web runtime from the local
292
- `deepseek-harness` checkout.
334
+ The published `0.4.0` declared `>=0.1.2-alpha.2 <0.2.0-0` — which the semver
335
+ gate **rejects on every DSH `0.2.0-0` prerelease** (`<0.2.0-0` excludes
336
+ `0.2.0-rc.x`), and its host settings half still called the `installSection`
337
+ API that `dsh-settings 0.1.7+` removed. `0.5.0` fixes **both**: the range
338
+ opens to `<0.3.0-0` (verified against DSH's own `includePrerelease` check, so
339
+ `0.2.0-rc.2` and the whole `0.2.x` line pass), and the settings card is
340
+ migrated onto the entry-id **volatile Config** model described above (with a
341
+ feature-detected legacy fallback).
293
342
 
294
343
  | Component | Supported range |
295
344
  | --- | --- |
296
- | DSH runtime / host / client package family | `>=0.1.2-alpha.2 <0.2.0-0` |
345
+ | DSH runtime / host / client package family | `>=0.1.2-alpha.2 <0.3.0-0` |
297
346
  | `@deepseek-ai/cordis` | `^4.0.2` |
298
347
  | `@deepseek-ai/schemastery` | `^3.18.2` |
299
348
  | Node.js | `>=22.19.0` |
300
349
 
301
- `0.1.2-alpha.2` is the minimum version for this API family: host settings use
302
- `settings.installSection`, and the browser settings card reads and writes
303
- through the plugin's own HTTP route (`/api/dsh-engram/settings`, see Route B
304
- above). Host import and settings-registration smoke checks pass on
305
- `0.1.2-alpha.2` and `0.1.3-alpha.2`; the full regression suite is validated
306
- on `0.1.5-alpha.1` (real-machine GET/PUT/conflict/reset/persist-across-restart
307
- all verified on `dsh 0.1.5-rc.1` with an isolated profile). The older
350
+ `0.1.2-alpha.2` is the minimum version for this API family: host settings are
351
+ **feature-detected** — on DSH `≤0.1.5` they register the `dsh-engram`
352
+ namespace via `settings.installSection`; on `≥0.1.7` / `0.2.x` the form is the
353
+ plugin's `Config` with the 27 GUI knobs marked `.volatile()` (entry id
354
+ `engram`). The browser settings card reads and writes through the plugin's own
355
+ HTTP route (`/api/dsh-engram/settings`, see Route B above) on both. The full
356
+ regression suite (npm test 329/329, incl. the new settings-v2 coverage for the
357
+ volatile-Config model) passes against the DSH `0.2.0-rc.2` dependency set
358
+ (cordis 4.0.4 + schemastery 3.18.4 + cosmokit, matching the global DSH CLI).
359
+ The `.volatile()`/loader hot-commit path and the describe-side projection were
360
+ verified against the real `dsh-settings 0.2.0-rc.2` utilities, and a full
361
+ real-machine boot smoke on an isolated `dsh 0.2.0-rc.2` profile **passed**:
362
+ the plugin boots with no version-range denial, the settings card form is
363
+ served for entry `engram` (all 27 GUI knobs, incl. `trustedHosts`), and a
364
+ GET → PUT (`mutate` ops, revision 0→1) → unset round trip on
365
+ `/api/dsh-engram/settings` lands in the profile row and reads back. The older
308
366
  `0.1.0-rc.7` and `0.1.1` API graphs are outside this release's compatibility
309
367
  range.
310
368
 
311
369
  To install the DSH version used for validation:
312
370
 
313
371
  ```sh
314
- npm install --global @deepseek-ai/dsh@0.1.5-alpha.1
372
+ npm install --global @deepseek-ai/dsh@0.2.0-rc.2
315
373
  dsh --version
316
374
  ```
317
375
 
@@ -705,9 +763,59 @@ later request. The two switches are independent.
705
763
  simply never trigger (no boundary → no prune, zero side effects); the
706
764
  corpus-wide dry run activates on 38% of sessions. If the pruner service is
707
765
  absent the mechanism disables itself with one log line.
766
+ - **Boundary sources**: todo-progress completion and goal terminal events
767
+ (default), plus an optional **request-count cadence**
768
+ (`boundaryPruneIdleCadence`, default 0/off) — the "step idle" source that
769
+ covers the ~62% of sessions which never touch todo/goal (recon:
770
+ `eval/recon-idle-boundary.mjs`; +85% saved chars at cadence 50 in the
771
+ counterfactual `eval/simulate-idle-cadence.mjs`), still repayment-gated.
772
+ - **Threshold override**: `boundaryPruneThresholdChars` (default 0 → host's
773
+ 8192). Set e.g. 2048 with smaller retained edges
774
+ (`boundaryPruneHeadChars`/`boundaryPruneTailChars`) to prune earlier —
775
+ T2048+small-edges lands 2.4× the first-prune savings of the host default on
776
+ the real corpus (10.14M vs 4.23M chars). Lowering the threshold is safe with
777
+ `resultPack` on, which archives the removed middles.
708
778
  - Default **off**: `boundaryPrune: false`. When on, the log shows
709
779
  `engram boundary-prune (todo @N reqs): pruned …`.
710
780
 
781
+ ### ResultPack L2 (archive + `result_recall`, default OFF)
782
+
783
+ BoundaryPrune makes pruning lossless: the removed middle of every pruned
784
+ tool result is redacted (same rules as the memory write path) and archived to
785
+ `~/.dsh/engram/resultpack/<callId>.json`, and the pruned surface node gets a
786
+ tail handle — `[middle archived: result_recall(<callId>) 可取]` — so the model
787
+ knows the full result is one step away. `result_recall(callId, offset?)`
788
+ returns the archived middle in paged text blocks (progressive disclosure, the
789
+ same philosophy as `[ENGRAM]` → `engram_detail`, and the complete
790
+ ObservationPack pattern from SoL-Pi mechanism ③).
791
+
792
+ - **Lossless by construction**: L1 pruning shrinks the replay; L2 keeps the
793
+ original recoverable on demand — the "model rereads a pruned result" failure
794
+ mode goes to zero.
795
+ - **Scoped cost**: archives live outside the memory domain (big blobs never
796
+ bloat the domain JSON or the [ENGRAM] index); retention is a mtime sweep
797
+ (`resultPackRetentionDays`, default 7) riding the same GC cadence.
798
+ - **Self-disabling**: L2 without `boundaryPrune` has nothing to archive, so it
799
+ registers no tool and emits one warning.
800
+ - Default **off**: `resultPack: false` (must be on together with
801
+ `boundaryPrune: true`). When active the log shows
802
+ `engram result-pack enabled: result_recall(callId, offset) registered`.
803
+
804
+ ### Activation telemetry (SoL-Pi M15/C13)
805
+
806
+ Mechanisms must be provably activating, and dormant ones are off by default.
807
+ `/api/dsh-engram/stats` (and the GUI overview) now answer
808
+ `activation: { boundaryPrune, recall }`:
809
+
810
+ - `boundaryPrune` — cumulative, durable record of real BoundaryPrune fires
811
+ (fires / prunedResults / charsRemoved / byKind todo|goal|step / lastFire),
812
+ written to `~/.dsh/engram/boundary-prune.status.json` on every landed prune,
813
+ independent of `resultPack`.
814
+ - `recall` — the injection leg of the recall funnel: how many `[RECALL]`
815
+ blocks actually made it into sessions (`recall-injections.json`), closing
816
+ the loop already measured from session logs (engram_recall queries → hits →
817
+ engram_detail follows) so you can see injection→query→detail activation.
818
+
711
819
  ## Auto-capture policy
712
820
 
713
821
  Capture is deterministic and offline — it only sees tool *results*, never the
@@ -790,6 +898,15 @@ Defaults are token-conscious; override any key via the profile patch
790
898
  - id: engram
791
899
  config:
792
900
  autoCapture: true # zero-LLM tool-result capture
901
+ autoCapturePreferences: true # zero-LLM user-correction/preference capture (prefer/avoid/switch-to cues)
902
+ preferenceCapturePerSession: 8 # per-session preference memory cap
903
+ boundaryPrune: false # ResultPack L1: prune stale over-threshold tool results at todo/goal boundaries (default OFF)
904
+ boundaryPruneThresholdChars: 0 # threshold override (0 = host 8192; e.g. 2048 prunes earlier)
905
+ boundaryPruneHeadChars: 0 # retained head under an override (0 = host 4096)
906
+ boundaryPruneTailChars: 0 # retained tail under an override (0 = host 1024)
907
+ boundaryPruneIdleCadence: 0 # request-count boundary for todo/goal-less sessions (0 = event-only)
908
+ resultPack: false # ResultPack L2: archive pruned middles + result_recall paged recall (default OFF; needs L1 on)
909
+ resultPackRetentionDays: 7 # archive retention days (mtime sweep on the GC cadence)
793
910
  sessionSearch: true # engram_recall may also FTS past sessions
794
911
  recallScope: workspace # recall scope: "workspace" (strict isolation, default) | "global" (opt-in cross-workspace recall with [W:<ws>] origin markers)
795
912
  autoRecallOnStart: true # session-start auto recall (false = back to pure pull)
@@ -816,7 +933,7 @@ Defaults are token-conscious; override any key via the profile patch
816
933
  ## Development
817
934
 
818
935
  ```sh
819
- npm test # 279 tests: core + web API + GC + usage-observability + Context GC + ESR triggers + BoundaryPrune (node:test)
936
+ npm test # 291 tests: core + web API + GC + usage-observability + Context GC + ESR triggers + BoundaryPrune + preference learning (node:test)
820
937
  npm run eval # offline recall + structure benchmark (deterministic)
821
938
  npm run build:client
822
939
  ```
package/README.zh.md CHANGED
@@ -17,6 +17,15 @@
17
17
  + 失败行)自动捕获为 `tags:["error","test"]`、信号上调。DSH **goal 域打通**:completed/blocked 的
18
18
  goal(`goal/change` 会话事件)自动沉淀为 `handoff`/`error` 记忆(`tag:goal`),目标结局不随会话消散
19
19
  (读取面见 `GET /api/dsh-engram/goals`)。
20
+ - **用户纠正/偏好学习(`autoCapturePreferences`,默认开,零 LLM)** — 「AI 犯错被纠正后,教训要留存」
21
+ ([correx](https://github.com/ozoz5/correx)、[PAHF](https://arxiv.org/abs/2602.16173)、Mem0 偏好提取):
22
+ 一个 `session/event` 监听器盯 `user/message`,用确定性纠正/偏好句式把用户话语沉淀为独立的
23
+ `preference` 记忆 kind——中文(`不要用X`、`改用X`、`以后统一用X`…)与英文(`don't use X`、
24
+ `switch to X`、`prefer X`、`never use X`…)模式,三种方向(`avoid` / `switch-to` / `prefer`),
25
+ 并带疑问句 / 一次性操作指令 / 记忆工具元话语过滤器抑制误报。`preference` 记忆在 `[ENGRAM]`
26
+ 块里**压过其它所有 kind**(`[R]` 前缀),让用户的长期规则成为每会话模型看到的第一行;
27
+ 每会话上限 + 近期窗口去重与自动捕获同纪律。零 LLM、零向量、确定性纯函数——提取片段有界,
28
+ 每条写入依旧先过密钥脱敏器。
20
29
  - **符号索引 + 渐进披露** — 一个紧凑的 `[ENGRAM]` 块(默认预算 700 字符 ≈ 175 token;每条记忆一行)在
21
30
  组装提示词时注入,并**按会话冻结**,让请求前缀字节稳定以复用 KV 缓存。模型需要细节时用
22
31
  `engram_recall` / `engram_detail` 下钻,而不是把命中的原文灌进上下文。召回对内存池做
@@ -35,6 +44,10 @@
35
44
  agentmemory 候选①)——相关记忆不依赖模型想起来就已在上下文里。纯规则、零 LLM、
36
45
  纯读不 bump 命中;superseded 过期真相不占注入槽(仍可 `engram_detail` 重取);
37
46
  与 `[ENGRAM]` 一起按会话冻结保持前缀稳定。
47
+ - **读路径激活(D)** — 命中条目的单行预览被截断(>140 字符)时带 `…` 标记,且块尾
48
+ 引导行点名该具体 id:`#<id> truncated — engram_detail <id> (full) · engram_recall <query>`。
49
+ 模型由此知道完整记录就在一次确定性 `engram_detail` 之后,而不是以为预览就是全文
50
+ (实证盲区:66 个会话 0 次 `engram_detail`——下钻路径此前对模型不可见)。
38
51
  - **记忆间语义(supersede/contradict)** — `engram_store` 可选收 `supersedes` / `contradicts`
39
52
  记忆 id(同工作区校验)。被 supersede 的「过期真相」在召回里**降级到尾部**、并从 `[ENGRAM]`
40
53
  块中**剔除**(新陈述占行);被 contradicts 的记忆保留排序但标注 `· contradicted by <id>`。
@@ -153,8 +166,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
153
166
 
154
167
  <!-- dsh-market 兼容信息块 —— 与 docs/RELEASE-NOTES.TEMPLATE.md 的市场 README 公约保持一致 -->
155
168
 
156
- - **DSH 版本**:`>=0.1.2-alpha.2 <0.2.0-0`(dsh-engram `0.3.7`;`0.2.0`–`0.3.6` 仅旧代际 —— 见下文矩阵)
157
- - **最后验证日期**:2026-09-10 —— `dsh-v0.1.5-alpha.1` 完整回归;`dsh 0.1.5-rc.1` 隔离 profile 真机验证设置 GET/PUT/冲突/复位/跨重启持久化
169
+ - **DSH 版本**:`>=0.1.2-alpha.2 <0.3.0-0`(本版 `0.5.0`;已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` —— 见下文矩阵)
170
+ - **最后验证日期**:2026-10-15 —— 设置模型迁移(entry-id volatile Config,兼容 DSH `0.2.x`)落地,npm test 329/329 通过;真实 `dsh 0.2.0-rc.2` 隔离 profile 运行烟测通过(见下文“当前发布”)
158
171
  - **安装方式已验证**:`dsh plugin --profile web add dsh-engram`
159
172
 
160
173
  > **一句话结论**:所有**已真正发布**的版本(`0.2.0` … `0.3.6`)**只支持旧版
@@ -165,7 +178,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
165
178
  | dsh-engram 发布版本 | 状态 | 支持的 DSH | 声明方式 |
166
179
  | --- | --- | --- | --- |
167
180
  | `0.2.0` … `0.3.6`(npm 全量 + GitHub v0.3.3–v0.3.6) | 已发布 | **仅旧版** —— `0.1.0-rc.7` / `0.1.0-rc.8` / `0.1.1-rc.1` / `0.1.1-rc.2` | peers `@deepseek-ai/dsh-*@^0.1.0-rc.7` |
168
- | `0.3.7`(npm + GitHub v0.3.7) | 已发布 | **仅新版** —— `>=0.1.2-alpha.2 <0.2.0-0` | `engines.dsh` + peers 同范围 |
181
+ | `0.3.7` / `0.4.0`(npm) | 已发布 | **新版 DSH,settings `installSection`** —— `>=0.1.2-alpha.2 <0.2.0-0` | `engines.dsh` + peers 同范围 |
182
+ | `0.5.0`(本期 / this release) | 预发布 | **DSH `0.1.2-alpha.2` … `<0.3.0-0`** —— 设置卡迁移到 entry-id volatile Config(`0.1.7+`/`0.2.x`),`≤0.1.5` 自动回退 `installSection` | `engines.dsh` + peers `>=0.1.2-alpha.2 <0.3.0-0` |
169
183
 
170
184
  ### API 族断裂点:`0.1.2-alpha.2`
171
185
 
@@ -185,33 +199,59 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
185
199
  HTTP 路由(`/api/dsh-engram/settings`,与记忆 viewer 同样的同源 fetch)读写
186
200
  `dsh-engram` settings namespace,因此在 `0.1.2-alpha.2` 分界线两侧都是代际无关的。
187
201
 
202
+ **`0.1.7` / `0.2.0` 还有一次 settings 断裂**——`0.4.0` 在最新 DSH 上启动被拒的
203
+ 真正原因:`@deepseek-ai/dsh-settings` 在 `0.1.7-alpha.2` **再次移除了
204
+ `settings.installSection`**(`0.2.x` 一路保留)。新设置模型是**条目式**的:可编辑字段
205
+ 是插件自身 `Config` 的 `.volatile()` 字段(由 loader 校验并热提交),设置表单以
206
+ profile 条目 id(`engram`)为命名空间,不再需要任何“挂载”调用。`0.5.0`
207
+ 在 `installEngramSettings` 里对两代际做特性检测:
208
+
209
+ - DSH `≥0.1.7` / `0.2.x` —— 27 个 GUI 旋钮即 `Config` 的 volatile 字段
210
+ (`lib/settings.js` `makeEngineConfigSchema`,由 `lib/index.js` 导出为
211
+ `Config`);卡片仍走同一 `/api/dsh-engram/settings` HTTP 路由读写条目 `engram`,
212
+ `service.settings` 接缝在 `loader/volatile-update` 事件上把 `resolved`
213
+ 重新同步,卡片编辑对新会话即时生效。
214
+ - DSH `≤0.1.5` —— 照旧注册旧 `settings.installSection` 命名空间(`dsh-engram`)。
215
+
216
+ 声明范围 `>=0.1.2-alpha.2 <0.3.0-0` 同时覆盖两代际;HTTP wire 命名空间在两代际都保持
217
+ `dsh-engram`(由接缝的活动命名空间驱动写入)。
218
+
188
219
  所以:跑 ≤ `0.3.6` 的已发布版本时请把 DSH 保持在 `0.1.1-rc.2` 或更旧;升级
189
220
  DSH 到 `0.1.2-alpha.2` 之前,先升级插件到 `>=0.3.7`。
190
221
 
191
- ### 当前发布版本 —— `0.4.0`
222
+ ### 当前发布版本 —— `0.5.0`
192
223
 
193
- 当前发布版本声明适配 DSH `>=0.1.2-alpha.2 <0.2.0-0`,并以本地
194
- `deepseek-harness` 的 `dsh-v0.1.5-alpha.1` 代码和 Web 运行时完成完整验证。
224
+ 已发布的 `0.4.0` 声明 `>=0.1.2-alpha.2 <0.2.0-0`——这个范围被 semver 门槛在
225
+ **所有 DSH `0.2.0-0` 预发布**上拒绝(`<0.2.0-0` 排除 `0.2.0-rc.x`),且其宿主设置端
226
+ 仍在调用 `dsh-settings 0.1.7+` 已移除的 `installSection` API。`0.5.0`
227
+ **两处都修复**:范围放宽到 `<0.3.0-0`(用 DSH 自身的 `includePrerelease` 检查验证过,
228
+ `0.2.0-rc.2` 及整个 `0.2.x` 线都放行);设置卡迁移到上面描述的 entry-id **volatile
229
+ Config** 模型(并带特性检测的旧代际回退)。
195
230
 
196
231
  | 组件 | 适配版本 |
197
232
  | --- | --- |
198
- | DSH runtime / host / client 包族 | `>=0.1.2-alpha.2 <0.2.0-0` |
233
+ | DSH runtime / host / client 包族 | `>=0.1.2-alpha.2 <0.3.0-0` |
199
234
  | `@deepseek-ai/cordis` | `^4.0.2` |
200
235
  | `@deepseek-ai/schemastery` | `^3.18.2` |
201
236
  | Node.js | `>=22.19.0` |
202
237
 
203
- `0.1.2-alpha.2` 是这组 API 的最低版本:宿主设置注册使用
204
- `settings.installSection`,浏览器端设置卡通过插件自有 HTTP 路由
205
- (`/api/dsh-engram/settings`,见上文 Route B)读写。
206
- `0.1.2-alpha.2` 和 `0.1.3-alpha.2` 已完成宿主导入与设置注册 smoke 验证;
207
- `0.1.5-alpha.1` 已完成完整回归(并在 `dsh 0.1.5-rc.1` 隔离 profile 上真机验证
208
- 设置 GET/PUT/冲突/复位/跨重启持久化全通)。旧的 `0.1.0-rc.7` 与 `0.1.1` API 图不在
238
+ `0.1.2-alpha.2` 是这组 API 的最低版本:宿主设置注册按代际**特性检测**——DSH `≤0.1.5`
239
+ 走 `settings.installSection` 注册 `dsh-engram` 命名空间;`≥0.1.7` / `0.2.x` 走插件
240
+ `Config`(27 个 GUI 旋钮标记为 `.volatile()`,条目 id `engram`)。浏览器设置卡在两代际
241
+ 都经插件自有 HTTP 路由(`/api/dsh-engram/settings`,见上文 Route B)读写。完整回归
242
+ (npm test 329/329,含新增的 volatile-Config 模型 settings-v2 覆盖)在 DSH
243
+ `0.2.0-rc.2` 依赖集(cordis 4.0.4 + schemastery 3.18.4 + cosmokit,与全局 DSH CLI 同版本)
244
+ 上通过;`.volatile()`/loader 热提交路径与 describe 侧投影已针对真实 `dsh-settings
245
+ 0.2.0-rc.2` 工具函数验证;并在隔离的 `dsh 0.2.0-rc.2` profile 上完成了**真机启动烟测并
246
+ 通过**:插件启动无版本范围拒绝、设置卡表单以条目 `engram` 对外提供(全部 27 个 GUI
247
+ 旋钮,含 `trustedHosts`)、`/api/dsh-engram/settings` 的 GET → PUT(mutate ops,
248
+ revision 0→1)→ unset 往返落盘并读回。旧的 `0.1.0-rc.7` 与 `0.1.1` API 图不在
209
249
  本发布版本的兼容范围内。
210
250
 
211
251
  如需按验证版本安装 DSH:
212
252
 
213
253
  ```sh
214
- npm install --global @deepseek-ai/dsh@0.1.5-alpha.1
254
+ npm install --global @deepseek-ai/dsh@0.2.0-rc.2
215
255
  dsh --version
216
256
  ```
217
257
 
@@ -333,7 +373,7 @@ npm run build:client
333
373
  ## 测试与评测
334
374
 
335
375
  ```sh
336
- npm test # 279 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发 / BoundaryPrune)
376
+ npm test # 311 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发 / BoundaryPrune)
337
377
  npm run eval # 离线召回 + 结构基准(确定性语料,跑真实 store/recall 路径)
338
378
  ```
339
379
 
@@ -518,9 +558,48 @@ Context GC 管「压缩的摘要内容」,BoundaryPrune 管「确定性剪枝
518
558
  workspace 历史先验 + 超过后 ×2 外推(有界乐观),长会话不会被挡死。
519
559
  - **诚实激活面**:goal=0 且无 todo 推进的会话天然不触发(无边界不剪,零副作用);
520
560
  全量干跑 77 会话激活 38%。pruner 服务缺席即自动禁用并打一条日志。
561
+ - **边界源**:默认 todo 完成推进 / goal 终态事件;可选**空闲步数边界**
562
+ (`boundaryPruneIdleCadence`,默认 0=关)——覆盖约 62% 从不动 todo/goal 的
563
+ 会话(侦察:`eval/recon-idle-boundary.mjs`;反事实:`eval/simulate-idle-cadence.mjs`
564
+ 显示 cadence 50 时节省 +85%),仍受偿还门约束。
565
+ - **阈值覆盖**:`boundaryPruneThresholdChars`(默认 0=宿主 8192)。设为 2048
566
+ 并配更小保留边缘(`boundaryPruneHeadChars`/`boundaryPruneTailChars`)可更早
567
+ 开剪——T2048+小边缘在真实语料上首剪节省是宿主默认的 2.4 倍(10.14M vs 4.23M
568
+ 字符)。降阈值在 `resultPack` 开启时安全(被剪中段有归档兜底)。
521
569
  - 默认**关**:`boundaryPrune: false`。开启后启动日志出现
522
570
  `engram boundary-prune (todo @N reqs): pruned …`。
523
571
 
572
+ ### ResultPack L2(结果归档 + `result_recall`,默认关)
573
+
574
+ 让 BoundaryPrune 剪枝**无损**:每个被剪工具结果的被移除中段,按与记忆写入
575
+ 相同的规则脱敏后归档到 `~/.dsh/engram/resultpack/<callId>.json`,并在剪枝
576
+ 节点尾部追加 handle——`[middle archived: result_recall(<callId>) 可取]`——
577
+ 模型知道完整结果就在一步之遥。`result_recall(callId, offset?)` 按页返回
578
+ 归档中段(渐进披露,与 `[ENGRAM]` → `engram_detail` 同一哲学,也是 SoL-Pi
579
+ 机制③ ObservationPack 的完整形态)。
580
+
581
+ - **构造性无损**:L1 剪掉的是重放、L2 保留的是可随时取回的原文——「模型回头
582
+ 找被剪结果」的失败模式归零。
583
+ - **成本受控**:归档在记忆域之外(大块内容永远不会撑大 domain JSON 或
584
+ [ENGRAM] 索引);保留期 `resultPackRetentionDays`(默认 7 天)按 mtime 清理,
585
+ 搭 GC 同一节奏。
586
+ - **自我禁用**:只开 L2 不开 L1 将无内容可归档,故不注册工具并打一条警告。
587
+ - 默认**关**:`resultPack: false`(须与 `boundaryPrune: true` 同开)。开启后
588
+ 启动日志出现 `engram result-pack enabled: result_recall(callId, offset) registered`。
589
+
590
+ ### 激活遥测(SoL-Pi M15/C13)
591
+
592
+ 机制必须可证明在激活,休眠机制默认关。`/api/dsh-engram/stats`(以及 GUI
593
+ overview)现在返回 `activation: { boundaryPrune, recall }`:
594
+
595
+ - `boundaryPrune`——BoundaryPrune 真实 fire 的累计持久记录(fires /
596
+ prunedResults / charsRemoved / byKind todo|goal|step / lastFire),每次剪枝
597
+ 落盘到 `~/.dsh/engram/boundary-prune.status.json`,与 `resultPack` 无关。
598
+ - `recall`——召回漏斗的**注入腿**:实际进过会话的 `[RECALL]` 块数量
599
+ (`recall-injections.json`),与从 session log 已统计的另一端
600
+ (engram_recall 查询 → 命中 → engram_detail 下钻)闭合,可看到
601
+ 注入→查询→下钻的完整激活。
602
+
524
603
  ## 自动捕获策略
525
604
 
526
605
  捕获是确定性、离线的——只看到工具*结果*,从不看对话本身。什么会被记录成一条记忆:
@@ -591,6 +670,15 @@ promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-pr
591
670
  - id: engram
592
671
  config:
593
672
  autoCapture: true # 零 LLM 工具结果捕获
673
+ autoCapturePreferences: true # 零 LLM 用户纠正/偏好捕获(prefer/avoid/switch-to 句式)
674
+ preferenceCapturePerSession: 8 # 每会话偏好记忆条数上限
675
+ boundaryPrune: false # ResultPack L1:todo/goal 边界剪掉超阈值旧工具结果(默认关)
676
+ boundaryPruneThresholdChars: 0 # 阈值覆盖(0=宿主 8192;如 2048 可更早开剪)
677
+ boundaryPruneHeadChars: 0 # 覆盖时的保留头部(0=宿主 4096)
678
+ boundaryPruneTailChars: 0 # 覆盖时的保留尾部(0=宿主 1024)
679
+ boundaryPruneIdleCadence: 0 # 空闲步数边界(0=仅事件边界;覆盖无 todo/goal 会话)
680
+ resultPack: false # ResultPack L2:剪掉的中段脱敏归档 + result_recall 按页召回(默认关,须与 L1 同开)
681
+ resultPackRetentionDays: 7 # 归档保留天数(mtime 清理,走 GC 节奏)
594
682
  sessionSearch: true # engram_recall 也可对历史会话 FTS
595
683
  recallScope: workspace # 召回范围:workspace(严格工作区隔离,默认)| global(可选跨工作区召回,带 [W:<ws>] 来源标记)
596
684
  autoRecallOnStart: true # 会话启动自动召回:按首条消息注入 [RECALL] 块(false=回到纯拉取)
@@ -3,7 +3,7 @@
3
3
  > 依据:[SoL-Pi](https://nvlabs.github.io/SoL-Pi/) 四幸存机制之二(ObservationPack:归档载荷、上下文留 handle+摘录、按页召回)
4
4
  > 与提案池 C11「Reduce tool output before its first prompt insertion」/ C23「Build observation packs before paying for full bodies」。
5
5
  > 数据:[Oracle Analysis](./ORACLE-ANALYSIS.zh.md)(97 真实会话、13.9K 请求、15.0K 工具调用)。
6
- > **状态:L1 已实现(`lib/boundary-prune.js`,默认关;L0 见 §6,全量干跑见 §6.1)。L2 归档召回未实施。**
6
+ > **状态:L1 已实现(`lib/boundary-prune.js`,默认关;L0 见 §6,全量干跑见 §6.1)。L2 已实现(`lib/result-pack.js`,默认关)。**
7
7
 
8
8
  ---
9
9
 
@@ -56,6 +56,8 @@ L1 剪掉的中段今天直接丢弃(仅 session 日志保留原文,模型取不
56
56
  - 注册 `result_recall(callId, offset?)` 工具,按页返回归档中段 —— 与 [ENGRAM] 渐进披露同一哲学:索引行→详情下钻;
57
57
  - 剪枝结果尾部追加一行 handle:`[middle pruned: result_recall(callId) 可取]`,模型可自主决定是否下钻(M3 遥测将能看到激活率)。
58
58
 
59
+ > **已实现(2026-09-23,§8)**:实现细节与设计中两处修正——①归档落 `~/.dsh/engram/resultpack/<callId>.json`(独立文件而非域表,大块内容不撑 domain JSON);②handle 走宿主 surface-replace 协议(`session.append("tool/result", …, surfaceOp replace)`),replay-safe 零 LLM;脱敏先于持久化。17 项单测 + L1→L2 钩子 3 项 + 进程内端到端(归档→分页→缺档)验证通过。
60
+
59
61
  ### L3 · 上游提案(严格 C11)
60
62
 
61
63
  向 DSH 提 PR:ToolRuntime 开放 per-tool 可扩展 finalizeContent 链(或"结果归档 transport")。附上 oracle 数据(96% 集中在三个工具、top-10 占 46%)作为动机。
@@ -128,8 +130,10 @@ L1 剪掉的中段今天直接丢弃(仅 session 日志保留原文,模型取不
128
130
 
129
131
  ## 8. 后续
130
132
 
131
- - **L2(未实施)**:pruned 中段归档 + `result_recall` 按页召回 + handle 行;T2048 阈值与 L2 绑定。
132
- - **激活遥测**:fire 事件已可从 session log 的 `compaction/prune` 序列读出;建议 /stats 加一个「boundary-prune vs overflow-prune 来源区分」计数(用 fire 前后日志行归属判定)。
133
+ - ~~**L2**~~ **已实现**(2026-09-23, `lib/result-pack.js`):pruned 中段归档 + `result_recall` 按页召回 + handle 行;默认关(`resultPack`,须与 `boundaryPrune` 同开),保留 7 天走 GC;T2048 阈值与 L2 绑定,**现已满足开低阈值的归档前提**。
134
+ - ~~**激活遥测**~~ **已实现**(2026-09-23):`/stats` 与 GUI overview 返回 `activation: { boundaryPrune, recall }` —— BoundaryPrune fire 每次落盘 `~/.dsh/engram/boundary-prune.status.json`(fires/prunedResults/charsRemoved/byKind,与 resultPack 无关),[RECALL] 注入计数落盘 `recall-injections.json`,与 session-log 已统计的 recall 漏斗(查询→命中→detail 下钻)闭合(SoL-Pi M15/C13)。
135
+ - ~~**step 空转边界**~~ **已实现**(2026-09-23):`boundaryPruneIdleCadence`(默认 0=关)——请求计数边界,覆盖无 todo/goal 会话。侦察 105 会话:65 个无边界、含 5.23M over-threshold 暴露(38%);反事实仿真(cadence 50):激活面 36→64 会话、首剪节省 4.23M→7.84M(+85%),仍受偿还门约束。
136
+ - **T2048 落地**(2026-09-23):`boundaryPruneThresholdChars`/`HeadChars`/`TailChars`(默认 0=宿主)——插件以 `Object.create(pruner)` 原型克隆宿主 pruner 实例、仅覆盖 config,宿主不变。真实语料干跑:T2048 H1024/T256 首剪 10.14M vs 宿主默认 4.23M(+2.4x)。
133
137
  - **上游提案(L3)**:ToolRuntime 开放 finalizeContent 链,附 oracle 数据。
134
138
 
135
139
 
@@ -0,0 +1,58 @@
1
+ ## dsh-engram v0.5.0
2
+
3
+ DSH 0.2.x 兼容修复版:`0.1.7` 起 `@deepseek-ai/dsh-settings` 移除了
4
+ `settings.installSection`,设置卡改为**条目式 volatile Config** 模型;本版本把设置
5
+ 绑定迁移到新模型(entry-id `engram`),并把声明范围修到能覆盖 DSH `0.2.x` 全系,
6
+ 使插件在最新 DSH 上恢复加载与设置卡读写。
7
+
8
+ ### 功能
9
+
10
+ - **DSH ≥0.1.7 / 0.2.x 设置模型支持** — 插件 `Config`(`lib/index.js` 导出的
11
+ `Config`)声明全部 48 个 DEFAULTS 键;27 个 GUI 旋钮为 `.volatile()` 字段,
12
+ 设置表单由 DSH 以 profile 条目 id `engram` 对外提供,卡片编辑经
13
+ `loader/volatile-update` **热提交**到运行配置(新会话即时生效,已冻结块保持
14
+ 冻结——插件既有契约)。其余键普通声明,profile patch 里的内部旋钮不再被校验丢弃。
15
+ - **双代际特性检测** — `installEngramSettings` 自动识别宿主:DSH `≤0.1.5`
16
+ 回退旧的 `settings.installSection` 命名空间(`dsh-engram`),行为与 0.4.0 完全
17
+ 一致;`≥0.1.7` / `0.2.x` 走新模型(无挂载调用)。HTTP wire 命名空间两代际都保持
18
+ `dsh-engram`,**客户端 bundle 零改动**。
19
+
20
+ ### 修复
21
+
22
+ - **版本范围**:`engines.dsh` + 5 个 `@deepseek-ai/dsh-*` peer 从
23
+ `>=0.1.2-alpha.2 <0.2.0-0` 修正为 `>=0.1.2-alpha.2 <0.3.0-0`——原范围把
24
+ DSH `0.2.0-rc.x` 全部排除(semver 预发布窗口),插件在最新 DSH 上被兼容门槛拒绝。
25
+ 新范围已用 DSH 自身的 `includePrerelease` 检查实测:`0.2.0-rc.2` 与整个 `0.2.x`
26
+ 线放行,`0.3.0-0` 拒绝。
27
+ - **宿主设置崩溃**:移除对已不存在的 `settings.installSection` 的无条件调用
28
+ (其在 `dsh-settings 0.1.7-alpha.2` 起被删除),改为特性检测。
29
+ - **`boundaryPruneThresholdChars` 默认 `null` → `0`**:与设置卡长期使用的
30
+ “0=用宿主 8192” 语义一致(`0` 与 `null` 在剪枝判定中等价),并让 describe 的
31
+ 表单值完整包含该键。
32
+
33
+ ### 其他
34
+
35
+ - 测试 329/329 通过(新增 `test/settings-v2.test.mjs` 6 个用例,覆盖 volatile
36
+ Config 校验/解包/热同步与两代际安装路径;`test/api.test.mjs` 随 ns 语义更新)。
37
+ - settings 投影与写路径针对真实 `dsh-settings 0.2.0-rc.2` 工具函数
38
+ (`volatileForm`/`projectForm`/`isVolatilePath`/`plainConfig`)验证;
39
+ 真实 cordis 4.0.4 集成验证 Config 校验与热提交。
40
+ - **真机烟测通过**(隔离 profile,`dsh 0.2.0-rc.2`,`--port 3099`):插件启动无
41
+ 版本拒绝、`/api/dsh-engram/settings` 提供 27 键表单、
42
+ GET → PUT(mutate,revision 0→1)→ unset 往返落盘并读回。
43
+ - `scripts/prepublish-check.mjs` 随新导出面与 peer 范围更新,`prepublishOnly`
44
+ 在 0.2.x peer 集上通过。
45
+
46
+ ## DSH 版本支持
47
+
48
+ - **支持的 DSH 范围**:>=0.1.2-alpha.2 <0.3.0-0
49
+ - **peer 声明**:
50
+ - `@deepseek-ai/dsh-compaction`: `>=0.1.2-alpha.2 <0.3.0-0`
51
+ - `@deepseek-ai/dsh-compaction-basic`: `>=0.1.2-alpha.2 <0.3.0-0`
52
+ - `@deepseek-ai/dsh-llm`: `>=0.1.2-alpha.2 <0.3.0-0`
53
+ - `@deepseek-ai/dsh-settings`: `>=0.1.2-alpha.2 <0.3.0-0`
54
+ - `@deepseek-ai/dsh-storage-domain`: `>=0.1.2-alpha.2 <0.3.0-0`
55
+ - **验证版本**:`dsh-v0.2.0-rc.2`(全局 DSH CLI,并针对真实
56
+ `dsh-settings` / `cordis` / `schemastery` 依赖集)
57
+ - **最后验证日期**:2026-10-15
58
+ - **安装方式已验证**:`dsh plugin --profile web add dsh-engram`
package/lib/api.js CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { uuid, shortId } from "./util.js";
14
14
  import { collectToolCounts, collectUsageStats } from "./usage.js";
15
- import { SETTINGS_NS } from "./settings.js";
15
+ import { SETTINGS_NS, WIRE_SETTINGS_NS, LEGACY_SETTINGS_NS } from "./settings.js";
16
16
 
17
17
  /** Route family prefix (kept in sync with the client half). */
18
18
  export const API_PREFIX = "/api/dsh-engram";
@@ -160,8 +160,9 @@ export function makeEngramRoutes(service) {
160
160
  writeJson(res, 200, {
161
161
  ...summary,
162
162
  indexes,
163
- captures: service.captureStats ?? { total: 0, git: 0, file: 0, error: 0 },
163
+ captures: service.captureStats ?? { total: 0, git: 0, file: 0, error: 0, preference: 0 },
164
164
  gc: service.gcStats ?? { lastRun: 0, archivedMemories: 0, archivedTasks: 0, removedLinks: 0 },
165
+ activation: service.packTelemetry ? service.packTelemetry() : { boundaryPrune: null, recall: null },
165
166
  compaction: service.compactionStatus ?? { host: "unknown", web: { action: "unknown", presets: [] } },
166
167
  config: service.config ?? {},
167
168
  anonymous: {
@@ -512,6 +513,10 @@ export function makeEngramRoutes(service) {
512
513
  recallHitsPerQuery: queries > 0 ? +(hitsTotal / queries).toFixed(3) : null,
513
514
  detailFollowRate: queries > 0 ? +(detailFollows / queries).toFixed(4) : null,
514
515
  },
516
+ // SoL-Pi activation telemetry: the durable BoundaryPrune / ResultPack
517
+ // activation record (fires actually landed on this profile) + the
518
+ // [RECALL] injection-funnel leg (blocks actually injected into sessions).
519
+ activation: service.packTelemetry ? service.packTelemetry() : { boundaryPrune: null, recall: null },
515
520
  });
516
521
  };
517
522
 
@@ -561,8 +566,13 @@ export function makeEngramRoutes(service) {
561
566
  // Normalize both onto the HTTP contract {namespaces:[...]} the client uses.
562
567
  const payload = settings.describe();
563
568
  const namespaces = Array.isArray(payload) ? payload : (payload?.namespaces ?? []);
564
- const view = namespaces.find((n) => n && n.ns === SETTINGS_NS);
565
- writeJson(res, 200, view ? { namespaces: [view] } : { namespaces: [] });
569
+ // The seam carries the ACTIVE namespace — entry id `engram` on DSH
570
+ // ≥0.1.7 / 0.2.x, legacy `dsh-engram` on ≤0.1.5. The view is re-labelled
571
+ // onto the stable WIRE_SETTINGS_NS the client card filters on, so the
572
+ // shipped client works unchanged across generations.
573
+ const activeNs = settings.ns ?? SETTINGS_NS;
574
+ const view = namespaces.find((n) => n && (n.ns === activeNs || n.ns === LEGACY_SETTINGS_NS));
575
+ writeJson(res, 200, view ? { namespaces: [{ ...view, ns: WIRE_SETTINGS_NS }] } : { namespaces: [] });
566
576
  } catch (error) {
567
577
  writeError(res, 500, String(error?.message ?? error));
568
578
  }
@@ -586,10 +596,13 @@ export function makeEngramRoutes(service) {
586
596
  return writeError(res, 400, "body must provide ops (array of {op,path[,value]}) or patch (object)");
587
597
  }
588
598
  try {
599
+ // Drive the seam's ACTIVE namespace (entry id `engram` on DSH ≥0.1.7 /
600
+ // 0.2.x, legacy `dsh-engram` on ≤0.1.5), never the wire label.
601
+ const activeNs = settings.ns ?? SETTINGS_NS;
589
602
  if (Array.isArray(ops)) {
590
- await settings.mutate(SETTINGS_NS, ops, expectedRevision);
603
+ await settings.mutate(activeNs, ops, expectedRevision);
591
604
  } else {
592
- await settings.update(SETTINGS_NS, patch, expectedRevision);
605
+ await settings.update(activeNs, patch, expectedRevision);
593
606
  }
594
607
  writeJson(res, 200, { ok: true });
595
608
  } catch (error) {