dsh-engram 0.3.7 → 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
@@ -238,7 +259,14 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
238
259
  -d "{\"payload\": $(cat engram-backup.json), \"mode\": \"merge\"}"
239
260
  ```
240
261
 
241
- ## DSH compatibility
262
+ ## Compatibility
263
+
264
+ <!-- dsh-market compatibility block — keep labels bilingual and in sync with
265
+ docs/RELEASE-NOTES.TEMPLATE.md (market README convention) -->
266
+
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 运行烟测通过(见下方“当前发布”)
269
+ - **安装方式已验证 / Verified install**: `dsh plugin --profile web add dsh-engram`
242
270
 
243
271
  > **Short version.** Every **published** release (`0.2.0` … `0.3.6`) supports
244
272
  > the **old** DSH API graph (`0.1.0-rc.7` / `0.1.1`, peers `^0.1.0-rc.7`)
@@ -250,7 +278,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
250
278
  | dsh-engram release | Status | DSH support | Declared via |
251
279
  | --- | --- | --- | --- |
252
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` |
253
- | `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` |
254
283
 
255
284
  ### Where the API family broke — `0.1.2-alpha.2`
256
285
 
@@ -275,36 +304,72 @@ HTTP route (`/api/dsh-engram/settings`, same-origin fetch like the memory
275
304
  viewer), so the card is generation-agnostic on both sides of the
276
305
  `0.1.2-alpha.2` boundary.
277
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
+
278
329
  So: keep DSH on `0.1.1-rc.2` or older while running a release ≤ `0.3.6`, and
279
330
  upgrade the plugin to `>=0.3.7` before moving DSH to `0.1.2-alpha.2`.
280
331
 
281
- ### Current release — `0.3.7`
332
+ ### Current release — `0.5.0`
282
333
 
283
- This release declares support for DSH `>=0.1.2-alpha.2 <0.2.0-0` and was fully
284
- validated against the `dsh-v0.1.5-alpha.1` source and web runtime from the local
285
- `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).
286
342
 
287
343
  | Component | Supported range |
288
344
  | --- | --- |
289
- | 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` |
290
346
  | `@deepseek-ai/cordis` | `^4.0.2` |
291
347
  | `@deepseek-ai/schemastery` | `^3.18.2` |
292
348
  | Node.js | `>=22.19.0` |
293
349
 
294
- `0.1.2-alpha.2` is the minimum version for this API family: host settings use
295
- `settings.installSection`, and the browser settings card reads and writes
296
- through the plugin's own HTTP route (`/api/dsh-engram/settings`, see Route B
297
- above). Host import and settings-registration smoke checks pass on
298
- `0.1.2-alpha.2` and `0.1.3-alpha.2`; the full regression suite is validated
299
- on `0.1.5-alpha.1` (real-machine GET/PUT/conflict/reset/persist-across-restart
300
- 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
301
366
  `0.1.0-rc.7` and `0.1.1` API graphs are outside this release's compatibility
302
367
  range.
303
368
 
304
369
  To install the DSH version used for validation:
305
370
 
306
371
  ```sh
307
- npm install --global @deepseek-ai/dsh@0.1.5-alpha.1
372
+ npm install --global @deepseek-ai/dsh@0.2.0-rc.2
308
373
  dsh --version
309
374
  ```
310
375
 
@@ -675,6 +740,82 @@ means that preset was customized (or has no compaction group) and was left untou
675
740
  `dsh-compaction-basic unavailable` means the backend is missing and everything
676
741
  falls back to the default compressor.
677
742
 
743
+ ### BoundaryPrune (subtask-boundary pruning, default OFF)
744
+
745
+ Context GC owns *what a compaction summary says*; BoundaryPrune owns *when a
746
+ deterministic prune fires*: at todo-completion progress and goal terminal
747
+ events, it calls DSH's own `toolResultPruner` (deterministic surface-replace,
748
+ zero LLM) so long-stale over-threshold tool results stop replaying in every
749
+ later request. The two switches are independent.
750
+
751
+ - **Evidence chain** ([oracle analysis](docs/ORACLE-ANALYSIS.zh.md) +
752
+ [proposal](docs/PROPOSAL-resultpack.zh.md)): 97 real sessions measured —
753
+ 2.77B–7.53B chars of tool-result replay exposure (~692M–1.88M tokens), 96%
754
+ of result bytes from bash/read/run_code; the counterfactual simulation says
755
+ todo boundaries cover 79% of that exposure and a repayment gate lifts
756
+ cache-aware net savings 12–32% on every threshold config.
757
+ - **Repayment gate**: a prune rewrites the prefix (one KV-cache break), so it
758
+ fires only while the session is still expected to run at least
759
+ `boundaryPruneMinRemaining` (default 50) more requests — expectation is a
760
+ workspace-history prior with ×2 growth projection past it, so long sessions
761
+ are never starved.
762
+ - **Honest activation surface**: sessions with no goal and no todo progress
763
+ simply never trigger (no boundary → no prune, zero side effects); the
764
+ corpus-wide dry run activates on 38% of sessions. If the pruner service is
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.
778
+ - Default **off**: `boundaryPrune: false`. When on, the log shows
779
+ `engram boundary-prune (todo @N reqs): pruned …`.
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
+
678
819
  ## Auto-capture policy
679
820
 
680
821
  Capture is deterministic and offline — it only sees tool *results*, never the
@@ -757,6 +898,15 @@ Defaults are token-conscious; override any key via the profile patch
757
898
  - id: engram
758
899
  config:
759
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)
760
910
  sessionSearch: true # engram_recall may also FTS past sessions
761
911
  recallScope: workspace # recall scope: "workspace" (strict isolation, default) | "global" (opt-in cross-workspace recall with [W:<ws>] origin markers)
762
912
  autoRecallOnStart: true # session-start auto recall (false = back to pure pull)
@@ -783,7 +933,7 @@ Defaults are token-conscious; override any key via the profile patch
783
933
  ## Development
784
934
 
785
935
  ```sh
786
- npm test # 152 tests: core + web API + GC + usage-observability + Context GC + ESR triggers (node:test)
936
+ npm test # 291 tests: core + web API + GC + usage-observability + Context GC + ESR triggers + BoundaryPrune + preference learning (node:test)
787
937
  npm run eval # offline recall + structure benchmark (deterministic)
788
938
  npm run build:client
789
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>`。
@@ -151,6 +164,12 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
151
164
 
152
165
  ## 兼容性
153
166
 
167
+ <!-- dsh-market 兼容信息块 —— 与 docs/RELEASE-NOTES.TEMPLATE.md 的市场 README 公约保持一致 -->
168
+
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 运行烟测通过(见下文“当前发布”)
171
+ - **安装方式已验证**:`dsh plugin --profile web add dsh-engram`
172
+
154
173
  > **一句话结论**:所有**已真正发布**的版本(`0.2.0` … `0.3.6`)**只支持旧版
155
174
  > DSH**(`0.1.0-rc.7` / `0.1.1` API 图,peers `^0.1.0-rc.7`)。DSH
156
175
  > `0.1.2-alpha.2` 及更新版本与它们**不兼容**——settings/client API 族在该版本
@@ -159,7 +178,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
159
178
  | dsh-engram 发布版本 | 状态 | 支持的 DSH | 声明方式 |
160
179
  | --- | --- | --- | --- |
161
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` |
162
- | `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` |
163
183
 
164
184
  ### API 族断裂点:`0.1.2-alpha.2`
165
185
 
@@ -179,33 +199,59 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
179
199
  HTTP 路由(`/api/dsh-engram/settings`,与记忆 viewer 同样的同源 fetch)读写
180
200
  `dsh-engram` settings namespace,因此在 `0.1.2-alpha.2` 分界线两侧都是代际无关的。
181
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
+
182
219
  所以:跑 ≤ `0.3.6` 的已发布版本时请把 DSH 保持在 `0.1.1-rc.2` 或更旧;升级
183
220
  DSH 到 `0.1.2-alpha.2` 之前,先升级插件到 `>=0.3.7`。
184
221
 
185
- ### 当前发布版本 —— `0.3.7`
222
+ ### 当前发布版本 —— `0.5.0`
186
223
 
187
- 当前发布版本声明适配 DSH `>=0.1.2-alpha.2 <0.2.0-0`,并以本地
188
- `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** 模型(并带特性检测的旧代际回退)。
189
230
 
190
231
  | 组件 | 适配版本 |
191
232
  | --- | --- |
192
- | 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` |
193
234
  | `@deepseek-ai/cordis` | `^4.0.2` |
194
235
  | `@deepseek-ai/schemastery` | `^3.18.2` |
195
236
  | Node.js | `>=22.19.0` |
196
237
 
197
- `0.1.2-alpha.2` 是这组 API 的最低版本:宿主设置注册使用
198
- `settings.installSection`,浏览器端设置卡通过插件自有 HTTP 路由
199
- (`/api/dsh-engram/settings`,见上文 Route B)读写。
200
- `0.1.2-alpha.2` 和 `0.1.3-alpha.2` 已完成宿主导入与设置注册 smoke 验证;
201
- `0.1.5-alpha.1` 已完成完整回归(并在 `dsh 0.1.5-rc.1` 隔离 profile 上真机验证
202
- 设置 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 图不在
203
249
  本发布版本的兼容范围内。
204
250
 
205
251
  如需按验证版本安装 DSH:
206
252
 
207
253
  ```sh
208
- npm install --global @deepseek-ai/dsh@0.1.5-alpha.1
254
+ npm install --global @deepseek-ai/dsh@0.2.0-rc.2
209
255
  dsh --version
210
256
  ```
211
257
 
@@ -327,10 +373,18 @@ npm run build:client
327
373
  ## 测试与评测
328
374
 
329
375
  ```sh
330
- npm test # 152 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发)
376
+ npm test # 311 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发 / BoundaryPrune)
331
377
  npm run eval # 离线召回 + 结构基准(确定性语料,跑真实 store/recall 路径)
332
378
  ```
333
379
 
380
+ 另有三个**会话语料分析工具**(零 LLM、确定性,读 `~/.dsh/sessions` 真实日志):
381
+
382
+ ```sh
383
+ node eval/oracle-analysis.mjs # 浪费源量化:结果重放暴露 / 融合候选 / 机制激活率
384
+ node eval/simulate-boundary-prune.mjs # L0 反事实仿真:边界剪枝的预期节省与偿还门扫掠
385
+ node eval/dryrun-boundary-prune.mjs # BoundaryPrune 全量干跑:真实激活面与节省
386
+ ```
387
+
334
388
  `npm run eval` 的检索部分参照 LongMemEval 的问答式评测:受控语料(ASCII + CJK、标签/实体/时间戳已知),
335
389
  对真实 `domain.recall()` 逐一校准 **Precision@k / Recall@k / MRR / Hit@1**(probe 覆盖 tag 精确、子串、
336
390
  多词、CJK、短语唯一、标签排序、负样本无回);结构部分借鉴 StructMemEval:精确去重率(同文本存 3 次折叠为 1)、
@@ -487,6 +541,65 @@ Context GC 的装配在**两个平面**——web 现在**全部自动**,零配
487
541
  - (若见 `… keeping the existing compaction service` 说明同域已有 provider、未替换成功;`web-provision … skipped (custom)`
488
542
  说明某预设是自定义/无 compaction 布局、未被触碰;`dsh-compaction-basic unavailable` 说明环境缺依赖,自动回退默认压缩。)
489
543
 
544
+ ### BoundaryPrune(子任务边界剪枝,默认关)
545
+
546
+ Context GC 管「压缩的摘要内容」,BoundaryPrune 管「确定性剪枝的触发时机」——
547
+ 在 todo 完成推进 / goal 终态这些子任务边界,主动调用 DSH 自带的
548
+ `toolResultPruner`(确定性 surface-replace,零 LLM),把早已超阈值的旧工具
549
+ 结果剪掉头尾保留,省掉它们在后续每个请求里的整段重放。两个开关互相独立。
550
+
551
+ - **证据链**([docs/ORACLE-ANALYSIS.zh.md](docs/ORACLE-ANALYSIS.zh.md) +
552
+ [docs/PROPOSAL-resultpack.zh.md](docs/PROPOSAL-resultpack.zh.md)):97 个真实
553
+ 会话实测,工具结果重放暴露 2.77B–7.53B 字符(≈692M–1.88M token),96% 的
554
+ 结果字节来自 bash/read/run_code;反事实仿真显示 todo 边界覆盖 79% 的暴露,
555
+ 且偿还门在所有配置下提升净收益 12–32%。
556
+ - **偿还门**:剪枝会重写前缀(KV 缓存击穿一次),只有当会话预计还要跑
557
+ `boundaryPruneMinRemaining`(默认 50)个以上请求时才 fire——预计长度用
558
+ workspace 历史先验 + 超过后 ×2 外推(有界乐观),长会话不会被挡死。
559
+ - **诚实激活面**:goal=0 且无 todo 推进的会话天然不触发(无边界不剪,零副作用);
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` 开启时安全(被剪中段有归档兜底)。
569
+ - 默认**关**:`boundaryPrune: false`。开启后启动日志出现
570
+ `engram boundary-prune (todo @N reqs): pruned …`。
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
+
490
603
  ## 自动捕获策略
491
604
 
492
605
  捕获是确定性、离线的——只看到工具*结果*,从不看对话本身。什么会被记录成一条记忆:
@@ -557,6 +670,15 @@ promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-pr
557
670
  - id: engram
558
671
  config:
559
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 节奏)
560
682
  sessionSearch: true # engram_recall 也可对历史会话 FTS
561
683
  recallScope: workspace # 召回范围:workspace(严格工作区隔离,默认)| global(可选跨工作区召回,带 [W:<ws>] 来源标记)
562
684
  autoRecallOnStart: true # 会话启动自动召回:按首条消息注入 [RECALL] 块(false=回到纯拉取)
@@ -575,6 +697,8 @@ promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-pr
575
697
  gcStableRetentionDays: 120 # 超过此天数的 stable 任务离开 [ESR]
576
698
  gcReplacesCompaction: true # Context GC:接管 DSH 自动 compact(false=回退默认 LLM 压缩)
577
699
  gcNarrative: true # 无锚轮次走 scoped LLM 叙事;false=纯机械(零 LLM)
700
+ boundaryPrune: false # BoundaryPrune:todo/goal 边界主动剪超阈值旧工具结果(默认关)
701
+ boundaryPruneMinRemaining: 50 # 偿还门:预计剩余请求数低于此值不剪
578
702
  engramIndexOrder: 40 # systemPrompt section 顺序(位于 tools 段之前)
579
703
  esrOrder: 41
580
704
  ```
@@ -0,0 +1,63 @@
1
+ # Oracle Analysis:真实会话 token 浪费源量化
2
+
3
+ > 方法论来自 [SoL-Pi](https://nvlabs.github.io/SoL-Pi/)(NVIDIA Labs):**动手造机制之前,先在真实轨迹上量化浪费源**("Oracle Analysis" / M6 "Use issue history and blind failure mining as proposal evidence" / M42 "Choose tasks with mechanism headroom before scaling")。
4
+ >
5
+ > 工具:`node eval/oracle-analysis.mjs`(流式解压 `~/.dsh/sessions`,零 LLM、确定性、约 90 秒跑完全量)。
6
+
7
+ ## 语料
8
+
9
+ 97 个真实会话日志(3 个 workspace 桶),13,900 次模型请求,15,020 次工具调用,65 轮 compaction,1,376 次会话中途 inbox splice。模型混合 local-vllm deepseek-v4-flash 为主,兼 glm-5.3 / qwen3.8-flash 等。
10
+
11
+ ## M1 工具结果重放暴露(ObservationPack headroom,SoL-Pi C11/C23)
12
+
13
+ 每个工具结果产生后,会在后续每个请求里重放(直到 compaction 改写历史)。exposure = Σ 结果字符 × 后续请求数:
14
+
15
+ | 口径 | 字符数 | ≈token(chars/4) |
16
+ |---|---|---|
17
+ | naive(无 compaction 截断,上界) | 7.53B | 1.88B |
18
+ | compaction 截断(下界) | 2.77B | 692M |
19
+ | 占「重放+固定开销」比重 | 57.6%(naive)– 21.2%(cut) | — |
20
+
21
+ - **top-10 结果占全会话结果字节的 46.1%(中位数)**——少数巨型输出承载了近一半的重放压力,正是 ObservationPack「归档载荷、留 handle+摘录」的靶子。
22
+ - 结果字节来源:**bash 14.2M + read 10.6M + run_code 6.1M ≈ 96%**。截断/归档机制只需覆盖这三个工具即可命中几乎全部暴露。
23
+ - 重放最重的会话(解析器开发,549–1,399 请求/会话)单会话重放高达 91–383Mc(23–96M token)——长时程会话正是 SoL-Pi 指出效率差异可测的场景。
24
+
25
+ **固定开销**(每个请求重发的 system+tools schema):全语料 436Mc ≈ 109M token,平均 ~7.8K token/请求;富预设(43 工具)单请求 schema 41.8Kc。**本插件自身 schema 仅 2.9Kc(≈730 token,占该预设 7%)——插件的开销主张成立。**
26
+
27
+ ## M2 Action-Fusion headroom(SoL-Pi "Action Fusion")
28
+
29
+ 编辑类调用(edit/write/str_replace_editor,共 1,993 次)之后**紧接着**由模型发起 bash(中间隔一次模型决策)的候选:607 次。
30
+
31
+ - 占全部工具调用 **4.0%**;占 bash 调用 **7.5%**;占步间转移 ~4.4%(SoL-Pi 在 Pi 上测得 12.3%,DSH 上 headroom 更小)。
32
+ - 结论:融合编辑+运行为单一工具调用是**上游 harness 的杠杆**,不是插件的;优先级排后。
33
+
34
+ ## M3 engram 机制激活(SoL-Pi M15 "prove mechanism activation")
35
+
36
+ 挂载插件的 66 个会话(全桶):
37
+
38
+ | 指标 | 值 |
39
+ |---|---|
40
+ | 有任何 engram/esr 工具调用 | 26/66(39.4%) |
41
+ | 显式 engram_recall | 6 会话(9.1%),共 8 次,命中 8/8 |
42
+ | engram_detail 下钻 | **0/66** |
43
+ | engram_store / esr_* | 76 / 38 次 |
44
+
45
+ **写路径激活良好,显式读路径近乎休眠**(autoRecallOnStart 的注入不走工具调用,本指标不可见——这本身就是个遥测缺口)。0 次 detail 说明渐进披露的第二层从未被用到:要么索引行已足够(设计成功,上下文成本仅 ~175 token/会话),要么模型从未想到下钻(机制休眠)。区分二者需要新增遥测:**记录每次 [ENGRAM] 注入后本会话是否发生 recall/detail,以及 recall 是否紧随索引覆盖的主题**。
46
+
47
+ ## 副产品发现
48
+
49
+ - **1,376 次会话中途 splice**(agent/inbox/spliced)是 prompt 前缀抖动(C20)的主要来源,每次都可能击穿 KV 缓存、把重放从缓存价打回全价。
50
+ - 65 轮 compaction 集中在长会话;截断口径(naive vs cut)差异即来自它们。
51
+
52
+ ## 下一个机制怎么选(按 headroom 排序)
53
+
54
+ 1. **结果首插压缩(C11/C23)**:对 bash/read/run_code 输出在进入首个 prompt 前归档截断(保头保尾、留 handle、可分页召回)。靶子占结果字节 96%,且与 SoL-Pi 已在配对 A/B 中验证的 ObservationPack 同构。注意 KV 缓存折价与 1376 次 splice 击穿,收益按 token 占用与缓存击穿双口径核算。
55
+ 2. **激活遥测(M15)**:给 /stats 增加「注入→下钻」漏斗计数,并做一次「仅索引 vs 索引+detail 工具」的 A/B,判定 detail 层是否该保留。
56
+ 3. **Action Fusion**:作为上游 DSH 提案(4–7.5% headroom),不在本插件做。
57
+
58
+ ## 口径与限制
59
+
60
+ - token 为 chars/4 估算(CJK 偏差存在,只用于量级)。
61
+ - KV 缓存未折价:重放 token 多数走缓存读价(约 1/10),但中途 splice/compaction 会击穿前缀,击穿部分按全价计——真实成本在两者之间。
62
+ - exposure 的真值介于 naive 与 cut 之间(compaction 摘要本身仍会重放,未计入)。
63
+ - 本机语料为单人三 workspace,非基准任务集;数字用于选机制,不宣称普适。