dsh-engram 0.3.4 → 0.3.6

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,36 @@ 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
+ - **Session-start auto-recall (`autoRecallOnStart`, default on)** — the pull-only
44
+ design had a gap proven in real use: recall tools relied on model initiative,
45
+ and in practice the model almost never calls `engram_recall` on its own (19
46
+ real sessions, thousands of tool calls, ~5 recall calls total). So at the FIRST
47
+ assembly of a new session the host runs one deterministic BM25 recall keyed on
48
+ the session's first user message and injects the top ≤ `autoRecallLimit` hits
49
+ as a `[RECALL]` block (default 700-char budget, "few and precise" — agentmemory
50
+ candidate ①). Relevant memory is in context even without a single recall call.
51
+ Pure rule, zero LLM, pure read (never bumps `hits`); superseded stale truth does
52
+ not occupy an injection slot (still fetchable via `engram_detail`); frozen per
53
+ session together with `[ENGRAM]` for prefix stability.
54
+ - **Memory-to-memory semantics** — `engram_store` accepts optional
55
+ `supersedes` / `contradicts` memory ids (validated against the same
56
+ workspace). A superseded ("stale truth") memory is demoted to the tail of
57
+ recall and **excluded from the `[ENGRAM]` block**, while the superseding
58
+ statement keeps the line; a contradicted memory stays ranked but is flagged
59
+ `· contradicted by <id>`. Old rows are never deleted — re-fetchable, just
60
+ honestly ranked. Opt-in `autoSupersede` config (default **off**) auto-marks
61
+ entity-anchored replacement updates ("改用 / no longer / switched…") as
62
+ superseding their matching older row; explicit `supersedes` always wins.
63
+ - **Failure→fix closure (zero LLM)** — error memories carry a `cmd:` signature
64
+ tag; when that same command later succeeds, engram auto-sediments a
65
+ `procedure` memory (`fixed: <cmd> — N earlier failing runs now succeed`),
66
+ tags the earlier error rows `resolved` (never re-ranking them), and recall
67
+ then surfaces the fix instead of the stale failure.
27
68
  - **ESR-lite closure protocol** — `esr_task` / `esr_close` / `esr_link` give tasks
28
69
  a `draft → active → stable` lifecycle where `stable` requires real evidence
29
70
  (`artifact` / `evaluation` / `memory_ref`), surfacing closure gaps instead of
@@ -56,6 +97,18 @@ with one goal: **save tokens**.
56
97
  The `tools/result` recorder feeds the actionables' data sources; P4
57
98
  conversion metering stays (`#suggest-*` tags + 10-minute attribution +
58
99
  `GET /api/dsh-engram/triggerstats`). Pure rules, zero LLM.
100
+ - **Session-end todo auto-sink (draft safety net, `autoSinkTodosOnEnd` default
101
+ on)** — during a session the native todo stays cheap, per-session working
102
+ memory that dies with the session. But when a session closes at its teardown
103
+ edge (`session/disposed`) while its plan still has pending todos, they are
104
+ **auto-landed as ESR `draft` tasks** (name = the todo text, description
105
+ marked 「源自会话计划」, deduped against existing task names, capped by
106
+ `maxTasksPerWorkspace`), so the plan never silently evaporates. Sinking to
107
+ **draft** — not active — keeps them out of the `[ESR]` active rows and creates
108
+ no evidence obligation; an explicit `esr_claim` / `esr_task` step is still
109
+ what promotes a draft to active. The "nothing auto-written during the
110
+ session" trade-off is unchanged; only the end-of-session data-loss gap is
111
+ closed. Disable via profile patch or the settings card.
59
112
  - **Memory GC (pi-esr constraints)** — a scheduled, mechanical, archive-only
60
113
  sweep: TTL-expired memories are archived, over-cap workspaces evict the
61
114
  lowest-value entries, stable tasks past their retention window leave the
@@ -86,14 +139,105 @@ LLM-distillation and vector/graph niches are already crowded. dsh-engram fills t
86
139
  three gaps that matter for token discipline:
87
140
 
88
141
  1. **No model in the write path** — capture is deterministic pattern matching.
89
- 2. **No raw text in the prompt** — a bounded symbolic index is injected, retrieval
90
- stays on demand ("retrieved ≠ injected").
142
+ 2. **No raw text in the prompt** — a bounded symbolic index is injected, plus the
143
+ top ≤ N memories auto-recalled for the session's first message (within budget);
144
+ deeper retrieval stays on demand ("retrieved ≠ full-text injected").
91
145
  3. **Honest task closure** — STABLE cannot be declared without evidence.
92
146
 
93
147
  DSH already provides cross-session FTS (`ctx.sessionQuery`), storage
94
148
  (`ctx.storageDomain`), prompt-injection hooks and settings slots; dsh-engram is a
95
149
  thin composition layer over them, not a re-implementation.
96
150
 
151
+ ## Security model
152
+
153
+ The DSH plugin ecosystem is young and unvetted — there is no official directory,
154
+ no signature check, and the harness's read/write permission tiers do **not**
155
+ constrain what a plugin's own code can do. So the trust bar for a memory plugin
156
+ is higher than for a tool: a memory plugin sits on your prompt prefix and writes
157
+ to disk on your behalf. dsh-engram's model, stated plainly:
158
+
159
+ - **No network, ever.** The plugin has no outbound socket, no telemetry, no
160
+ update pinger. Nothing here can leak your sessions off the machine.
161
+ - **No shell, no filesystem access beyond one storage file.** It never executes
162
+ commands and never touches arbitrary paths. All data lives in the single
163
+ storage-domain unit `~/.dsh/storages/dsh_engram.json` (plus the session-log
164
+ reads it makes through DSH's own `ctx.sessionQuery`).
165
+ - **Secrets are redacted before they can land on disk.** Every write path —
166
+ `engram_store`, auto-capture, and the import/restore route — runs text through
167
+ the deterministic `redactText` rule engine (API keys, JWTs, `Bearer` tokens,
168
+ private keys, `key=value` secret shapes) *before* the dedup hash, the char
169
+ cap, and storage. Nothing sensitive is ever persisted, so nothing sensitive
170
+ can be recalled later.
171
+ - **Doing harm requires your harness's own permissions.** The plugin only *asks*
172
+ the host to do things via the same seams the tools you already trust use. It
173
+ does not widen sandbox policy or escalate to `danger-full-access` — if you
174
+ keep your profile's defaults, engram inherits exactly those bounds.
175
+ - **Read-mostly web API, loopback-fenced.** The GUI routes under
176
+ `/api/dsh-engram` reject any non-loopback caller unless you explicitly
177
+ whitelist a hostname via `trustedHosts`; even then the same-origin fence holds.
178
+ - **Verifiable.** `npm run dsh-engram -- doctor` (`/scripts/dsh-engram.mjs
179
+ doctor`) reports what the plugin is configured to touch; the whole store is
180
+ inspectable, nothing is opaque. If you ever doubt a claim in this section, the
181
+ code to check is `lib/redact.js`, `lib/store.js` and this plugin's
182
+ `cordis.patch.yml`.
183
+
184
+ What this deliberately does **not** claim: it is not an air-gap. If your profile
185
+ grants the agent `danger-full-access` or a shell, the agent can use those —
186
+ engram is a memory layer, not a sandbox. The point above is that *dsh-engram
187
+ itself* adds no surface of its own.
188
+
189
+ ## Data contract: export / import
190
+
191
+ Memory should not be a hostage. The four tables (memories / tasks / links /
192
+ entities) can be dumped and restored through a stable, versioned, machine-readable
193
+ shape — so the corpus survives a reinstall, a machine move, or (when a thin
194
+ adapter is written) a move to another harness, MCP end-point or Claude Code
195
+ skill. The contract is plain JSON with a self-describing header:
196
+
197
+ ```http
198
+ GET /api/dsh-engram/export?workspace=/path/to/project # one workspace
199
+ GET /api/dsh-engram/export # every workspace
200
+ ```
201
+
202
+ ```jsonc
203
+ {
204
+ "meta": { "format": "dsh-engram/export", "version": 1, "exportedAt": 1724…, "workspace": "/path/to/project" },
205
+ "memories": [ /* all rows, archived included — a backup never loses provenance */ ],
206
+ "tasks": [ /* draft/active/stable + archived */ ],
207
+ "links": [ /* typed edges */ ],
208
+ "entities": [ /* graph nodes */ ]
209
+ }
210
+ ```
211
+
212
+ Restore is idempotent by default and gated when destructive:
213
+
214
+ ```http
215
+ POST /api/dsh-engram/import # body: { payload, mode, dryRun?, workspace?, confirm? }
216
+ ```
217
+
218
+ - **`mode: "merge"`** (default) — writes only rows whose `id` is absent, so a
219
+ backup can be re-applied any number of times without duplicating anything. Each
220
+ row keeps its own `workspace`.
221
+ - **`mode: "replace"`** — wipes the target `workspace` from all four tables, then
222
+ restores only its rows. Destructive, so it additionally requires
223
+ `confirm: "restore"` in the body.
224
+ - **`dryRun: true`** — computes the identical plan (what would be written /
225
+ skipped) without writing a single row.
226
+ - **Import honors the same invariants as `storeMemory`**: memory text passes the
227
+ secret redactor before touching disk, and rows that would break the contract
228
+ (missing id/workspace, empty text, over `maxMemoryChars`, id already present
229
+ in merge, over the workspace memory cap) are skipped and listed in the
230
+ response report instead of aborting the batch.
231
+
232
+ A restore round-trip for one workspace is then:
233
+
234
+ ```sh
235
+ curl -s "http://127.0.0.1:3080/api/dsh-engram/export?workspace=$PWD" -o engram-backup.json
236
+ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
237
+ -H "content-type: application/json" \
238
+ -d "{\"payload\": $(cat engram-backup.json), \"mode\": \"merge\"}"
239
+ ```
240
+
97
241
  ## Install
98
242
 
99
243
  ```sh
@@ -275,15 +419,20 @@ npm run build:client
275
419
 
276
420
  | Tool | Purpose | Kind |
277
421
  |---|---|---|
278
- | `engram_store` | Explicitly store one memory (kind, tags, optional entity anchor) | write |
279
- | `engram_recall` | Deterministic keyword recall over workspace memories; optional `search_sessions` FTS over past sessions | read |
422
+ | `engram_store` | Explicitly store one memory (kind, tags, optional entity anchor, optional supersedes/contradicts ids, optional `source`/`conditions`) | write |
423
+ | `engram_recall` | Deterministic keyword recall over workspace memories; optional `search_sessions` FTS over past sessions; opt-in `scope=global`/`recallScope` cross-workspace recall with `[W:<ws>]` origin markers | read |
280
424
  | `engram_detail` | Full record of one memory id (provenance, tags, hits) | read |
281
425
  | `esr_task` | Create a task entity (draft → active) | write |
282
426
  | `esr_close` | Close a task via the evidence protocol (artifact + evaluation + memory_ref) | write |
283
427
  | `esr_link` | Add a typed relation between two entities (mini graph) | write |
428
+ | `esr_dep` | Add a dependency edge (blocks / relates-to / parent-of) between tasks | write |
429
+ | `esr_claim` | Atomically claim a task (assignee + claimedAt, draft → active) | write |
430
+ | `esr_unclaim` | Release a claimed task's assignee | write |
284
431
  | `esr_ready` | List claimable tasks (no open blocker, unclaimed) | read |
285
432
  | `esr_status` | Pull live ESR state + derived hints (`since_revision` short "unchanged" response) | read |
433
+ | `esr_node` | Create/update an entity node (stable symbol) | write |
286
434
  | `esr_gc` | Run the memory GC for the workspace (`dry_run:true` previews) | write |
435
+ | `esr_model` | Precomputed mental model of the workspace (`brief`/`full`, `max_chars`) | read |
287
436
 
288
437
  The `[ESR]` block is a frozen per-session snapshot and never auto-refreshes —
289
438
  call `esr_status` for the live truth.
@@ -331,24 +480,74 @@ Triggers follow DSH's own compaction timing (step pressure / context-overflow /
331
480
  `/compact`); the lock, replay validation, tool-call/result pairing and token
332
481
  pricing are all reused from the basic engine. Any error falls back to the
333
482
  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:
483
+ false` (or a host without `dsh-compaction-basic`) a bare `BasicCompactionEngine`
484
+ is mounted instead, so the `compaction` service never disappears while
485
+ dsh-engram is installed. Mounting registers the `compaction` service on the
486
+ plugin's fiber, so unloading/reloading engram restores the default engine.
487
+
488
+ > **Shrink gate**: the harness refuses any checkpoint that is not strictly
489
+ > smaller than the evicted span (`summary is not smaller than the shadowed
490
+ > content`) and rolls the compaction back — otherwise the context never shrinks
491
+ > and the session eventually hits the model-side overflow. Context GC
492
+ > self-predicts that gate: it mirrors the harness framing with the host
493
+ > `tokenMeter` (verified token-for-token identical) and truncates the
494
+ > pointer/narrative body to the evicted span's token budget (the pointer head
495
+ > and working set survive; the trimmed tail stays recoverable in the session
496
+ > log), so compactions actually commit. Full from-scratch usage:
497
+ > [`docs/CONTEXT-GC-GUIDE.zh.md`](docs/CONTEXT-GC-GUIDE.zh.md).
498
+
499
+ #### Where it is enabled (the entry point)
500
+
501
+ Assembly happens on **two planes** — and web is now fully automatic, zero config:
502
+
503
+ - **Host plane (headless / TUI / base profiles) — on by default, zero config**:
504
+ the main plugin (`lib/index.js`) mounts the engine inside `ctx.effect` via
505
+ `mountCompactionEngine(ctx, resolved, { readWorkspace }) → new Engine()`,
506
+ registering the `compaction` service on its own fiber; the plugin patch
507
+ disables the base `compaction-basic` row, making engram the sole host-plane
508
+ provider. `gcReplacesCompaction: true` (default) → `ContextGcEngine`;
509
+ `false` → a bare `BasicCompactionEngine`. Tune via profile patch
510
+ (`config: { gcReplacesCompaction: false }`) or the settings card's
511
+ `gcNarrative` knob (narrative off = fully mechanical).
512
+ - **Preset plane (web profile) — fully automatic, zero config, covers every
513
+ preset**: web keeps compaction inside each agent preset's isolated realm
514
+ (the shipped presets mount their own `compaction-basic`), unreachable from a
515
+ host-plane row — and unreachable from a profile patch too (`mountPreset`
516
+ builds its `Include.Config` with no patches; verified against the harness
517
+ source). So on boot, once the `agentPresets` service is ready, the plugin
518
+ **auto-edits every preset that still has the stock layout** — the default
519
+ preset plus the whole roster (shipped root + `~/.dsh/.agent-presets` user
520
+ root: `standard`, `code`, `cordis`, and your own presets): it swaps the
521
+ `compaction-basic` row inside the `compaction` group for
522
+ `dsh-engram/compaction`. That auto-wiring is:
523
+ - **on by default** (`autoWebCompaction: true`, toggle in the settings card's
524
+ memory-GC section), idempotent — an already-wired preset is left alone;
525
+ **what "off" means**: the plugin stops auto-wiring on later boots (the
526
+ headless/TUI/base host plane is unaffected) — it does NOT un-wire presets
527
+ that were already wired; use `npm run web-compaction:revert` for that;
528
+ - **per-preset**: only stock-layout presets are touched — a preset with a
529
+ custom compaction group, or none at all (e.g. the shipped `minimal`
530
+ preset), is never written, only logged;
531
+ - **backed up + validated**: a create-only `agent.cordis.yml.engram.bak`
532
+ holds each original next to its file, every result is re-read and verified
533
+ after writing, and any doubt rolls that file back; a failure only warns and
534
+ that session keeps DSH's default summarizer — the host plane is never
535
+ affected.
536
+ - **config actually propagates**: the engine config written into each row
537
+ comes from the live settings (`gcReplacesCompaction` / `gcNarrative`) — with
538
+ "already wired but with a different config" treated as a refresh, so
539
+ changing the settings knobs re-provisions the web rows on the next boot
540
+ instead of being silently ignored.
541
+ - A fully equivalent manual CLI, shipped in the npm package
542
+ (scans both the shipped and the user root; `--file <path>` targets one):
543
+ ```bash
544
+ npx dsh-engram status # stock = untouched · wired = active · custom = hands-off
545
+ npx dsh-engram doctor # status + ranked next steps for every gap found
546
+ npx dsh-engram enable # same as the boot-time auto-wiring (usually a no-op)
547
+ npx dsh-engram revert # restore the stock row everywhere — run BEFORE uninstalling
548
+ ```
549
+ (in the repo, `npm run web-compaction:*` is an alias of the same CLI.)
550
+ Either way the `compaction` group becomes:
352
551
  ```yaml
353
552
  - id: compaction
354
553
  name: cordis:group
@@ -360,14 +559,51 @@ provider of the same service in the same realm (`provide()` throws
360
559
  - id: engram-compaction # replaces the original compaction-basic
361
560
  name: dsh-engram/compaction
362
561
  config:
363
- gcReplacesCompaction: true # optional; false = default LLM summary for this session
364
- gcNarrative: true
562
+ gcReplacesCompaction: true # from settings; false = default LLM summary for this session
563
+ gcNarrative: true # from settings; false = fully mechanical
365
564
  - id: command-compact
366
565
  name: '@deepseek-ai/dsh-command-compact'
367
566
  - id: tool-result-pruner
368
567
  name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
369
568
  # keep the original config
370
569
  ```
570
+ ⚠️ **Uninstall = revert first**: once a preset references
571
+ `dsh-engram/compaction`, removing dsh-engram leaves every such row dangling
572
+ and web sessions hang on load — so run `npx dsh-engram revert`
573
+ (restores every preset) BEFORE uninstalling. A harness upgrade that ships a
574
+ fresh standard preset overwrites the swap and self-heals the dangling
575
+ reference.
576
+
577
+ #### First impressions after an npm install (perceive / guide / configure)
578
+
579
+ - **Automatic**: install into a profile and boot once — the host plane is taken
580
+ over by `mountCompactionEngine`, and the web plane by `autoWebCompaction`
581
+ (default on) rewriting every stock preset at boot. **Zero manual setup.**
582
+ - **Perceivable**: at boot the plugin writes an authoritative snapshot to
583
+ `$DSH_HOME/engram/context-gc.status.json` (`host` plane mode + the per-preset
584
+ `web` results + effective config). At any time run `npx dsh-engram status` or
585
+ `npx dsh-engram doctor`; the web memory board (ESR kanban) also shows a
586
+ header chip ("Context GC·主机 ·N 预设") fed by the overview API. Two startup
587
+ log lines confirm it too: `engram context-gc: compaction = Context GC …` and
588
+ `engram web-provision: wired Context GC into preset …`.
589
+ - **Configurable**: one settings card (memory-GC section) governs everything:
590
+ `autoWebCompaction` (web auto-wiring on/off), `gcReplacesCompaction`
591
+ (Context GC vs default summarizer), `gcNarrative` (narrative safety net).
592
+ Changes take effect after restarting `dsh web`, and the web rows are
593
+ refreshed to match automatically.
594
+
595
+ **Verify it took**: after restarting `dsh web` (or the profile) —
596
+ - host plane (headless/TUI/base): the startup log shows
597
+ `engram context-gc: compaction = Context GC (mechanical eviction + re-fetch pointers)`;
598
+ - web plane: the log shows one line per wired preset —
599
+ `engram web-provision: wired Context GC into preset "standard" (…); restart dsh web so sessions pick it up`,
600
+ or `npx dsh-engram status` reports `wired` for each; sessions composed
601
+ from ANY preset then compact via mechanical eviction + re-fetch pointers.
602
+ A line like `… keeping the existing compaction service` means another provider
603
+ already owns `compaction` in that realm (the swap didn't take); `web-provision … skipped (custom)`
604
+ means that preset was customized (or has no compaction group) and was left untouched;
605
+ `dsh-compaction-basic unavailable` means the backend is missing and everything
606
+ falls back to the default compressor.
371
607
 
372
608
  ## Auto-capture policy
373
609
 
@@ -408,7 +644,11 @@ ESR operating protocol (static, byte-identical every turn)
408
644
  [ENGRAM] workspace: symbolic-index · 2 memories · 1 task(s) active · 0 links
409
645
  [D] 06-18 Decided: use sqlite-vec for retrieval #a2331d87
410
646
  [T] 06-18 Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref #tsk_8b26
411
- drill: engram_store (user asks to remember) | engram_recall <query> | engram_detail <id> | esr_task / esr_close / esr_link
647
+ drill: use [RECALL] below · engram_detail <id> (full record) · engram_recall <query> (more) · esr_task/esr_node/esr_link (work)
648
+
649
+ [RECALL] recall · 1 hit(s) · first msg: retrieval
650
+ - [D] 06-18 Decided: use sqlite-vec for retrieval upgrade #a2331d87 ×2
651
+ context: use above · engram_detail <id> · engram_recall <query>
412
652
 
413
653
  [ESR] tasks: 1 active / 1 stable
414
654
  - tsk_0d: Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref
@@ -430,6 +670,14 @@ records via `engram_detail`. When a workspace has no tasks, `[ESR]` still render
430
670
  one line naming `esr_task`/`esr_close` so the mechanism stays visible to the
431
671
  model instead of vanishing.
432
672
 
673
+ `[RECALL]` (session-start auto recall, `autoRecallOnStart`) runs one deterministic
674
+ BM25 pass over the workspace keyed on the session's first user message and injects
675
+ the top `autoRecallLimit` hits, bounded by the `autoRecallMaxChars` character
676
+ budget; nothing renders on zero hits or an empty workspace. It is a *pointer* — the
677
+ model should treat it as "this is relevant; `engram_detail` for the full record" —
678
+ rather than full-text dumping. It is a pure read (never bumps `hits`), superseded
679
+ stale truth never occupies a slot, and it freezes per session with `[ENGRAM]`.
680
+
433
681
  ## Config
434
682
 
435
683
  Defaults are token-conscious; override any key via the profile patch
@@ -440,6 +688,10 @@ Defaults are token-conscious; override any key via the profile patch
440
688
  config:
441
689
  autoCapture: true # zero-LLM tool-result capture
442
690
  sessionSearch: true # engram_recall may also FTS past sessions
691
+ recallScope: workspace # recall scope: "workspace" (strict isolation, default) | "global" (opt-in cross-workspace recall with [W:<ws>] origin markers)
692
+ autoRecallOnStart: true # session-start auto recall (false = back to pure pull)
693
+ autoRecallLimit: 3 # [RECALL] max injected hits (few and precise)
694
+ autoRecallMaxChars: 700 # [RECALL] char budget
443
695
  autoCapturePerSession: 40
444
696
  indexMaxLines: 12 # [ENGRAM] line cap
445
697
  indexMaxChars: 700 # [ENGRAM] char cap (token budget)
@@ -482,7 +734,11 @@ dedup 1.0.
482
734
  **/api/dsh-engram/stats + the observability panel** is the real-session layer —
483
735
  it answers how the model actually uses the memory in production (ESR
484
736
  proactivity ratio, recall hit rate, detail conversion), while the eval
485
- answers how good the retrieval layer itself is.
737
+ answers how good the retrieval layer itself is. The stats read the
738
+ concatenated multi-frame `session.jsonl.zstd` logs with Node's built-in
739
+ `node:zlib` (frame scan + one reused decoder handle) — **no system `zstd` CLI
740
+ is required**, so the telemetry works out-of-the-box on Windows and other
741
+ hosts without an external codec.
486
742
 
487
743
  Repo layout: `lib/` (host half: store / capture / index-block / tools / api /
488
744
  settings), `client/` (browser half, TSX + `build.mjs`), `test/` (node:test).