dsh-engram 0.6.0 → 0.7.1

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
@@ -264,8 +264,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
264
264
  <!-- dsh-market compatibility block — keep labels bilingual and in sync with
265
265
  docs/RELEASE-NOTES.TEMPLATE.md (market README convention) -->
266
266
 
267
- - **DSH 版本 / DSH versions**: `>=0.1.2-alpha.2 <0.3.0-0` (本版 `0.6.0`; 已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` — 见下方矩阵)
268
- - **最后验证日期 / Last verified**: 2026-10-19 — Context Ledger / 默认值纠偏 / JIT 推送,npm test 359/359 通过
267
+ - **DSH 版本 / DSH versions**: `>=0.1.2-alpha.2 <0.3.0-0` (本版 `0.7.1`; 已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` — 见下方矩阵)
268
+ - **最后验证日期 / Last verified**: 2026-10-19 — web-plane Context GC 适配 DSH `0.2.0` 声明式预设(`dsh-web-app/presets/*.patch.yml`),npm test 396/396 通过
269
269
  - **安装方式已验证 / Verified install**: `dsh plugin --profile web add dsh-engram`
270
270
 
271
271
  > **Short version.** Every **published** release (`0.2.0` … `0.3.6`) supports
@@ -281,7 +281,9 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
281
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
282
  | `0.5.0` (npm) | published | **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` |
283
283
  | `0.5.1` | published | 同 `0.5.0` — 会话扫描修复(`session.v<N>.jsonl.zstd`,issue #8)、数据导出/导入 GUI、替换/冲突可视化(issue #3) | 同 `0.5.0` |
284
- | `0.6.0` (本期 / this release) | published | 同 `0.5.0` — Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
284
+ | `0.6.0` | published | 同 `0.5.0` — Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
285
+ | `0.7.0` | published | 同 `0.5.0` — session episode、resume brief、entity pages/cards、revalidatable anchors + layered TTL | 同 `0.5.0` |
286
+ | `0.7.1` (本期 / this release) | prepared | 同 `0.5.0` — web 平面 Context GC 适配 DSH `0.2.0` 声明式预设(`dsh-web-app/presets/*.patch.yml`,14 空格缩进),状态/doctor CLI 同步 | 同 `0.5.0` |
285
287
 
286
288
  ### Where the API family broke — `0.1.2-alpha.2`
287
289
 
@@ -315,7 +317,7 @@ the loader), and the settings form is keyed by the profile entry id (`engram`),
315
317
  so nothing needs "mounting". `0.5.0` feature-detects the two generations in
316
318
  `installEngramSettings`:
317
319
 
318
- - DSH `≥0.1.7` / `0.2.x` — the 31 GUI knobs are the volatile `Config` fields
320
+ - DSH `≥0.1.7` / `0.2.x` — the 34 GUI knobs are the volatile `Config` fields
319
321
  (`lib/settings.js` `makeEngineConfigSchema`, exported as `Config` from
320
322
  `lib/index.js`); the card reads/writes entry `engram` through the same
321
323
  `/api/dsh-engram/settings` HTTP route, and the `service.settings` seam
@@ -331,27 +333,59 @@ drives mutations).
331
333
  So: keep DSH on `0.1.1-rc.2` or older while running a release ≤ `0.3.6`, and
332
334
  upgrade the plugin to `>=0.3.7` before moving DSH to `0.1.2-alpha.2`.
333
335
 
334
- ### Current release — `0.6.0`
335
-
336
- From a "memory plugin" to a **context portfolio**: make token savings auditable
337
- first, then use the ledger to correct defaults, veto unprofitable prunes and
338
- push memory at deterministic moments.
339
-
340
- - **Context Ledger** — per session / workspace / day
341
- `net = avoided − injected − rewrite penalty`; exposed on `/stats` and
342
- `/overview`, rendered on the telemetry page, persisted to
343
- `~/.dsh/engram/ledger.json`.
344
- - **Default correction** — `boundaryPrune` / `resultPack` default ON (pruning is
345
- lossless via the archive); `gcReplacesCompaction` / `autoWebCompaction` are
346
- now opt-in (no preset rewriting by default).
347
- - **Ledger veto** — `boundaryPruneAdaptive` stops pruning a workspace whose own
348
- accounting is net negative after ≥3 real fires (fail-open).
349
- - **JIT push** — error recurrence / file focus / goal completion, append-only,
350
- ≤3 lines per session, silent without a hit; `[JIT]` section + `jitEnabled`.
351
- - **Migration** — restart `dsh web` after upgrading; if you still want Context
352
- GC takeover, set `gcReplacesCompaction:true` (plus `autoWebCompaction:true`
353
- for web presets). The `0.5.1` versioned session-log fix (issue #8) and data
354
- backup GUI (issue #3) are unchanged.
336
+ ### Current release — `0.7.1`
337
+
338
+ Web-plane Context GC on DSH `0.2.0`'s **declarative presets** — the preset
339
+ format changed shape (one `@deepseek-ai/dsh-agent-preset` declaration inside
340
+ `dsh-web-app/presets/*.patch.yml`), and the auto-wiring was anchored to the old
341
+ `agent.cordis.yml` layout, so `autoWebCompaction` silently skipped every preset
342
+ (and could half-rewrite a stock row: the pattern did not include its trailing
343
+ newline).
344
+
345
+ - **Structural row location** — the `compaction` group is found by YAML
346
+ structure (a list item carrying `group: true`), not by column position; the
347
+ stock/engram rows are validated by BOTH their `- id:` and `name:` values, and
348
+ the replacement inherits the row's own indent. Legacy `agent.cordis.yml`
349
+ (4 spaces) and declarative patch files (14/16/18 spaces) both round-trip
350
+ byte-identically.
351
+ - **Declarative discovery** — the preset roster no longer carries `path` on
352
+ DSH `0.2.0`; the plugin maps each roster id to the shipped
353
+ `<presetsDir>/<id>.patch.yml` (resolved through the `dsh` install, with
354
+ `DSH_WEB_PRESETS_DIR` override) and skips user-defined declarative presets
355
+ (profile-patch overrides) with an explicit log line instead of failing.
356
+ - **Status/doctor** — `npx dsh-engram status`/`doctor` discover both preset
357
+ generations, show the detected layout, and flag `autoWebCompaction=false`
358
+ (the opt-in toggle that makes the web plane a no-op), node_modules preset
359
+ wiring (survives `npm update dsh` only while auto-wiring stays on), and
360
+ profile-patch `preset-*` overrides that outrank the shipped files.
361
+ - **Migration** — restart `dsh web` with `autoWebCompaction:true`: the next boot
362
+ re-wires every stock declarative preset (idempotent refresh; existing
363
+ hand-wired rows are rewritten to the requested engine config). `npm test`
364
+ 396/396.
365
+
366
+ ### Previous release — `0.7.0`
367
+
368
+ From event records to **maintainable current truth**: sessions sediment an
369
+ episode at teardown, new sessions get a resume brief, `esr_node` becomes a
370
+ readable page, and file anchors make stale truth detectable.
371
+
372
+ - **Session episode** — zero-LLM digest at `session/disposed` (files / commands /
373
+ failures→fixed / decisions / open loops), never indexed, newest 20 per
374
+ workspace kept.
375
+ - **Resume brief** — the newest episode from the last 7 days is injected as a
376
+ `[RESUME]` line (≤240 chars + drill pointer) at session start.
377
+ - **Entity pages + cards** — `truth / timeline / open loops / conflicts /
378
+ relations`; up to 2 one-line cards in the index, full page via
379
+ `engram_detail(entity=…)`.
380
+ - **Revalidatable anchors** — file-backed memories record `mtime/size/sha256`;
381
+ a bounded revalidation at session start tags changed/missing sources `stale`
382
+ (dropped from index/truth, still re-fetchable).
383
+ - **Layered TTL** — preference/procedure permanent, episode 14d, error 60d,
384
+ others 180d.
385
+ - **Migration** — only new writes are anchored (no historical backfill);
386
+ restart `dsh web` after upgrading; an episode appears after one session ends,
387
+ the resume brief on the next new session. The `0.6.0` ledger / defaults / JIT
388
+ behaviour is unchanged.
355
389
 
356
390
  The published `0.4.0` declared `>=0.1.2-alpha.2 <0.2.0-0` — which the semver
357
391
  gate **rejects on every DSH `0.2.0-0` prerelease** (`<0.2.0-0` excludes
@@ -372,10 +406,10 @@ feature-detected legacy fallback).
372
406
  `0.1.2-alpha.2` is the minimum version for this API family: host settings are
373
407
  **feature-detected** — on DSH `≤0.1.5` they register the `dsh-engram`
374
408
  namespace via `settings.installSection`; on `≥0.1.7` / `0.2.x` the form is the
375
- plugin's `Config` with the 31 GUI knobs marked `.volatile()` (entry id
409
+ plugin's `Config` with the 34 GUI knobs marked `.volatile()` (entry id
376
410
  `engram`). The browser settings card reads and writes through the plugin's own
377
411
  HTTP route (`/api/dsh-engram/settings`, see Route B above) on both. The full
378
- regression suite (npm test 329/329, incl. the new settings-v2 coverage for the
412
+ regression suite (npm test 396/396, incl. the new settings-v2 coverage for the
379
413
  volatile-Config model) passes against the DSH `0.2.0-rc.2` dependency set
380
414
  (cordis 4.0.4 + schemastery 3.18.4 + cosmokit, matching the global DSH CLI).
381
415
  The `.volatile()`/loader hot-commit path and the describe-side projection were
@@ -675,10 +709,15 @@ default):
675
709
  builds its `Include.Config` with no patches; verified against the harness
676
710
  source). So on boot, once the `agentPresets` service is ready, the plugin
677
711
  **auto-edits every preset that still has the stock layout** — the default
678
- preset plus the whole roster (shipped root + `~/.dsh/.agent-presets` user
679
- root: `standard`, `code`, `cordis`, and your own presets): it swaps the
680
- `compaction-basic` row inside the `compaction` group for
681
- `dsh-engram/compaction`. That auto-wiring is:
712
+ preset plus the whole roster, across BOTH preset generations: legacy
713
+ `<root>/<id>/agent.cordis.yml` compositions (DSH ≤ 0.1.x, row at 4 spaces)
714
+ and the declarative `@deepseek-ai/dsh-web-app/presets/<id>.patch.yml` files
715
+ (DSH ≥ 0.2.0, row at 14 spaces inside the `@deepseek-ai/dsh-agent-preset`
716
+ declaration; a user-defined declarative preset with no shipped file is left
717
+ alone). The row is located by YAML structure (a list item carrying
718
+ `group: true`) and validated by both its `- id:` and `name:` values, so the
719
+ swap is column-position independent: it swaps the `compaction-basic` row
720
+ inside the `compaction` group for `dsh-engram/compaction`. That auto-wiring is:
682
721
  - **off by default (opt-in)** (`autoWebCompaction: false`, toggle in the
683
722
  settings card's memory-GC section); once enabled it is idempotent — an
684
723
  already-wired preset is left alone;
@@ -688,8 +727,9 @@ default):
688
727
  - **per-preset**: only stock-layout presets are touched — a preset with a
689
728
  custom compaction group, or none at all (e.g. the shipped `minimal`
690
729
  preset), is never written, only logged;
691
- - **backed up + validated**: a create-only `agent.cordis.yml.engram.bak`
692
- holds each original next to its file, every result is re-read and verified
730
+ - **backed up + validated**: a create-only `.engram.bak`
731
+ (`agent.cordis.yml.engram.bak` or `<id>.patch.yml.engram.bak`) holds each
732
+ original next to its file, every result is re-read and verified
693
733
  after writing, and any doubt rolls that file back; a failure only warns and
694
734
  that session keeps DSH's default summarizer — the host plane is never
695
735
  affected.
@@ -699,7 +739,9 @@ default):
699
739
  changing the settings knobs re-provisions the web rows on the next boot
700
740
  instead of being silently ignored.
701
741
  - A fully equivalent manual CLI, shipped in the npm package
702
- (scans both the shipped and the user root; `--file <path>` targets one):
742
+ (scans both preset generations — legacy roots, the shipped declarative
743
+ presets, and the paths recorded at boot; `--presets-dir <dir>` overrides
744
+ declarative discovery, `--file <path>` targets one):
703
745
  ```bash
704
746
  npx dsh-engram status # stock = untouched · wired = active · custom = hands-off
705
747
  npx dsh-engram doctor # status + ranked next steps for every gap found
@@ -869,10 +911,25 @@ adds a "net context tokens" stat card. Durable rollup at
869
911
  `~/.dsh/engram/ledger.json` (atomic write, daily buckets, delta flush); the hot
870
912
  path is an integer add, zero I/O and zero model calls.
871
913
  Defaults: `ledgerEnabled: true`, `ledgerCacheDiscount: 0.1`; off = no listeners,
872
- no writes. **Honest scope**: tokens are `chars / 4` estimates, not provider
873
- billing, and no quality delta is claimed; Context GC compaction savings are not
874
- wired yet (the host does not expose before/after sizes), so the ledger only
875
- counts what it can prove.
914
+ no writes.
915
+
916
+ **Two credit legs** (v0.7.1 — before this the ledger only charged costs, so the
917
+ net was structurally negative):
918
+
919
+ 1. **L1 boundary prune** — chars removed by `toolResultPruner`, credited per
920
+ later request. Requires a host-plane `toolResultPruner`; DSH ≥ 0.2.0 web
921
+ profiles move that row into each agent preset's isolate realm, so the bundle
922
+ patch re-enables the host row — check `boundaryPrune.armed` in the status
923
+ snapshot.
924
+ 2. **Compaction (Context GC)** — every checkpoint credits `span chars −
925
+ checkpoint chars` from the engine that produced it; host plane and the web
926
+ preset realm (bridged through the `engramLedger` service) write the same
927
+ ledger.
928
+
929
+ **Honest scope**: tokens are `chars / 4` estimates, not provider billing, and no
930
+ quality delta is claimed. Until a workspace actually prunes or compacts its
931
+ "saved" leg is 0, so the net equals injected + rewrite penalty (negative is
932
+ expected) — the Telemetry tab says so inline.
876
933
 
877
934
  ### JIT push (v0.6-C)
878
935
 
@@ -898,6 +955,60 @@ and silence when there is nothing relevant to point at:
898
955
  remaining-requests ROI gate — today the 3-line budget plus the ledger are the
899
956
  guardrails.
900
957
 
958
+ ### Session episode + resume brief (v0.7-A/D)
959
+
960
+ - **episode** — at `session/disposed`, a zero-LLM digest of the event log: files
961
+ touched, commands run, failures that later recovered (`cmd×N→fixed`), written
962
+ commit subjects, and still-open todos. Written only when there is substance (a
963
+ bare `ls` session produces none); **never indexed**; the newest 20 per
964
+ workspace are kept and older ones archived.
965
+ - **resume brief** — at the first assembly of a new session, the newest episode
966
+ from the last 7 days is injected as a `[RESUME]` line (≤240 chars) plus an
967
+ `engram_detail <id>` pointer, frozen with `[ENGRAM]`/`[RECALL]`. No recent
968
+ episode → no line; returning to a project does not need a re-briefing.
969
+ - Settings: `episodeEnabled`, `resumeBriefEnabled` (both default on), plus
970
+ `episodeMaxPerWorkspace` / `resumeBriefMaxAgeDays` / `resumeBriefMaxChars`.
971
+
972
+ ### Entity pages + entity cards (v0.7-B)
973
+
974
+ `esr_node` grows from a dot on the graph into a readable **page**: current
975
+ truth, timeline, open loops, conflicts and relations for one thing
976
+ (package / repo / service / bug…).
977
+
978
+ - **Entity card (in the index, default on)** — `[ENGRAM]` renders ONE line for
979
+ each active entity (anchored memories or open tasks), at most 2:
980
+ `[ENT] dsh-engram · package · 3 mem · 1 open · truth: … #id · page: engram_detail(entity="dsh-engram")`
981
+ Those entities' flat memory lines are not repeated; other entities stay in a
982
+ single `nodes:` line.
983
+ - **Full page (on drill)** — `engram_detail(entity="<name|id>")` returns
984
+ `truth / timeline / open loops / conflicts / relations`, with memory ids and
985
+ evidence gaps. A name with anchored memories but no registered node still
986
+ gets a virtual page.
987
+ - Pure, deterministic: preference leads `truth`, superseded rows never enter
988
+ it, conflicts are de-duplicated by unordered pair (`#a vs #b`).
989
+ - Settings `entityCardsEnabled` (default on); `entityCardsMax` /
990
+ `entityCardChars` tunable.
991
+
992
+ ### Revalidatable anchors + layered TTL (v0.7-C)
993
+
994
+ "Treating the past as truth" is the strongest credibility critique against
995
+ memory plugins. v0.7 gives file-backed memories an **anchor**:
996
+
997
+ - on write, record `{path, mtimeMs, size, hash?}` (sha256 for files ≤256KB,
998
+ stat only for larger ones) in the memory row; relative paths resolve against
999
+ the workspace;
1000
+ - at session start a bounded revalidation (≤40 rows) tags changed/missing
1001
+ sources `stale`; the `[ENGRAM]` index and entity `truth` stop showing them,
1002
+ but nothing is deleted — `engram_detail` still returns them and the entity
1003
+ timeline marks `· stale`;
1004
+ - **layered TTL**: `preference` / `procedure` permanent; `episode` 14 days;
1005
+ `error` 60 days; everything else `expireDays` (180). Hard deletes still do not
1006
+ exist — GC only archives.
1007
+ - Settings: `anchorRevalidate` (default on), `episodeTtlDays`, `errorTtlDays`.
1008
+ - **Not done**: the offline consolidation job (episode abstraction/merge) —
1009
+ deterministic rules can only roll up, not abstract semantically; deferred
1010
+ until v0.8 alongside playbooks, rather than shipping a lossy pseudo-merge.
1011
+
901
1012
  ## Auto-capture policy
902
1013
 
903
1014
  Capture is deterministic and offline — it only sees tool *results*, never the
@@ -1011,6 +1122,11 @@ Defaults are token-conscious; override any key via the profile patch
1011
1122
  gcNarrative: true # scoped LLM narrative for un-provenanced turns; false = fully mechanical (zero LLM)
1012
1123
  autoWebCompaction: false # rewrite stock web presets (default off; explicit opt-in, revert with the CLI)
1013
1124
  jitEnabled: true # JIT push: error recurrence / file focus / goal completion → one memory line
1125
+ episodeEnabled: true # session episode: zero-LLM digest at teardown (never indexed)
1126
+ resumeBriefEnabled: true # resume brief: newest episode from the last 7 days at session start
1127
+ entityCardsEnabled: true # entity card: one truth/tasks/drill line per active entity in the index
1128
+ anchorRevalidate: true # re-stat filePath anchors at session start (changed/missing → stale)
1129
+ episodeTtlDays: 14 # episode TTL (preference/procedure permanent; error 60d; others expireDays)
1014
1130
  ledgerEnabled: true # Context Ledger: injected cost / prune savings / rewrite penalty → net tokens
1015
1131
  ledgerCacheDiscount: 0.1 # cached-prefix price share; rewrites pay (1−discount)
1016
1132
  engramIndexOrder: 40 # systemPrompt section order (before tools band)
@@ -1020,7 +1136,7 @@ Defaults are token-conscious; override any key via the profile patch
1020
1136
  ## Development
1021
1137
 
1022
1138
  ```sh
1023
- npm test # 359 tests: core + web API + GC + usage-observability + Context GC + ESR triggers + BoundaryPrune + preference learning + Context Ledger + JIT push (node:test)
1139
+ npm test # 381 tests: core + web API + GC + usage-observability + Context GC + ESR triggers + BoundaryPrune + preference learning + Context Ledger + JIT push (node:test)
1024
1140
  npm run eval # offline recall + structure benchmark (deterministic)
1025
1141
  npm run build:client
1026
1142
  ```
package/README.zh.md CHANGED
@@ -166,8 +166,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
166
166
 
167
167
  <!-- dsh-market 兼容信息块 —— 与 docs/RELEASE-NOTES.TEMPLATE.md 的市场 README 公约保持一致 -->
168
168
 
169
- - **DSH 版本**:`>=0.1.2-alpha.2 <0.3.0-0`(本版 `0.6.0`;已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` —— 见下文矩阵)
170
- - **最后验证日期**:2026-10-19 —— Context Ledger / 默认值纠偏 / JIT 推送,npm test 359/359 通过
169
+ - **DSH 版本**:`>=0.1.2-alpha.2 <0.3.0-0`(本版 `0.7.1`;已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` —— 见下文矩阵)
170
+ - **最后验证日期**:2026-10-19 —— web 平面 Context GC 适配 DSH `0.2.0` 声明式预设(`dsh-web-app/presets/*.patch.yml`),npm test 396/396 通过
171
171
  - **安装方式已验证**:`dsh plugin --profile web add dsh-engram`
172
172
 
173
173
  > **一句话结论**:所有**已真正发布**的版本(`0.2.0` … `0.3.6`)**只支持旧版
@@ -181,7 +181,9 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
181
181
  | `0.3.7` / `0.4.0`(npm) | 已发布 | **新版 DSH,settings `installSection`** —— `>=0.1.2-alpha.2 <0.2.0-0` | `engines.dsh` + peers 同范围 |
182
182
  | `0.5.0`(npm) | 已发布 | **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` |
183
183
  | `0.5.1` | 已发布 | 同 `0.5.0` —— 会话扫描修复(`session.v<N>.jsonl.zstd`,issue #8)、数据导出/导入 GUI、替换/冲突可视化(issue #3) | 同 `0.5.0` |
184
- | `0.6.0`(本期 / this release) | 已发布 | 同 `0.5.0` —— Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
184
+ | `0.6.0` | 已发布 | 同 `0.5.0` —— Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
185
+ | `0.7.0` | 已发布 | 同 `0.5.0` —— 会话 episode、恢复简报、实体页/实体卡、可重验证锚点 + 分层 TTL | 同 `0.5.0` |
186
+ | `0.7.1`(本期 / this release) | 待发布 | 同 `0.5.0` —— web 平面 Context GC 适配 DSH `0.2.0` 声明式预设(`dsh-web-app/presets/*.patch.yml`,14 空格缩进),status/doctor CLI 同步 | 同 `0.5.0` |
185
187
 
186
188
  ### API 族断裂点:`0.1.2-alpha.2`
187
189
 
@@ -208,7 +210,7 @@ HTTP 路由(`/api/dsh-engram/settings`,与记忆 viewer 同样的同源 fetc
208
210
  profile 条目 id(`engram`)为命名空间,不再需要任何“挂载”调用。`0.5.0`
209
211
  在 `installEngramSettings` 里对两代际做特性检测:
210
212
 
211
- - DSH `≥0.1.7` / `0.2.x` —— 31 个 GUI 旋钮即 `Config` 的 volatile 字段
213
+ - DSH `≥0.1.7` / `0.2.x` —— 34 个 GUI 旋钮即 `Config` 的 volatile 字段
212
214
  (`lib/settings.js` `makeEngineConfigSchema`,由 `lib/index.js` 导出为
213
215
  `Config`);卡片仍走同一 `/api/dsh-engram/settings` HTTP 路由读写条目 `engram`,
214
216
  `service.settings` 接缝在 `loader/volatile-update` 事件上把 `resolved`
@@ -221,23 +223,39 @@ profile 条目 id(`engram`)为命名空间,不再需要任何“挂载”
221
223
  所以:跑 ≤ `0.3.6` 的已发布版本时请把 DSH 保持在 `0.1.1-rc.2` 或更旧;升级
222
224
  DSH 到 `0.1.2-alpha.2` 之前,先升级插件到 `>=0.3.7`。
223
225
 
224
- ### 当前发布版本 —— `0.6.0`
225
-
226
- 从「记忆插件」转向**上下文投资组合**:先把省 token 变成可审计的账本,再用账本
227
- 纠正默认值、否决不划算的剪枝,并按确定性事件把记忆推给模型。
228
-
229
- - **Context Ledger**:每会话/工作区/天记录 `net = 节省 − 注入 − 重写罚金`,
230
- `/stats` 与 `/overview` 返回,GUI 遥测页有面板;落盘 `~/.dsh/engram/ledger.json`。
231
- - **默认值纠偏**:`boundaryPrune` / `resultPack` 默认开(剪枝由归档兜底),
232
- `gcReplacesCompaction` / `autoWebCompaction` 改 opt-in(不再主动改写宿主预设)。
233
- - **账本否决**:`boundaryPruneAdaptive` 在某 workspace 净收益为负且 ≥3 次剪枝时
234
- 自动停剪(fail-open)。
235
- - **JIT 推送**:错误复发 / 文件焦点 / goal 完成三类事件,append-only、每会话 ≤3 行、
236
- 无命中沉默;`[JIT]` section + `jitEnabled`。
237
- - **迁移**:升级后重启 `dsh web`;若仍要 Context GC 接管,显式打开
238
- `gcReplacesCompaction:true`(web 还需 `autoWebCompaction:true`)。
239
- - `0.5.1` 的会话日志版本化修复(`session.v<N>.jsonl.zstd`,issue #8)与数据备份
240
- GUI(issue #3)保持不变。
226
+ ### 当前发布版本 —— `0.7.1`
227
+
228
+ 修复 **DSH `0.2.0` 声明式预设下的 web 平面 Context GC 接管**:旧版自动装配以
229
+ `agent.cordis.yml` 的 4 空格布局为锚,而 `0.2.0` 把预设换成
230
+ `dsh-web-app/presets/<id>.patch.yml` 里的 `@deepseek-ai/dsh-agent-preset` 声明(行在
231
+ 14/16/18 空格缩进),导致 `autoWebCompaction` 静默跳过所有预设。
232
+
233
+ - **结构化定位 compaction 行**:找 `group: true` 的 `compaction` 组,组内按 `- id:` +
234
+ `name:` 双重校验定位 stock/engram 行,替换块继承该行自己的缩进;旧版与声明式布局都
235
+ swap → restore 逐字节还原。顺带修掉旧 stock 替换串不含行尾换行、可能半改写文件的隐患。
236
+ - **声明式预设发现**:roster 不再带 `path` 时把 id 映射到出厂 `<presetsDir>/<id>.patch.yml`
237
+ (`DSH_WEB_PRESETS_DIR` 可覆盖);无出厂文件的用户自定义声明式预设跳过并打日志。
238
+ - **status/doctor 适配**:自动发现两种布局并显示布局类型;doctor 新增
239
+ `autoWebCompaction=false`、node_modules 预设(`npm update dsh` 会恢复出厂布局)、
240
+ profile patch `preset-*` 覆写(优先于 shipped 文件)三类提示。
241
+ - **迁移**:打开 `autoWebCompaction` 后重启一次 `dsh web`,下一次启动即幂等接管所有
242
+ stock 声明式预设;`npm test` 396/396。
243
+
244
+ ### 上一发布版本 —— `0.7.0`
245
+
246
+ 把「记忆」从**事件记录**推进到**可维护的当前真相**:会话结束自动沉淀 episode、
247
+ 新会话给恢复简报、`esr_node` 升级为实体页、文件锚点让过期真相可被检测。
248
+
249
+ - **会话 episode**:`session/disposed` 零 LLM 生成紧凑纪要(文件/命令/失败→修复/
250
+ 决策/未闭环),不进索引,每 workspace 保留 20 条。
251
+ - **恢复简报**:新会话注入最近 7 天最新 episode(`[RESUME]` ≤240 字符 + 下钻指针)。
252
+ - **实体页 + 实体卡**:`truth / timeline / open loops / conflicts / relations`;
253
+ 索引前置最多 2 张实体卡,`engram_detail(entity=…)` 下钻。
254
+ - **可重验证锚点**:带 `filePath` 的记忆记录 `mtime/size/sha256`;会话启动有界
255
+ 重验证,改动/缺失 → `stale`(不进索引/truth,仍可 detail 重取)。
256
+ - **分层 TTL**:preference/procedure 永久、episode 14 天、error 60 天、其余 180 天。
257
+ - **迁移**:只有新写入会落锚(不回溯历史);升级后重启 `dsh web`;episode 要等一个
258
+ 会话结束,resume brief 在下一个新会话生效。`0.6.0` 的账本/默认值/JIT 保持不变。
241
259
 
242
260
  已发布的 `0.4.0` 声明 `>=0.1.2-alpha.2 <0.2.0-0`——这个范围被 semver 门槛在
243
261
  **所有 DSH `0.2.0-0` 预发布**上拒绝(`<0.2.0-0` 排除 `0.2.0-rc.x`),且其宿主设置端
@@ -255,13 +273,13 @@ Config** 模型(并带特性检测的旧代际回退)。
255
273
 
256
274
  `0.1.2-alpha.2` 是这组 API 的最低版本:宿主设置注册按代际**特性检测**——DSH `≤0.1.5`
257
275
  走 `settings.installSection` 注册 `dsh-engram` 命名空间;`≥0.1.7` / `0.2.x` 走插件
258
- `Config`(31 个 GUI 旋钮标记为 `.volatile()`,条目 id `engram`)。浏览器设置卡在两代际
276
+ `Config`(34 个 GUI 旋钮标记为 `.volatile()`,条目 id `engram`)。浏览器设置卡在两代际
259
277
  都经插件自有 HTTP 路由(`/api/dsh-engram/settings`,见上文 Route B)读写。完整回归
260
- (npm test 329/329,含新增的 volatile-Config 模型 settings-v2 覆盖)在 DSH
278
+ (npm test 396/396,含新增的 volatile-Config 模型 settings-v2 覆盖)在 DSH
261
279
  `0.2.0-rc.2` 依赖集(cordis 4.0.4 + schemastery 3.18.4 + cosmokit,与全局 DSH CLI 同版本)
262
280
  上通过;`.volatile()`/loader 热提交路径与 describe 侧投影已针对真实 `dsh-settings
263
281
  0.2.0-rc.2` 工具函数验证;并在隔离的 `dsh 0.2.0-rc.2` profile 上完成了**真机启动烟测并
264
- 通过**:插件启动无版本范围拒绝、设置卡表单以条目 `engram` 对外提供(全部 31 个 GUI
282
+ 通过**:插件启动无版本范围拒绝、设置卡表单以条目 `engram` 对外提供(全部 34 个 GUI
265
283
  旋钮,含 `trustedHosts`)、`/api/dsh-engram/settings` 的 GET → PUT(mutate ops,
266
284
  revision 0→1)→ unset 往返落盘并读回。旧的 `0.1.0-rc.7` 与 `0.1.1` API 图不在
267
285
  本发布版本的兼容范围内。
@@ -392,7 +410,7 @@ npm run build:client
392
410
  ## 测试与评测
393
411
 
394
412
  ```sh
395
- npm test # 359 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发 / BoundaryPrune / Context Ledger / JIT 推送)
413
+ npm test # 381 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发 / BoundaryPrune / Context Ledger / JIT 推送)
396
414
  npm run eval # 离线召回 + 结构基准(确定性语料,跑真实 store/recall 路径)
397
415
  ```
398
416
 
@@ -491,22 +509,27 @@ Context GC 的装配在**两个平面**,但两个开关都是 opt-in(默认
491
509
  - **preset 平面(web profile)——显式开启后覆盖全部 stock 预设**:web 面把 compaction 放在 agent
492
510
  preset 的 isolate realm(shipped 预设自挂 `compaction-basic`),host 条目进不去、profile patch 也
493
511
  够不到预设文件(`mountPreset` 的 Include 不带 patches——已对源码核实)。所以插件在启动时
494
- (`agentPresets` 服务就绪后)**自动改写每个仍处于出厂 stock 布局的预设**——默认预设 + 整张 roster
495
- (shipped 根 + `~/.dsh/.agent-presets` 用户根,`standard`/`code`/`cordis` 和你自己的预设都覆盖):
496
- 把 `compaction` 组里的 `compaction-basic` 行换成 `dsh-engram/compaction`。这一自动装配是:
512
+ (`agentPresets` 服务就绪后)**自动改写每个仍处于出厂 stock 布局的预设**——默认预设 + 整张 roster,
513
+ 两代预设布局都覆盖:旧版 `<root>/<id>/agent.cordis.yml` 组合文件(DSH ≤ 0.1.x,行在 4 空格)与
514
+ 声明式 `@deepseek-ai/dsh-web-app/presets/<id>.patch.yml`(DSH ≥ 0.2.0,行在
515
+ `@deepseek-ai/dsh-agent-preset` 声明里、14 空格缩进;没有出厂文件的用户自定义声明式预设不碰):
516
+ 把 `compaction` 组里的 `compaction-basic` 行换成 `dsh-engram/compaction`。行按 YAML 结构定位
517
+ (带 `group: true` 的列表项)、按 `- id:` 与 `name:` 双重校验,**与缩进列数无关**。这一自动装配是:
497
518
  - **默认关(opt-in)**(`autoWebCompaction: false`;设置卡「记忆 GC」可开),开启后幂等——已替换则跳过;
498
519
  **关掉的含义**:只是在之后的启动时不再自动接管(headless/TUI/base 的 host 平面不受影响)——
499
520
  它**不会**还原之前已接管的预设,要还原用 `npm run web-compaction:revert`;
500
521
  - **逐预设判定,只碰 stock 布局**——用户自定义过 compaction 组的预设,以及根本没有 compaction 组的
501
522
  预设(如 shipped `minimal`),一律不写、只记日志;
502
- - **带备份 + 严格校验**:每个文件写前在旁边留 `agent.cordis.yml.engram.bak`(create-only,保留首次
503
- 原件),写后重读校验,任何一步可疑即回滚该文件;改写失败只 warn,该会话仍用 DSH 默认摘要,
523
+ - **带备份 + 严格校验**:每个文件写前在旁边留 `.engram.bak`(create-only,保留首次
524
+ 原件;旧版布局为 `agent.cordis.yml.engram.bak`,声明式布局为 `<id>.patch.yml.engram.bak`),
525
+ 写后重读校验,任何一步可疑即回滚该文件;改写失败只 warn,该会话仍用 DSH 默认摘要,
504
526
  **永不影响宿主**。
505
527
  - **配置真实传导**:写进预设行的引擎配置来自设置卡/配置里的 `gcReplacesCompaction` 与
506
528
  `gcNarrative`——改了这两个开关,下次启动会自动把已接管的行刷新成新配置(幂等重写),
507
529
  所以**设置卡对 web 平面同样生效**,不再硬编码 true/true。
508
- - 手动的完全等价物(预览 / 审计 / 卸载前回归,随 npm 包发布的 CLI,默认扫 shipped + 用户两个根、
509
- 可 `--file` 指定单个):
530
+ - 手动的完全等价物(预览 / 审计 / 卸载前回归,随 npm 包发布的 CLI,自动发现两代预设布局——
531
+ 旧版根、出厂声明式预设与上次启动记录的路径;`--presets-dir <dir>` 覆盖声明式目录探测、
532
+ `--file` 指定单个):
510
533
  ```bash
511
534
  npx dsh-engram status # stock → 未动;wired → 已接管;custom → 不碰
512
535
  npx dsh-engram doctor # status + 按缺口排序的下一步建议
@@ -640,9 +663,20 @@ overview)现在返回 `activation: { boundaryPrune, recall }`:
640
663
  GUI「遥测」页签直接画这块账本(净 token 大数字 + 节省/注入/重写罚金三卡 + 近 14 天红绿柱),
641
664
  记忆概览页也多一张「上下文净 token」卡。落盘在 `~/.dsh/engram/ledger.json`(原子写、按天滚动、
642
665
  delta 落盘),进程内只做整数累加,热路径零 I/O、零模型调用。默认 `ledgerEnabled: true`、
643
- `ledgerCacheDiscount: 0.1`;关掉 = 不挂监听、不写文件。**口径诚实**:token 按
644
- `chars / 4` 估算,不含 provider 实际计费与模型质量差;Context GC 的压缩节省暂未
645
- 接入(宿主未暴露前后大小),因此账本只记「可证明」的部分。
666
+ `ledgerCacheDiscount: 0.1`;关掉 = 不挂监听、不写文件。
667
+
668
+ **节省的两条入账腿**(v0.7.1 补齐,此前只记成本所以净值恒负):
669
+
670
+ 1. **L1 边界剪枝**——`toolResultPruner` 剪掉的 chars,按后续请求数计入(需要有 host
671
+ 平面的 `toolResultPruner`;DSH ≥ 0.2.0 的 web profile 把该行移进 agent preset 的
672
+ isolate realm,本 bundle 的 `cordis.patch.yml` 会重新启用 host 行,状态快照里
673
+ `boundaryPrune.armed` 可核对);
674
+ 2. **压缩(Context GC)**——每次 checkpoint 用「被替换区间可见字符数 − checkpoint 字符数」
675
+ 入账,host 平面与 web 预设域(经 `engramLedger` 服务桥接)走同一个账本。
676
+
677
+ **口径诚实**:token 按 `chars / 4` 估算,不含 provider 实际计费与模型质量差;
678
+ 某工作区还没发生过剪枝/压缩时「节省」就是 0,净值等于注入成本 + 重写罚金(负值正常),
679
+ 遥测页会直接提示这一点。
646
680
 
647
681
  ### JIT 推送(v0.6-C)
648
682
 
@@ -664,6 +698,50 @@ v0.6 增加按事件**推送**:三个确定性事件,命中才推、每次
664
698
  - **尚未做**:resume brief(隔天继续)与「预计剩余请求不足时跳过推送」的显式 ROI 门——
665
699
  当前靠 3 行硬预算 + 账本观测。
666
700
 
701
+ ### 会话纪要 + 恢复简报(v0.7-A/D)
702
+
703
+ - **episode**:会话结束(`session/disposed`)时,用零 LLM 规则把事件日志压成一条紧凑纪要——
704
+ 改过哪些文件、跑过哪些命令、哪些失败后修复(`cmd×N→fixed`)、git commit 的书面主题、
705
+ 仍挂着的 todo。只在有实质内容时生成(纯 `ls` 会话不写);**永不进 `[ENGRAM]` 索引**;
706
+ 每个 workspace 只保留最近 20 条,超出自动归档。
707
+ 数据源全部是已有事件(`tool/call` 的 arguments、`tool/result`、`todo/write`),不调模型。
708
+ - **恢复简报**:新会话第一次组装时,取最近 7 天最新的 episode,注入 ≤240 字符的
709
+ `[RESUME]` 行 + `engram_detail <id>` 指针;与 `[ENGRAM]`/`[RECALL]` 一起按会话冻结。
710
+ 没有近期 episode 就不注入——隔天继续项目时不用重新交代背景。
711
+ - 设置键:`episodeEnabled`、`resumeBriefEnabled`(均默认开),
712
+ `episodeMaxPerWorkspace` / `resumeBriefMaxAgeDays` / `resumeBriefMaxChars` 可调。
713
+
714
+ ### 实体页 + 实体卡(v0.7-B)
715
+
716
+ `esr_node` 从「图上的点」升级成**可读的页**:对一个实体(package / repo / 服务 / bug…)
717
+ 聚合当前真相、时间线、未闭环任务、冲突与关系。
718
+
719
+ - **实体卡(索引内,默认开)**:`[ENGRAM]` 为活跃实体(有锚定记忆或未闭环任务)
720
+ 渲染一行,最多 2 张:
721
+ `[ENT] dsh-engram · package · 3 mem · 1 open · truth: … #id · page: engram_detail(entity="dsh-engram")`
722
+ 这些实体的平铺记忆行不再重复(真相由卡片承载);其余实体仍列在 `nodes:` 一行。
723
+ - **完整页(按需下钻)**:`engram_detail(entity="<name|id>")` 返回
724
+ `truth / timeline / open loops / conflicts / relations` 五段,带 memory id 与证据缺口;
725
+ 没有注册节点但有记忆锚定的名字也会生成虚拟页。
726
+ - 纯函数、确定性排序:preference 排在 truth 最前,superseded 旧真相不进 truth,
727
+ 冲突按无序对去重(`#a vs #b`)。
728
+ - 设置键 `entityCardsEnabled`(默认开);`entityCardsMax` / `entityCardChars` 可调。
729
+
730
+ ### 可重验证锚点 + 分层 TTL(v0.7-C)
731
+
732
+ 「把过去当真相」是记忆插件最大的可信度问题。v0.7 给带 `filePath` 的记忆加**锚点**:
733
+
734
+ - 写入时记录 `{path, mtimeMs, size, hash?}`(≤256KB 的文件存 sha256,大文件只存 stat),
735
+ 落盘在记忆行的 `anchor` 字段;相对路径按 workspace 解析为绝对路径。
736
+ - 会话启动时做一次**有界重验证**(最多 40 条):文件缺失/改动 → 记忆打 `stale` 标签;
737
+ `[ENGRAM]` 索引与实体页 `truth` **不再自动展示**它,但**不删除**——`engram_detail`
738
+ 仍可重取,实体页 timeline 会标 `· stale`。
739
+ - **分层 TTL**:`preference` / `procedure` 永久;`episode` 默认 14 天;`error` 默认 60 天;
740
+ 其余按 `expireDays`(默认 180 天)。真正删除仍只发生在 GC 的归档(不硬删)。
741
+ - 设置键:`anchorRevalidate`(默认开)、`episodeTtlDays`、`errorTtlDays`。
742
+ - **未做**:离线「巩固任务」(episode 抽象/合并)——确定性规则只能做 rollup,不是语义抽象;
743
+ 在没有证据前不引入,留到 v0.8 与 playbook 一起考虑。
744
+
667
745
  ## 自动捕获策略
668
746
 
669
747
  捕获是确定性、离线的——只看到工具*结果*,从不看对话本身。什么会被记录成一条记忆:
@@ -763,6 +841,11 @@ promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-pr
763
841
  gcReplacesCompaction: false # Context GC:接管 DSH 自动 compact(默认关=宿主 LLM 压缩;true=opt-in 指针摘要)
764
842
  gcNarrative: true # 无锚轮次走 scoped LLM 叙事;false=纯机械(零 LLM)
765
843
  jitEnabled: true # JIT 推送:错误复发/文件焦点/goal 完成时推一行已有记忆
844
+ episodeEnabled: true # 会话纪要:结束时零 LLM 生成 episode(不进索引,供恢复简报)
845
+ resumeBriefEnabled: true # 恢复简报:新会话注入最近 7 天最新 episode
846
+ entityCardsEnabled: true # 实体卡:[ENGRAM] 为活跃实体渲染一行真相 + 任务 + 下钻指针
847
+ anchorRevalidate: true # 会话启动重验证 filePath 锚点(改动/缺失 → stale,不进索引/truth)
848
+ episodeTtlDays: 14 # episode TTL(preference/procedure 永久,error 60 天,其余 expireDays)
766
849
  ledgerEnabled: true # Context Ledger:注入成本/剪枝节省/前缀重写罚金 → 净 token
767
850
  ledgerCacheDiscount: 0.1 # 缓存折价:重写按 (1−0.1) 计罚金;0.1 = 命中只付 10%
768
851
  autoWebCompaction: false # web 预设自动改写(默认关;开启需显式确认,可用 revert 还原)
@@ -774,7 +857,7 @@ promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-pr
774
857
  ## 开发
775
858
 
776
859
  ```sh
777
- npm test # 359 个测试:核心 + Web API + GC + Context GC + ESR 触发 + Context Ledger + JIT(node:test)
860
+ npm test # 381 个测试:核心 + Web API + GC + Context GC + ESR 触发 + Context Ledger + JIT(node:test)
778
861
  npm run build:client
779
862
  ```
780
863
 
package/cordis.patch.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-engram bundle patch: inserts the host plugin row into the web profile
2
2
  # roster. Activate with:
3
- # dsh plugin --profile web add file:/d1/chuxiong/code/dsh-engram
3
+ # dsh plugin --profile web add file:/Users/sunchuxiong/dsh-loom
4
4
  #
5
5
  # The row is a bare host-half plugin by package name. It does not need to
6
6
  # enable storage-domain / storage-json / session-query-sqlite — the standard
@@ -15,14 +15,27 @@
15
15
  # NOTE: with `gcReplacesCompaction:false`, dsh-engram still mounts a plain
16
16
  # BasicCompactionEngine, so compaction never goes away while it is installed.
17
17
  # The host-plane provider is mounted by `lib/index.js` (`mountCompactionEngine`);
18
- # the WEB plane is auto-wired at boot (`autoWebCompaction`, default on): the
19
- # plugin swaps the `compaction-basic` row for `dsh-engram/compaction` in every
20
- # stock-layout preset — default + full roster (shipped + user roots; idempotent,
21
- # backed up, never touches a custom or no-compaction preset; manual CLI
18
+ # the WEB plane is auto-wired at boot when `autoWebCompaction` is on (opt-in
19
+ # since v0.6, default off): the plugin swaps the `compaction-basic` row for
20
+ # `dsh-engram/compaction` in every stock-layout preset — default + full roster
21
+ # (shipped `agent.cordis.yml` roots and DSH ≥ 0.2.0 declarative
22
+ # `dsh-web-app/presets/*.patch.yml`; idempotent, backed up, never touches a
23
+ # custom or no-compaction preset; manual CLI
22
24
  # `npm run web-compaction:status|enable|revert`).
23
25
  - id: compaction-basic
24
26
  disabled: true
25
27
 
28
+ # BoundaryPrune (ResultPack L1) needs DSH's `toolResultPruner` service on the
29
+ # HOST plane. DSH ≥ 0.2.0 web profiles move compaction + the pruner into each
30
+ # agent preset's isolate realm and disable the host-plane `tool-result-pruner`
31
+ # row, so the host plugin's L1 found no service and silently went dormant (the
32
+ # Context Ledger then never credited pruning savings). Re-arm the host row here
33
+ # — the preset realm keeps its own copy for compaction-time pruning, so both
34
+ # paths coexist. Without this, `boundaryPrune` only works on headless/TUI/base
35
+ # profiles. Verify via the status snapshot's `boundaryPrune.armed`.
36
+ - id: tool-result-pruner
37
+ disabled: false
38
+
26
39
  - insert:
27
40
  - id: engram
28
41
  name: dsh-engram