dsh-engram 0.3.4 → 0.3.5

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
@@ -2,6 +2,8 @@
2
2
 
3
3
  > **[English](README.md) · [中文](README.zh.md)**
4
4
 
5
+ [![CI](https://github.com/skepsun/dsh-engram/actions/workflows/ci.yml/badge.svg)](https://github.com/skepsun/dsh-engram/actions/workflows/ci.yml)
6
+
5
7
  Minimalist long-term memory for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness), distilled from the
6
8
  [symbolic-index](https://github.com/skepsun/symbolic-index) and [pi-esr](https://github.com/skepsun/pi-esr) ideas —
7
9
  with one goal: **save tokens**.
@@ -11,6 +13,16 @@ with one goal: **save tokens**.
11
13
  key files, repeated errors), plus an explicit `engram_store`. Nothing on the hot
12
14
  path calls a model, and pure plumbing — `git push` / `git stash` / silent
13
15
  commits — is deliberately never recorded (see *Auto-capture policy* below).
16
+ Every write is run through a deterministic **secret redactor** first — API
17
+ keys, JWTs, `Bearer` tokens, private keys, AWS/Stripe/Slack/GitHub tokens and
18
+ `key=value` secret shapes are replaced with `<REDACTED:…>` markers before the
19
+ dedup hash, the char cap, and storage, so sensitive text can never land on
20
+ disk. Failing **test runs** are recognized by pure patterns (`npm test`,
21
+ `node --test`, vitest/pytest/jest, failure lines) and captured as
22
+ `tags:["error","test"]` entries with a raised signal. DSH **goal-domain
23
+ integration**: a completed or blocked goal (`goal/change` session event)
24
+ auto-sediments as a `handoff`/`error` memory (`tag:goal`), so the outcome of
25
+ a goal survives its session (see the `GET /api/dsh-engram/goals` read surface).
14
26
  - **Symbolic index + progressive disclosure** — a compact `[ENGRAM]` block (default
15
27
  budget 700 chars ≈ 175 tokens; one line per memory) is injected at prompt
16
28
  assembly and **frozen per session**, keeping the request prefix byte-stable for
@@ -23,7 +35,25 @@ with one goal: **save tokens**.
23
35
  (repeat-failure revival: recency + hit climb toward promoteHits so the
24
36
  failure resurfaces instead of piling up), and on zero local hits falls back
25
37
  to DSH's own cross-session full-text index (`ctx.sessionQuery`, filtered by
26
- cwd) instead of building a parallel SQLite index.
38
+ cwd) instead of building a parallel SQLite index; when the FTS fallback also
39
+ comes back empty and the query contains **CJK** characters (Chinese FTS5
40
+ tokenization), recall runs a bounded substring scan over the newest session
41
+ logs (zstd-decompressed, LRU-cached) and appends matched past sessions —
42
+ deterministic `# past sessions` lines, capped files and bytes.
43
+ - **Memory-to-memory semantics** — `engram_store` accepts optional
44
+ `supersedes` / `contradicts` memory ids (validated against the same
45
+ workspace). A superseded ("stale truth") memory is demoted to the tail of
46
+ recall and **excluded from the `[ENGRAM]` block**, while the superseding
47
+ statement keeps the line; a contradicted memory stays ranked but is flagged
48
+ `· contradicted by <id>`. Old rows are never deleted — re-fetchable, just
49
+ honestly ranked. Opt-in `autoSupersede` config (default **off**) auto-marks
50
+ entity-anchored replacement updates ("改用 / no longer / switched…") as
51
+ superseding their matching older row; explicit `supersedes` always wins.
52
+ - **Failure→fix closure (zero LLM)** — error memories carry a `cmd:` signature
53
+ tag; when that same command later succeeds, engram auto-sediments a
54
+ `procedure` memory (`fixed: <cmd> — N earlier failing runs now succeed`),
55
+ tags the earlier error rows `resolved` (never re-ranking them), and recall
56
+ then surfaces the fix instead of the stale failure.
27
57
  - **ESR-lite closure protocol** — `esr_task` / `esr_close` / `esr_link` give tasks
28
58
  a `draft → active → stable` lifecycle where `stable` requires real evidence
29
59
  (`artifact` / `evaluation` / `memory_ref`), surfacing closure gaps instead of
@@ -56,6 +86,18 @@ with one goal: **save tokens**.
56
86
  The `tools/result` recorder feeds the actionables' data sources; P4
57
87
  conversion metering stays (`#suggest-*` tags + 10-minute attribution +
58
88
  `GET /api/dsh-engram/triggerstats`). Pure rules, zero LLM.
89
+ - **Session-end todo auto-sink (draft safety net, `autoSinkTodosOnEnd` default
90
+ on)** — during a session the native todo stays cheap, per-session working
91
+ memory that dies with the session. But when a session closes at its teardown
92
+ edge (`session/disposed`) while its plan still has pending todos, they are
93
+ **auto-landed as ESR `draft` tasks** (name = the todo text, description
94
+ marked 「源自会话计划」, deduped against existing task names, capped by
95
+ `maxTasksPerWorkspace`), so the plan never silently evaporates. Sinking to
96
+ **draft** — not active — keeps them out of the `[ESR]` active rows and creates
97
+ no evidence obligation; an explicit `esr_claim` / `esr_task` step is still
98
+ what promotes a draft to active. The "nothing auto-written during the
99
+ session" trade-off is unchanged; only the end-of-session data-loss gap is
100
+ closed. Disable via profile patch or the settings card.
59
101
  - **Memory GC (pi-esr constraints)** — a scheduled, mechanical, archive-only
60
102
  sweep: TTL-expired memories are archived, over-cap workspaces evict the
61
103
  lowest-value entries, stable tasks past their retention window leave the
@@ -275,15 +317,20 @@ npm run build:client
275
317
 
276
318
  | Tool | Purpose | Kind |
277
319
  |---|---|---|
278
- | `engram_store` | Explicitly store one memory (kind, tags, optional entity anchor) | write |
320
+ | `engram_store` | Explicitly store one memory (kind, tags, optional entity anchor, optional supersedes/contradicts memory ids) | write |
279
321
  | `engram_recall` | Deterministic keyword recall over workspace memories; optional `search_sessions` FTS over past sessions | read |
280
322
  | `engram_detail` | Full record of one memory id (provenance, tags, hits) | read |
281
323
  | `esr_task` | Create a task entity (draft → active) | write |
282
324
  | `esr_close` | Close a task via the evidence protocol (artifact + evaluation + memory_ref) | write |
283
325
  | `esr_link` | Add a typed relation between two entities (mini graph) | write |
326
+ | `esr_dep` | Add a dependency edge (blocks / relates-to / parent-of) between tasks | write |
327
+ | `esr_claim` | Atomically claim a task (assignee + claimedAt, draft → active) | write |
328
+ | `esr_unclaim` | Release a claimed task's assignee | write |
284
329
  | `esr_ready` | List claimable tasks (no open blocker, unclaimed) | read |
285
330
  | `esr_status` | Pull live ESR state + derived hints (`since_revision` short "unchanged" response) | read |
331
+ | `esr_node` | Create/update an entity node (stable symbol) | write |
286
332
  | `esr_gc` | Run the memory GC for the workspace (`dry_run:true` previews) | write |
333
+ | `esr_model` | Precomputed mental model of the workspace (`brief`/`full`, `max_chars`) | read |
287
334
 
288
335
  The `[ESR]` block is a frozen per-session snapshot and never auto-refreshes —
289
336
  call `esr_status` for the live truth.
@@ -331,24 +378,74 @@ Triggers follow DSH's own compaction timing (step pressure / context-overflow /
331
378
  `/compact`); the lock, replay validation, tool-call/result pairing and token
332
379
  pricing are all reused from the basic engine. Any error falls back to the
333
380
  default compressor — compaction is never broken; with `gcReplacesCompaction:
334
- false` (or a host without `dsh-compaction-basic`) the engine never mounts.
335
- Mounting registers the `compaction` service on the plugin's fiber, so
336
- unloading/reloading engram restores the default engine.
337
-
338
- **Mounting semantics (verified against cordis source)**: cordis refuses a second
339
- provider of the same service in the same realm (`provide()` throws
340
- `service "compaction" has been registered` — not "last write wins"). Hence:
341
-
342
- - **Host plane**: the plugin patch disables the base `compaction-basic` row
343
- (re-armed automatically when engram is uninstalled); dsh-engram is then the
344
- sole `compaction` provider, mounting a bare `BasicCompactionEngine` under
345
- `gcReplacesCompaction:false` so the service never disappears. Headless / TUI /
346
- base-style profiles work out of the box.
347
- - **Preset plane (web profile)**: web keeps compaction inside each agent
348
- preset's isolated realm (the shipped `standard` preset mounts its own
349
- `compaction-basic`), unreachable from a host-plane row. Swap that row for the
350
- mountable entry dsh-engram ships (`dsh-engram/compaction`, package subpath
351
- export) to activate per session:
381
+ false` (or a host without `dsh-compaction-basic`) a bare `BasicCompactionEngine`
382
+ is mounted instead, so the `compaction` service never disappears while
383
+ dsh-engram is installed. Mounting registers the `compaction` service on the
384
+ plugin's fiber, so unloading/reloading engram restores the default engine.
385
+
386
+ > **Shrink gate**: the harness refuses any checkpoint that is not strictly
387
+ > smaller than the evicted span (`summary is not smaller than the shadowed
388
+ > content`) and rolls the compaction back — otherwise the context never shrinks
389
+ > and the session eventually hits the model-side overflow. Context GC
390
+ > self-predicts that gate: it mirrors the harness framing with the host
391
+ > `tokenMeter` (verified token-for-token identical) and truncates the
392
+ > pointer/narrative body to the evicted span's token budget (the pointer head
393
+ > and working set survive; the trimmed tail stays recoverable in the session
394
+ > log), so compactions actually commit. Full from-scratch usage:
395
+ > [`docs/CONTEXT-GC-GUIDE.zh.md`](docs/CONTEXT-GC-GUIDE.zh.md).
396
+
397
+ #### Where it is enabled (the entry point)
398
+
399
+ Assembly happens on **two planes** — and web is now fully automatic, zero config:
400
+
401
+ - **Host plane (headless / TUI / base profiles) — on by default, zero config**:
402
+ the main plugin (`lib/index.js`) mounts the engine inside `ctx.effect` via
403
+ `mountCompactionEngine(ctx, resolved, { readWorkspace }) → new Engine()`,
404
+ registering the `compaction` service on its own fiber; the plugin patch
405
+ disables the base `compaction-basic` row, making engram the sole host-plane
406
+ provider. `gcReplacesCompaction: true` (default) → `ContextGcEngine`;
407
+ `false` → a bare `BasicCompactionEngine`. Tune via profile patch
408
+ (`config: { gcReplacesCompaction: false }`) or the settings card's
409
+ `gcNarrative` knob (narrative off = fully mechanical).
410
+ - **Preset plane (web profile) — fully automatic, zero config, covers every
411
+ preset**: web keeps compaction inside each agent preset's isolated realm
412
+ (the shipped presets mount their own `compaction-basic`), unreachable from a
413
+ host-plane row — and unreachable from a profile patch too (`mountPreset`
414
+ builds its `Include.Config` with no patches; verified against the harness
415
+ source). So on boot, once the `agentPresets` service is ready, the plugin
416
+ **auto-edits every preset that still has the stock layout** — the default
417
+ preset plus the whole roster (shipped root + `~/.dsh/.agent-presets` user
418
+ root: `standard`, `code`, `cordis`, and your own presets): it swaps the
419
+ `compaction-basic` row inside the `compaction` group for
420
+ `dsh-engram/compaction`. That auto-wiring is:
421
+ - **on by default** (`autoWebCompaction: true`, toggle in the settings card's
422
+ memory-GC section), idempotent — an already-wired preset is left alone;
423
+ **what "off" means**: the plugin stops auto-wiring on later boots (the
424
+ headless/TUI/base host plane is unaffected) — it does NOT un-wire presets
425
+ that were already wired; use `npm run web-compaction:revert` for that;
426
+ - **per-preset**: only stock-layout presets are touched — a preset with a
427
+ custom compaction group, or none at all (e.g. the shipped `minimal`
428
+ preset), is never written, only logged;
429
+ - **backed up + validated**: a create-only `agent.cordis.yml.engram.bak`
430
+ holds each original next to its file, every result is re-read and verified
431
+ after writing, and any doubt rolls that file back; a failure only warns and
432
+ that session keeps DSH's default summarizer — the host plane is never
433
+ affected.
434
+ - **config actually propagates**: the engine config written into each row
435
+ comes from the live settings (`gcReplacesCompaction` / `gcNarrative`) — with
436
+ "already wired but with a different config" treated as a refresh, so
437
+ changing the settings knobs re-provisions the web rows on the next boot
438
+ instead of being silently ignored.
439
+ - A fully equivalent manual CLI, shipped in the npm package
440
+ (scans both the shipped and the user root; `--file <path>` targets one):
441
+ ```bash
442
+ npx dsh-engram status # stock = untouched · wired = active · custom = hands-off
443
+ npx dsh-engram doctor # status + ranked next steps for every gap found
444
+ npx dsh-engram enable # same as the boot-time auto-wiring (usually a no-op)
445
+ npx dsh-engram revert # restore the stock row everywhere — run BEFORE uninstalling
446
+ ```
447
+ (in the repo, `npm run web-compaction:*` is an alias of the same CLI.)
448
+ Either way the `compaction` group becomes:
352
449
  ```yaml
353
450
  - id: compaction
354
451
  name: cordis:group
@@ -360,14 +457,51 @@ provider of the same service in the same realm (`provide()` throws
360
457
  - id: engram-compaction # replaces the original compaction-basic
361
458
  name: dsh-engram/compaction
362
459
  config:
363
- gcReplacesCompaction: true # optional; false = default LLM summary for this session
364
- gcNarrative: true
460
+ gcReplacesCompaction: true # from settings; false = default LLM summary for this session
461
+ gcNarrative: true # from settings; false = fully mechanical
365
462
  - id: command-compact
366
463
  name: '@deepseek-ai/dsh-command-compact'
367
464
  - id: tool-result-pruner
368
465
  name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
369
466
  # keep the original config
370
467
  ```
468
+ ⚠️ **Uninstall = revert first**: once a preset references
469
+ `dsh-engram/compaction`, removing dsh-engram leaves every such row dangling
470
+ and web sessions hang on load — so run `npx dsh-engram revert`
471
+ (restores every preset) BEFORE uninstalling. A harness upgrade that ships a
472
+ fresh standard preset overwrites the swap and self-heals the dangling
473
+ reference.
474
+
475
+ #### First impressions after an npm install (perceive / guide / configure)
476
+
477
+ - **Automatic**: install into a profile and boot once — the host plane is taken
478
+ over by `mountCompactionEngine`, and the web plane by `autoWebCompaction`
479
+ (default on) rewriting every stock preset at boot. **Zero manual setup.**
480
+ - **Perceivable**: at boot the plugin writes an authoritative snapshot to
481
+ `$DSH_HOME/engram/context-gc.status.json` (`host` plane mode + the per-preset
482
+ `web` results + effective config). At any time run `npx dsh-engram status` or
483
+ `npx dsh-engram doctor`; the web memory board (ESR kanban) also shows a
484
+ header chip ("Context GC·主机 ·N 预设") fed by the overview API. Two startup
485
+ log lines confirm it too: `engram context-gc: compaction = Context GC …` and
486
+ `engram web-provision: wired Context GC into preset …`.
487
+ - **Configurable**: one settings card (memory-GC section) governs everything:
488
+ `autoWebCompaction` (web auto-wiring on/off), `gcReplacesCompaction`
489
+ (Context GC vs default summarizer), `gcNarrative` (narrative safety net).
490
+ Changes take effect after restarting `dsh web`, and the web rows are
491
+ refreshed to match automatically.
492
+
493
+ **Verify it took**: after restarting `dsh web` (or the profile) —
494
+ - host plane (headless/TUI/base): the startup log shows
495
+ `engram context-gc: compaction = Context GC (mechanical eviction + re-fetch pointers)`;
496
+ - web plane: the log shows one line per wired preset —
497
+ `engram web-provision: wired Context GC into preset "standard" (…); restart dsh web so sessions pick it up`,
498
+ or `npx dsh-engram status` reports `wired` for each; sessions composed
499
+ from ANY preset then compact via mechanical eviction + re-fetch pointers.
500
+ A line like `… keeping the existing compaction service` means another provider
501
+ already owns `compaction` in that realm (the swap didn't take); `web-provision … skipped (custom)`
502
+ means that preset was customized (or has no compaction group) and was left untouched;
503
+ `dsh-compaction-basic unavailable` means the backend is missing and everything
504
+ falls back to the default compressor.
371
505
 
372
506
  ## Auto-capture policy
373
507
 
package/README.zh.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  > **[中文](README.zh.md) · [English](README.md) · [已交付 UI 控件清单](FEATURES.zh.md)**
4
4
 
5
+ [![CI](https://github.com/skepsun/dsh-engram/actions/workflows/ci.yml/badge.svg)](https://github.com/skepsun/dsh-engram/actions/workflows/ci.yml)
6
+
5
7
  为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 设计的极简长期记忆插件,融合了
6
8
  [symbolic-index](https://github.com/skepsun/symbolic-index) 与 [pi-esr](https://github.com/skepsun/pi-esr) 的思想——
7
9
  目标只有一个:**省 token**。
@@ -9,6 +11,12 @@
9
11
  - **零 LLM 摄入** — 纯模式匹配从工具结果自动捕获有意义的事件(带书面 `-m` 提交信息的 git 里程碑、
10
12
  关键文件编辑、反复出现的错误),另有显式 `engram_store`。热路径上没有任何模型调用,纯粹的操作
11
13
  ——`git push` / `git stash` / 无提交信息的 commit——刻意**从不记录**(见下方「自动捕获策略」)。
14
+ 每条写入先过一道确定性**密钥脱敏器**——API key、JWT、`Bearer` token、私钥、AWS/Stripe/Slack/GitHub
15
+ token 与 `key=value` 形密钥一律替换为 `<REDACTED:…>` 标记,且在去重哈希 / 字符上限 / 落盘之前完成,
16
+ 敏感内容永远进不了磁盘。**测试失败**用纯模式识别(`npm test` / `node --test` / vitest/pytest/jest
17
+ + 失败行)自动捕获为 `tags:["error","test"]`、信号上调。DSH **goal 域打通**:completed/blocked 的
18
+ goal(`goal/change` 会话事件)自动沉淀为 `handoff`/`error` 记忆(`tag:goal`),目标结局不随会话消散
19
+ (读取面见 `GET /api/dsh-engram/goals`)。
12
20
  - **符号索引 + 渐进披露** — 一个紧凑的 `[ENGRAM]` 块(默认预算 700 字符 ≈ 175 token;每条记忆一行)在
13
21
  组装提示词时注入,并**按会话冻结**,让请求前缀字节稳定以复用 KV 缓存。模型需要细节时用
14
22
  `engram_recall` / `engram_detail` 下钻,而不是把命中的原文灌进上下文。召回对内存池做
@@ -16,7 +24,18 @@
16
24
  命中实体锚定的记忆时附带**实体邻域关系简表**(复用 esr_link:`node --rel--> node · conf%`);
17
25
  与历史失败**高度同源**的新错误会**唤醒旧 error 记忆**(刷新 recency + 命中,向 promoteHits
18
26
  爬升直至重回索引,失败不重复堆积);本地零命中时自动兜底到 **DSH 自带的跨会话全文索引**
19
- (`ctx.sessionQuery`,按 cwd 过滤)——不另建 SQLite,完全复用宿主。
27
+ (`ctx.sessionQuery`,按 cwd 过滤)——不另建 SQLite,完全复用宿主。当 FTS 兜底也返回空、
28
+ 且查询含 **CJK 中文**(FTS5 中文分词整段只算一个 token)时,召回会对最近若干会话日志做
29
+ 有界的**子串扫描**(zstd 解压 + LRU 缓存,限文件数与字节),追加命中会话的确定性
30
+ `# past sessions` 行。
31
+ - **记忆间语义(supersede/contradict)** — `engram_store` 可选收 `supersedes` / `contradicts`
32
+ 记忆 id(同工作区校验)。被 supersede 的「过期真相」在召回里**降级到尾部**、并从 `[ENGRAM]`
33
+ 块中**剔除**(新陈述占行);被 contradicts 的记忆保留排序但标注 `· contradicted by <id>`。
34
+ 旧行永不删除——可重取,只是排得诚实。可选 `autoSupersede` 配置(默认**关**)对"实体锚定 +
35
+ 替换式更新(改用/不再/no longer/switched…)"自动打 supersedes;显式 `supersedes` 永远优先。
36
+ - **失败→解法闭环(零 LLM)** — error 记忆带 `cmd:` 签名标签;同命令随后跑通时自动沉淀
37
+ `procedure` 记忆(`fixed: <cmd> — N earlier failing runs now succeed`),并把旧 error
38
+ 行打上 `resolved`(不影响其排序),召回因此浮出解法而不是过期失败。
20
39
  - **ESR-lite 证据闭环** — `esr_task` / `esr_close` / `esr_link` 给任务一个 `draft → active → stable`
21
40
  生命周期,其中 `stable` 必须要有真实证据(`artifact` / `evaluation` / `memory_ref`),把"缺什么"
22
41
  摊在明面上,而不是让 agent 没有证据就宣布完成。可开 `verifyArtifact`(默认开):非 URL 的
@@ -36,6 +55,13 @@
36
55
  **拉取**:`esr_status`(全量视图 + `since_revision` 增量短响应 + 派生 actionables)。
37
56
  recorder 由 `tools/result` 订阅喂入(actionables 的数据源);P4 转化度量照常(`#suggest-*` 标记 +
38
57
  10 分钟归因 + `GET /api/dsh-engram/triggerstats`)。纯规则、零 LLM。
58
+ - **会话结束 todo 自动沉淀(draft 兜底,`autoSinkTodosOnEnd` 默认开)** — 会话内 todo 仍是
59
+ 轻量、随会话而逝的"工作记忆";但当会话在其**拆解边界**(`session/disposed`)关闭时仍挂着 pending
60
+ todo,这些待办会**自动转为该工作区的 ESR draft 任务**(名称=todo 原文、描述「源自会话计划」、
61
+ 按已存在任务名去重、受 `maxTasksPerWorkspace` 上限约束),计划从此不静默蒸发。落到 **draft**
62
+ 而非 active:不占 `[ESR]` 的 active 行、不产生证据义务,仍需刻意的 `esr_claim` / `esr_task`
63
+ 推进才变 active——「进行中默认不写」的取舍不变,只把「收尾丢数据」的缺口补上。可关(profile
64
+ patch 或设置卡)。
39
65
  - **记忆 GC(pi-esr 约束)** — 定时、机械、只归档的回收:TTL 过期记忆归档、超容量工作区淘汰低价值条目、
40
66
  stable 任务超保留窗离开 `[ESR]` 表面、悬空链接边清理。工作集(active 任务引用 / 任务记忆 / 已入索引命中)
41
67
  永不触碰;**不硬删任何东西**——归档条目保留 id、始终可重取。
@@ -198,15 +224,20 @@ npm run eval # 离线召回 + 结构基准(确定性语料,跑真实 store/
198
224
 
199
225
  | 工具 | 用途 | 类型 |
200
226
  |---|---|---|
201
- | `engram_store` | 显式存入一条记忆(kind、tags、可选实体锚点) | 写 |
227
+ | `engram_store` | 显式存入一条记忆(kind、tags、可选实体锚点、可选 supersedes/contradicts 记忆 id) | 写 |
202
228
  | `engram_recall` | 工作区记忆的确定性关键词召回;可选 `search_sessions` 跨会话 FTS | 读 |
203
229
  | `engram_detail` | 一条记忆 id 的完整记录(来源、标签、命中数) | 读 |
204
230
  | `esr_task` | 创建任务实体(draft → active) | 写 |
205
231
  | `esr_close` | 按证据协议关闭任务(artifact + evaluation + memory_ref) | 写 |
206
232
  | `esr_link` | 在两个实体之间添加类型化关系(迷你图) | 写 |
233
+ | `esr_dep` | 在任务间添加依赖边(blocks / relates-to / parent-of) | 写 |
234
+ | `esr_claim` | 原子认领任务(assignee + claimedAt,draft → active) | 写 |
235
+ | `esr_unclaim` | 释放已认领任务的 assignee | 写 |
207
236
  | `esr_ready` | 列出可认领任务(无 blocker、无人认领) | 读 |
208
237
  | `esr_status` | 拉取实时 ESR 状态 + 派生提示(`since_revision` 增量短响应) | 读 |
238
+ | `esr_node` | 创建/更新实体节点(稳定符号) | 写 |
209
239
  | `esr_gc` | 运行本工作区的记忆 GC(`dry_run:true` 预览不落库) | 写 |
240
+ | `esr_model` | 工作区预计算心智模型(`brief`/`full`,`max_chars`) | 读 |
210
241
 
211
242
  `[ESR]` 块是每会话冻结快照,绝不中途刷新——要最新状态调 `esr_status`。
212
243
 
@@ -243,15 +274,53 @@ BasicCompactionEngine` 只重写唯一的 `summarize()` 钩子,把摘要正文
243
274
  tool-call/result 配对、token 定价全部复用基本引擎。任何错误 → 回退默认压缩,**永不破坏 compact**。
244
275
  装配即注册 `compaction` 服务,卸载/reload engram 自动还回默认引擎。
245
276
 
246
- **装配语义(已按 cordis 源码验证)**:cordis 拒绝同域第二个同名 provider(`provide()` 抛
247
- `service "compaction" has been registered`,不是「后注册 wins」)。因此:
248
-
249
- - **host 平面**:插件 patch 直接 `disable` 基座 `compaction-basic` 行(卸载 engram 自动还原),由
250
- dsh-engram 独占 `compaction`;`gcReplacesCompaction:false` 时仍挂裸 `BasicCompactionEngine`,
251
- 服务永不缺席。headless / TUI / base 型 profile 开箱即用。
252
- - **preset 平面(web profile)**:web 面把 compaction 放在 agent preset 的 isolate realm(`standard`
253
- 预设自挂 `compaction-basic`),host 条目进不去。把 preset `compaction` 组里的 `compaction-basic`
254
- 行换成 dsh-engram 提供的可挂载条目即可按 session 生效:
277
+ > **收缩闸门(shrink gate)**:harness 拒绝任何不比被驱逐片段更小的 checkpoint(`summary is not
278
+ > smaller than the shadowed content`),否则压缩回滚、上下文永不缩小、最终触发模型侧溢出。Context GC
279
+ > 据此**自预测闸门**:用 host `tokenMeter` 复刻 harness 的 framing 估算(实测逐 token 一致),把指针
280
+ > 摘要/叙事体按被驱逐 span 的 token 预算动态**裁剪尾部**(指针头与工作集保留,细节仍在会话日志可
281
+ > 重取),保证 checkpoint 严格缩得更小、压缩真正提交——不出现"start 涨、summary 不涨"的假接管。
282
+ > 完整用法见 [`docs/CONTEXT-GC-GUIDE.zh.md`](docs/CONTEXT-GC-GUIDE.zh.md)(从 0 开始的使用教程)。
283
+
284
+ #### 启用入口(这一节就是答案:该功能开在哪)
285
+
286
+ Context GC 的装配在**两个平面**——web 现在**全部自动**,零配置:
287
+
288
+ - **host 平面(headless / TUI / base 型 profile)——开箱即用,无需任何配置**:
289
+ 主插件 `lib/index.js` 在 `ctx.effect` 里直接
290
+ `mountCompactionEngine(ctx, resolved, { readWorkspace }) → new Engine()` 注册 `compaction` 服务;
291
+ 配合插件 patch 禁用基座 `compaction-basic` 行,engram 即为该平面唯一 provider。
292
+ - `gcReplacesCompaction: true`(**默认**)→ `ContextGcEngine`(机械驱逐 + 重取指针);
293
+ - `gcReplacesCompaction: false` → 挂裸 `BasicCompactionEngine`(退化为 DSH 默认 LLM 摘要),
294
+ **`compaction` 服务永不缺席**。
295
+ - 关闭/开启:profile patch 里给 engram 行加 `config: { gcReplacesCompaction: false }`,或设置卡
296
+ 「记忆 GC」区的 `gcNarrative` 关掉叙事。
297
+ - **preset 平面(web profile)——全自动,零配置,覆盖全部预设**:web 面把 compaction 放在 agent
298
+ preset 的 isolate realm(shipped 预设自挂 `compaction-basic`),host 条目进不去、profile patch 也
299
+ 够不到预设文件(`mountPreset` 的 Include 不带 patches——已对源码核实)。所以插件在启动时
300
+ (`agentPresets` 服务就绪后)**自动改写每个仍处于出厂 stock 布局的预设**——默认预设 + 整张 roster
301
+ (shipped 根 + `~/.dsh/.agent-presets` 用户根,`standard`/`code`/`cordis` 和你自己的预设都覆盖):
302
+ 把 `compaction` 组里的 `compaction-basic` 行换成 `dsh-engram/compaction`。这一自动装配是:
303
+ - **默认开**(`autoWebCompaction: true`,设置卡「记忆 GC」可关),幂等——已替换则跳过;
304
+ **关掉的含义**:只是在之后的启动时不再自动接管(headless/TUI/base 的 host 平面不受影响)——
305
+ 它**不会**还原之前已接管的预设,要还原用 `npm run web-compaction:revert`;
306
+ - **逐预设判定,只碰 stock 布局**——用户自定义过 compaction 组的预设,以及根本没有 compaction 组的
307
+ 预设(如 shipped `minimal`),一律不写、只记日志;
308
+ - **带备份 + 严格校验**:每个文件写前在旁边留 `agent.cordis.yml.engram.bak`(create-only,保留首次
309
+ 原件),写后重读校验,任何一步可疑即回滚该文件;改写失败只 warn,该会话仍用 DSH 默认摘要,
310
+ **永不影响宿主**。
311
+ - **配置真实传导**:写进预设行的引擎配置来自设置卡/配置里的 `gcReplacesCompaction` 与
312
+ `gcNarrative`——改了这两个开关,下次启动会自动把已接管的行刷新成新配置(幂等重写),
313
+ 所以**设置卡对 web 平面同样生效**,不再硬编码 true/true。
314
+ - 手动的完全等价物(预览 / 审计 / 卸载前回归,随 npm 包发布的 CLI,默认扫 shipped + 用户两个根、
315
+ 可 `--file` 指定单个):
316
+ ```bash
317
+ npx dsh-engram status # stock → 未动;wired → 已接管;custom → 不碰
318
+ npx dsh-engram doctor # status + 按缺口排序的下一步建议
319
+ npx dsh-engram enable # 等价于启动时自动装配(通常 no-op)
320
+ npx dsh-engram revert # 还原全部预设的 stock 行(卸载 dsh-engram 前先跑这个)
321
+ ```
322
+ (仓库内 `npm run web-compaction:*` 是同一 CLI 的别名。)
323
+ 替换后的 `compaction` 组形如:
255
324
  ```yaml
256
325
  - id: compaction
257
326
  name: cordis:group
@@ -263,14 +332,40 @@ tool-call/result 配对、token 定价全部复用基本引擎。任何错误
263
332
  - id: engram-compaction # ← 替换原 compaction-basic
264
333
  name: dsh-engram/compaction
265
334
  config:
266
- gcReplacesCompaction: true # 可选;false=该 session 用默认 LLM 摘要
267
- gcNarrative: true
335
+ gcReplacesCompaction: true # 来自设置;false=该 session 用默认 LLM 摘要
336
+ gcNarrative: true # 来自设置;false=纯机械无 LLM
268
337
  - id: command-compact
269
338
  name: '@deepseek-ai/dsh-command-compact'
270
339
  - id: tool-result-pruner
271
340
  name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
272
341
  # 原有配置保留
273
342
  ```
343
+ ⚠️ **卸载前先 revert**:预设引用 `dsh-engram/compaction` 后,卸载 dsh-engram 会让该行悬空、
344
+ web 会话挂 loading。所以**卸载 engram 前**先 `npx dsh-engram revert`(还原全部预设,
345
+ 或逐个恢复各自的 `.engram.bak`)。harness 大版本升级会覆盖 shipped 预设,悬空引用随之自愈。
346
+
347
+ #### 从 npm 安装后的第一印象(可感知 / 易引导 / 易配置)
348
+
349
+ - **自动**:装进 profile 后启动一次即可——host 平面由 `mountCompactionEngine` 直接接管;web 平面
350
+ 由 `autoWebCompaction`(默认开)在启动时自动改写全部 stock 预设。**无需任何手动配置。**
351
+ - **可感知**:插件启动时把权威状态写到 `$DSH_HOME/engram/context-gc.status.json`
352
+ (`host` 平面模式 + `web` 平面每个预设的接管结果 + 生效配置)。随时
353
+ `npx dsh-engram status` 或 `npx dsh-engram doctor` 查看;web 记忆看板(ESR 任务看板)头部也有
354
+ 一枚状态徽标("Context GC·主机 ·N 预设",来自 overview API)。启动日志也有两行关键确认:
355
+ `engram context-gc: compaction = Context GC …` 与 `engram web-provision: wired Context GC into preset …`。
356
+ - **易配置**:一个设置卡(「记忆 GC」区)管全部:`autoWebCompaction`(web 自动接管开关)、
357
+ `gcReplacesCompaction`(用 Context GC 还是回退默认摘要)、`gcNarrative`(叙事兜底开关)——
358
+ 改动后 `dsh web` 重启生效,web 预设行会被自动刷新成新配置。
359
+
360
+ **验证是否生效**:`dsh web`(或对应 profile)重启后——
361
+ - host 平面(headless/TUI/base):启动日志出现
362
+ `engram context-gc: compaction = Context GC (mechanical eviction + re-fetch pointers)`;
363
+ - web 平面:日志出现
364
+ `engram web-provision: wired Context GC into preset "standard" (…); restart dsh web so sessions pick it up`
365
+ (每个被接管的预设一行);或 `npx dsh-engram status` 显示 `wired`;任意预设的会话内
366
+ `command-compact`/压力触发即走机械驱逐 + 重取指针。
367
+ - (若见 `… keeping the existing compaction service` 说明同域已有 provider、未替换成功;`web-provision … skipped (custom)`
368
+ 说明某预设是自定义/无 compaction 布局、未被触碰;`dsh-compaction-basic unavailable` 说明环境缺依赖,自动回退默认压缩。)
274
369
 
275
370
  ## 自动捕获策略
276
371
 
package/cordis.patch.yml CHANGED
@@ -14,6 +14,12 @@
14
14
  # dsh-engram from the profile re-arms the base row automatically.
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
+ # 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
22
+ # `npm run web-compaction:status|enable|revert`).
17
23
  - id: compaction-basic
18
24
  disabled: true
19
25
 
@@ -0,0 +1,192 @@
1
+ # dsh-engram Context GC — 从 0 开始的使用教程
2
+
3
+ > 适用范围:dsh web(preset 平面)+ headless/TUI/base(host 平面)。
4
+ > 一句话:**用「机械驱逐 + 重取指针」替代 DSH 自带的有损 LLM 全量摘要压缩。**
5
+
6
+ ---
7
+
8
+ ## 1. 这是什么
9
+
10
+ DSH 默认的上下文压缩(`compaction-basic`)在自动 / 手动 compact 时,把被驱逐的历史压成一段
11
+ **不可查询的散文摘要**——有损、且摘要本身还要烧 token。
12
+
13
+ dsh-engram 的 Context GC 把这一步换成:
14
+
15
+ 1. **扫描**被驱逐轮次里的 provenance 锚——`engram_store`/`engram_recall`/`engram_detail`
16
+ 回显的 `#记忆id`、`esr_*` 涉及的 `tsk_*`/`ent_*`、`file_path` 锚;
17
+ 2. **指针摘要**——每条被驱逐类别都带显式重取调用(`engram_detail(id)` / `engram_recall(query)`
18
+ / `[ESR]` 块 / `esr_ready`);active 工作集在摘要里**复述**、不驱逐;
19
+ 3. **兜底叙事**——只有**无锚轮次**(纯对话/推理,没有任何可重取落点)才走 scoped LLM 摘要
20
+ (`gcNarrative`,默认开);关掉后整条路径**零 LLM**,无锚轮次截断原文保留。
21
+
22
+ 触发时机跟 DSH 现有 compact 完全一致:**自动压力(约 80% 窗口阈值)→ context-overflow → 手动
23
+ `/compact`**。任何一步出错都会回退默认压缩,**永不破坏 compact、永不弄丢会话**。
24
+
25
+ ### 两个平面
26
+
27
+ | 平面 | 覆盖 | 接管方式 |
28
+ |---|---|---|
29
+ | host 平面 | headless / TUI / base 型 profile | 插件启动时直接注册 `compaction` 服务为零配置 ContextGcEngine |
30
+ | web 平面 | dsh web 的 agent preset(shipped + 用户预设) | 启动时自动把 stock 预设 `compaction` 组的 `compaction-basic` 行换成 `dsh-engram/compaction` 行 |
31
+
32
+ ---
33
+
34
+ ## 2. 前提
35
+
36
+ - 已安装 dsh(含 **dsh web**)与一个 web profile;
37
+ - dsh-engram 已作为 profile 依赖挂好(`link:/path/to/dsh-loom` 或 npm 包),profile patch 里有
38
+ `engram` 行;
39
+ - 后端依赖 `@deepseek-ai/dsh-compaction-basic` 可被解析(安装 engram 时自动链接);
40
+ - 记忆/任务侧已配置好 engram 存储域(`engram_store` 有可写域、ESR 可选)。
41
+
42
+ ---
43
+
44
+ ## 3. 从 0 安装并启用(3 步 + 1 条铁律)
45
+
46
+ ```bash
47
+ # ① 安装(源码开发方式;生产用 npm install dsh-engram)
48
+ cd /path/to/dsh-loom && npm link # 或 profile package.json 加 "dsh-engram": "link:..."
49
+
50
+ # ② 启动一次 dsh web —— 这一步做「自动装配」
51
+ # - host 平面:立即注册 Context GC 为 compaction 服务
52
+ # - web 平面:改写每个 stock 预设文件(compaction-basic 行 → dsh-engram/compaction 行,
53
+ # 每个文件旁留 agent.cordis.yml.engram.bak)
54
+ dsh web
55
+
56
+ # ③ 再重启一次 dsh web —— 让已接管的预设真正生效
57
+ dsh web
58
+ ```
59
+
60
+ **铁律:代码/预设改动后必须重启 dsh。** 运行中的进程把旧 `lib/*.js` 和旧预设行都缓存/装载在内存里,
61
+ 磁盘上的修复与文件改写不会热生效;只有重启(host 开机自动装配默认 `autoWebCompaction:true`)才会
62
+ 加载新代码并接管 web 预设。
63
+
64
+ > 也可以先手动预写再一次性重启:
65
+ > ```bash
66
+ > npx dsh-engram enable # 等价于启动时的自动装配(通常 no-op)
67
+ > npx dsh-engram status # 确认 preset 已 wired
68
+ > # 然后重启 dsh web 一次
69
+ > ```
70
+
71
+ ---
72
+
73
+ ## 4. 验证是否生效
74
+
75
+ ```bash
76
+ npx dsh-engram status # 每个预设显示 wired(已接管)/ stock(未动)/ custom(不碰)
77
+ npx dsh-engram doctor # status + 按缺口排序的下一步建议
78
+ ```
79
+
80
+ 启动日志的两行金标准:
81
+
82
+ ```
83
+ engram context-gc: compaction = Context GC (mechanical eviction + re-fetch pointers) # host
84
+ engram web-provision: wired Context GC into preset "standard" (…); restart dsh web … # web 每个预设一行
85
+ ```
86
+
87
+ 其他入口:
88
+ - 记忆看板头部状态徽标("Context GC · 主机 ·N 预设");
89
+ - 权威快照 `$DSH_HOME/engram/context-gc.status.json`。
90
+
91
+ ---
92
+
93
+ ## 5. 日常使用:它替你做掉了什么
94
+
95
+ 在任意会话里照常对话、照常用工具即可。当 compact 触发(自动压力 / 溢出 / 手动 `/compact`),
96
+ 落在会话里的 checkpoint 长这样:
97
+
98
+ ```
99
+ ## Context GC — evicted detail is RE-FETCHABLE (do not re-derive)
100
+
101
+ Older conversation was evicted to free context. The detail is NOT lost: it lives in the engram memory
102
+ store and the ESR task store. Re-fetch ONLY when you actually need it, using the tools below.
103
+
104
+ Evicted 3 turns. 2 with re-fetchable provenance, 1 narrative-only.
105
+
106
+ ### ESR task state
107
+ Reload the full graph with the [ESR] block / `esr_ready` — only re-fetch what you need.
108
+ - Active working set (still tracked in ESR):
109
+ - `tsk_act1` — migrate compaction engine
110
+ - 2 closed task(s) retained — details stay re-fetchable via the [ESR] block.
111
+
112
+ ### engram memories (re-fetchable)
113
+ Fetch the exact records that are still in the store:
114
+ - `engram_detail(id: "8f7a2c4e-…")` [decision]
115
+ - Re-run recall: `engram_recall(query: "compaction shrink gate")`
116
+
117
+ ### Narrative not captured elsewhere
118
+ (只有无锚轮次的 scoped 叙事;gcNarrative 关掉则为截断原文)
119
+ ```
120
+
121
+ 指针类别速查:
122
+
123
+ | 被驱逐内容 | checkpoint 里留下的指针 |
124
+ |---|---|
125
+ | engram 记忆(含 auto-capture / recall 命中 `#xxxxxx`) | `engram_detail(id)` / `engram_recall(query/entity)` |
126
+ | ESR 任务 / 实体 / stable 任务 | `[ESR]` 块 / `esr_ready` / 实体 `ent_*` |
127
+ | 文件锚 | `filePath` 路径提示 |
128
+ | 无锚轮次(纯对话 / 推理) | `gcNarrative` 叙事(默认开)或截断原文(关闭后) |
129
+ | active 工作集 | **在摘要里复述,绝不驱逐** |
130
+
131
+ 手动触发:`/compact` 命令(来自 `command-compact` 插件)。
132
+
133
+ ---
134
+
135
+ ## 6. 配置项
136
+
137
+ > 位置:dsh web 设置卡「记忆 GC」区 / profile patch 的 `engram` 行 `config:` / 配置 JSON。
138
+
139
+ | 键 | 默认 | 作用 |
140
+ |---|---|---|
141
+ | `autoWebCompaction` | `true` | 是否在启动时自动改写 stock 预设并接管 web 平面 |
142
+ | `gcReplacesCompaction` | `true` | `true`=用 Context GC;`false`=回退 DSH 默认 LLM 摘要(`compaction` 服务永不缺席) |
143
+ | `gcNarrative` | `true` | 无锚轮次是否走 scoped LLM 叙事;`false`=**纯机械、整条路径零 LLM** |
144
+ | `gcNarrativeMaxTokens` | `1024` | 叙事生成的最大 token 数 |
145
+ | `gcNarrativeMaxChars` | `4000` | 无锚轮次原文兜底的截断长度(字符) |
146
+
147
+ **改配置后要重启 dsh web**——web 预设行会在下次启动时被自动刷新成新配置(幂等重写)。
148
+
149
+ ---
150
+
151
+ ## 7. 运维与卸载
152
+
153
+ | 命令 | 作用 |
154
+ |---|---|
155
+ | `npx dsh-engram status` | 查看 host + 每个预设的接管状态与生效配置 |
156
+ | `npx dsh-engram doctor` | status + 按缺口排序的下一步 |
157
+ | `npx dsh-engram enable` | 手动重跑启动时的自动装配(通常 no-op) |
158
+ | `npx dsh-engram revert` | 把全部预设还原成 stock `compaction-basic` 行 |
159
+
160
+ - 仓库内 `npm run web-compaction:*` 是同一 CLI 的别名。
161
+ - 每个被改写文件旁有 `agent.cordis.yml.engram.bak`(create-only,保留首次原件)。
162
+ - **卸载 dsh-engram 之前先 `npx dsh-engram revert`**——否则预设里的 `dsh-engram/compaction` 行会
163
+ 悬空,web 会话挂 loading。
164
+
165
+ ---
166
+
167
+ ## 8. 排障速查
168
+
169
+ | 现象 | 原因 | 处理 |
170
+ |---|---|---|
171
+ | 自动压缩没触发,反而触发模型侧上下文溢出 | 运行中的进程还是**旧代码**(shrink-gate 回滚 bug) | **重启 dsh web** 加载修复(本教程最典型场景) |
172
+ | 手动 `/compact` 报 `manual compaction did not commit cleanly` | 旧 Context GC 代码缺 provider/model 落盘,commit 阶段被拒 | 同上:重启加载新代码 |
173
+ | 长会话自动压缩仍不提交(`start` 涨、`summary` 不涨) | 同上 / 或某些后端版本旧 | 重启;确认日志有 `compaction = Context GC` |
174
+ | 日志 `… keeping the existing compaction service` | 同域已有 compaction provider(基座行未禁 / 双 provider) | 检查 patch 与预设行,避免重复注册 |
175
+ | 日志 `web-provision … skipped (custom)` | 该预设是自定义/无 compaction 布局 | **设计如此**,不会碰 |
176
+ | 日志 `dsh-compaction-basic unavailable` | 环境缺后端依赖 | 安装依赖;自动回退默认压缩,host 不崩 |
177
+ | 极小被驱逐片段(span 小于 checkpoint 开销)不压缩 | harness 收缩闸门对**任何**后端一视同仁 | 属正常,没必要为几十 token 起 checkpoint |
178
+ | 改配置后没变化 | web 行要重启才刷新 | 重启 dsh web |
179
+
180
+ ---
181
+
182
+ ## 9. FAQ
183
+
184
+ - **有损吗?** 锚定细节**无损**(全部可通过工具重取);只有无锚轮次走叙事/截断;所有原文仍留在
185
+ 会话日志里,即使 checkpoint 被裁剪也能找回。
186
+ - **会不会更烧 token?** 默认叙事只覆盖无锚轮次;需要完全零 LLM 就开 `gcNarrative:false`。
187
+ - **和记忆面板 GC 什么关系?** 完全正交:面板 GC 管**存储有界**(TTL/超容量归档),Context GC 管
188
+ **上下文窗口**(compact 时驱逐)。
189
+ - **是强制的吗?** 不是。`gcReplacesCompaction:false` 即回退 DSH 默认 LLM 摘要。
190
+ - **什么时候触发?** 与默认 compact 完全一致:自动压力(约 80% 窗口阈值)→ 溢出 → 手动 `/compact`。
191
+ - **之前失败过的会话有没有被弄坏?** 没有。收紧闸门/commit 失败都会**整事务回滚**并写
192
+ `compaction/end`(含 error),不留半成品;修复后直接再 `/compact` 即可。