dsh-engram 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-loom contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,331 @@
1
+ # dsh-engram
2
+
3
+ > **[English](README.md) · [中文](README.zh.md)**
4
+
5
+ Minimalist long-term memory for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness), distilled from the
6
+ [symbolic-index](https://github.com/skepsun/symbolic-index) and [pi-esr](https://github.com/skepsun/pi-esr) ideas —
7
+ with one goal: **save tokens**.
8
+
9
+ - **Zero-LLM intake** — auto-captures meaningful events from tool results by pure
10
+ pattern matching (git milestones with a written `-m` commit message, edits to
11
+ key files, repeated errors), plus an explicit `engram_store`. Nothing on the hot
12
+ path calls a model, and pure plumbing — `git push` / `git stash` / silent
13
+ commits — is deliberately never recorded (see *Auto-capture policy* below).
14
+ - **Symbolic index + progressive disclosure** — a compact `[ENGRAM]` block (default
15
+ budget 700 chars ≈ 175 tokens; one line per memory) is injected at prompt
16
+ assembly and **frozen per session**, keeping the request prefix byte-stable for
17
+ KV-cache reuse. The agent drills down with `engram_recall` / `engram_detail` instead
18
+ of dumping raw hits into context.
19
+ - **ESR-lite closure protocol** — `esr_task` / `esr_close` / `esr_link` give tasks
20
+ a `draft → active → stable` lifecycle where `stable` requires real evidence
21
+ (`artifact` / `evaluation` / `memory_ref`), surfacing closure gaps instead of
22
+ letting the agent declare victory without proof.
23
+ - **Memory GC (pi-esr constraints)** — a scheduled, mechanical, archive-only
24
+ sweep: TTL-expired memories are archived, over-cap workspaces evict the
25
+ lowest-value entries, stable tasks past their retention window leave the
26
+ `[ESR]` surface, and dangling link edges are dropped. The working set (active
27
+ task refs, task memories, indexed hits) is never touched, and nothing is
28
+ hard-deleted — every archived entry keeps its id and stays re-fetchable.
29
+ - **Web viewer** — a memory browser with benchmark-ish stats and a config card,
30
+ built entirely on DSH's native settings slots (no third-party UI package).
31
+
32
+ ```
33
+ MIT · node >= 22.19 · host-half + browser-half in one package
34
+ ```
35
+
36
+ ## Why another memory plugin?
37
+
38
+ Surveys of the existing DSH plugin ecosystem show the recall-bridge, approval-gate,
39
+ LLM-distillation and vector/graph niches are already crowded. dsh-engram fills the
40
+ three gaps that matter for token discipline:
41
+
42
+ 1. **No model in the write path** — capture is deterministic pattern matching.
43
+ 2. **No raw text in the prompt** — a bounded symbolic index is injected, retrieval
44
+ stays on demand ("retrieved ≠ injected").
45
+ 3. **Honest task closure** — STABLE cannot be declared without evidence.
46
+
47
+ DSH already provides cross-session FTS (`ctx.sessionQuery`), storage
48
+ (`ctx.storageDomain`), prompt-injection hooks and settings slots; dsh-engram is a
49
+ thin composition layer over them, not a re-implementation.
50
+
51
+ ## Install
52
+
53
+ ```sh
54
+ # from GitHub (this repo)
55
+ dsh plugin --profile web add github:skepsun/dsh-engram
56
+
57
+ # once published to npm
58
+ dsh plugin --profile web add dsh-engram
59
+
60
+ # local development (symlink — edits apply immediately)
61
+ dsh plugin --profile web add link:/path/to/dsh-engram
62
+ ```
63
+
64
+ Then **restart `dsh web`**. Data persists in `~/.dsh/storages/dsh_engram.json`.
65
+
66
+ > The npm and GitHub installs below need **no manual dependency step**: pnpm
67
+ > installs `zod` and vendors the optional `@deepseek-ai/*` peers into the
68
+ > plugin's own `node_modules`, and the CLI auto-registers the plugin into the
69
+ > profile's `dsh.profile.bundles`. The `setup-links` step below is only for
70
+ > the `link:` development workflow, where pnpm deliberately does not install a
71
+ > symlinked directory's dependencies.
72
+
73
+ > A fresh session is required to see the injected `[ENGRAM]`/`[ESR]` blocks and the
74
+ > tools; both prompts and the tools registry are assembled per session.
75
+
76
+ ### Dependencies for `link:` installs
77
+
78
+ A *symlinked* plugin resolves its imports from its own `node_modules`, so the
79
+ host-side dependencies must be present **next to the checkout** — they are not
80
+ tracked by git:
81
+
82
+ ```sh
83
+ cd /path/to/dsh-engram
84
+ node scripts/setup-links.mjs # one command: links the @deepseek-ai
85
+ # workspace packages into node_modules AND
86
+ # installs zod (reused from the harness
87
+ # pnpm store, or via `npm install`)
88
+ ```
89
+
90
+ The script auto-locates the harness checkout at `../deepseek-harness` (also
91
+ works when it sits next to the repo's *parent*, e.g. `E:\deepseek-harness` +
92
+ `E:\kototoro_demo\dsh-engram`); override with `DSH_HARNESS_DIR`. Without this
93
+ step, `dsh web` boot fails with `ERR_MODULE_NOT_FOUND: Cannot find package
94
+ 'zod'` (and would fail on the `@deepseek-ai/*` peers next). `node
95
+ scripts/setup-links.mjs --check` prints the state without writing anything.
96
+
97
+ ## What you get in the GUI
98
+
99
+ After restart, inside the **native** DSH settings surface:
100
+
101
+ - **Settings → Engram Memory** — a standalone first-class settings section
102
+ (right after the Plugins section, not a child tab of it). Default
103
+ "All workspaces" view shows every workspace's memories/tasks/links
104
+ grouped by workspace (dropdown + prev/next workspace pager; the memory
105
+ table additionally pages 10 rows per page with a jump dropdown, fixed
106
+ column widths, 3-line clamped content ellipsis and full text on hover).
107
+ Overview
108
+ stat cards (counts by workspace/kind, auto-capture totals, per-workspace
109
+ `[ENGRAM]` index token estimate, cumulative GC totals), a searchable /
110
+ filterable memory table with archive + delete actions, an ESR task board
111
+ with an inline "new task" form and a per-task "fill evidence to close"
112
+ (artifact / evaluation / memory_ref → STABLE, same gates as esr_close),
113
+ a node + relation list (nodes are domain objects the model registers via
114
+ esr_node — package/service/repo/concept; relations via esr_link), and a
115
+ memory-GC panel (dry-run toggle + run button + pointer report). The GUI
116
+ create/close use the host's new `POST /api/dsh-engram/tasks` and
117
+ `POST /api/dsh-engram/tasks/close` routes. Model-side proactivity is driven
118
+ by the [ENGRAM]/[ESR] injected blocks: multi-step work gets a task now,
119
+ recurring domain objects get a node, related tasks/nodes get a link.
120
+
121
+ **Real behaviour telemetry (agent observability)** — the ESR page opens
122
+ with an "agent behaviour" panel fed by a new `usage` table (per workspace
123
+ × day rollup) + `GET /api/dsh-engram/stats`: every `engram_*`/`esr_*` tool call
124
+ is recorded (counts, failures, recall mechanics). Reported ratios:
125
+ **ESR proactivity** = esr calls / (memory + esr calls); **recall hit rate**
126
+ = recalls returning ≥1 hit / total recalls; **mean hits per query**;
127
+ **detail conversion** = a engram_detail following a hit recall within 8
128
+ session events. Per-tool counts + a 14-day daily rollup are shown too.
129
+ Numbers are real, from real sessions — lift ESR proactivity by watching
130
+ this panel and tuning the injected prompt.
131
+ - **Settings → Plugins → Plugin configuration → dsh-engram** — a collapsible
132
+ config card in the same style as the built-in "Shell / Agent loop / Web
133
+ search" cards: title + one-line description + chevron, collapsed by default,
134
+ click to expand/collapse. Open, its ~12 options render under four groups —
135
+ Capture & Search / Index / Lifecycle & GC / Security. Changes apply to new
136
+ sessions (frozen blocks stay stable); Discard / Save with an "unsaved" badge
137
+ on the header. The card drives the namespace through the connection's own
138
+ settings RPCs (not the isLoopback-gated scope), so it stays editable even
139
+ when the GUI is reached through an operator-authorized tunnel.
140
+
141
+ The browser half is served by DSH's client-module loader directly from this
142
+ package (`dsh.client` + `exports["./client"]`, no web-application rebuild); the
143
+ data comes from the loopback-fenced `/api/dsh-engram/*` route family. The fence
144
+ stays closed by default; to reach the memory viewer from an authorized tunnel
145
+ hostname, list it in the plugin's `trustedHosts` config (e.g. via the registry
146
+ or a profile patch):
147
+
148
+ ```jsonc
149
+ // patch/engram.json
150
+ { "engram": { "trustedHosts": ["cream-club-fragrances-caught.trycloudflare.com"] } }
151
+ ```
152
+
153
+ If you change `client/src`, rebuild the bundle with:
154
+
155
+ ```sh
156
+ npm run build:client
157
+ ```
158
+
159
+ ## Tools
160
+
161
+ | Tool | Purpose | Kind |
162
+ |---|---|---|
163
+ | `engram_store` | Explicitly store one memory (kind, tags, optional entity anchor) | write |
164
+ | `engram_recall` | Deterministic keyword recall over workspace memories; optional `search_sessions` FTS over past sessions | read |
165
+ | `engram_detail` | Full record of one memory id (provenance, tags, hits) | read |
166
+ | `esr_task` | Create a task entity (draft → active) | write |
167
+ | `esr_close` | Close a task via the evidence protocol (artifact + evaluation + memory_ref) | write |
168
+ | `esr_link` | Add a typed relation between two entities (mini graph) | write |
169
+ | `esr_gc` | Run the memory GC for the workspace (`dry_run:true` previews) | write |
170
+
171
+ ## Memory GC
172
+
173
+ A scheduled sweep (`gcIntervalHours`, default 24h) plus a manual `esr_gc` /
174
+ GUI button keeps the store bounded the pi-esr way — **mechanical, working-set
175
+ protected, archive-only**:
176
+
177
+ - TTL-expired memories are archived (soft; the id stays, retrievable via the
178
+ GUI's archived filter);
179
+ - over-cap workspaces evict the lowest-value *non-protected* memories;
180
+ - stable tasks past `gcStableRetentionDays` become archived and leave `[ESR]`;
181
+ - links whose **both** endpoints are gone are dropped (dangling edges).
182
+
183
+ GC never touches the working set: memories referenced by an active task
184
+ (`memory_refs`), task-kind memories, and already-indexed hits
185
+ (`hits >= promoteHits`). Run `esr_gc` with `dry_run: true` to preview. Nothing
186
+ is hard-deleted — the report ends with re-fetch pointers for everything it
187
+ archived, so archives are recoverable, not lost.
188
+
189
+ ## Auto-capture policy
190
+
191
+ Capture is deterministic and offline — it only sees tool *results*, never the
192
+ conversation. Exactly what earns a memory record:
193
+
194
+ | Tool result | Action | Signal |
195
+ |---|---|---|
196
+ | `git commit … -m "subject"` | record — the written subject is the memory | 0.55 |
197
+ | `git merge` / `rebase` / `cherry-pick` / `tag` / `checkout -b` | record (milestone) | 0.5 |
198
+ | `git push` / `git stash` / commit without `-m` | **skip** — plumbing echo, not a decision | — |
199
+ | write/edit of a significant config & doc path | record | 0.3 |
200
+ | read of a config path | record | 0.3 |
201
+ | repeated tool error | record (deduped by message) | 0.25 |
202
+
203
+ Explicit `engram_store` writes are always recorded regardless of these rules
204
+ (rate-limited per session).
205
+
206
+ **Who earns a `[ENGRAM]` index line** (this is what actually touches the prompt):
207
+ `signal >= minIndexSignal` **or** `hits >= promoteHits` **or** `kind === "task"`,
208
+ then capped by `indexMaxLines` / `indexMaxChars`. One extra guard keeps the pipe
209
+ clean: auto-captured git command echoes — text that embeds a shell chain
210
+ (`git push: cd … && …`) — stay out of the index even when above the signal
211
+ threshold, until recall hits have promoted them. Everything else sits quietly in
212
+ storage, reachable on demand via `engram_recall` / `engram_detail` — "retrieved ≠
213
+ injected".
214
+
215
+ ## Injected blocks
216
+
217
+ What the model actually sees (rendered once per session, then frozen):
218
+
219
+ ```
220
+ [ENGRAM] workspace: symbolic-index · 2 memories · 1 task(s) active · 0 links
221
+ [D] 06-18 Decided: use sqlite-vec for retrieval #a2331d87
222
+ [T] 06-18 Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref #tsk_8b26
223
+ drill: engram_store (user asks to remember) | engram_recall <query> | engram_detail <id> | esr_task / esr_close / esr_link
224
+
225
+ [ESR] tasks: 1 active / 1 stable
226
+ - tsk_0d: Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref
227
+ - closed: tsk_9a (RAG eval) · +1
228
+ ```
229
+
230
+ Prefixes: `[D]` decision · `[E]` error · `[P]` procedure · `[F]` fact ·
231
+ `[I]` insight · `[H]` handoff · `[T]` task. Membership follows the
232
+ *Auto-capture policy* (signal threshold / recall promotion / git-echo guard),
233
+ bounded by the configured line and character budgets. `#` ids address the full
234
+ records via `engram_detail`. When a workspace has no tasks, `[ESR]` still renders
235
+ one line naming `esr_task`/`esr_close` so the mechanism stays visible to the
236
+ model instead of vanishing.
237
+
238
+ ## Config
239
+
240
+ Defaults are token-conscious; override any key via the profile patch
241
+ (`~/.dsh/profiles/web/cordis.patch.yml`) or the web config card:
242
+
243
+ ```yaml
244
+ - id: engram
245
+ config:
246
+ autoCapture: true # zero-LLM tool-result capture
247
+ sessionSearch: true # engram_recall may also FTS past sessions
248
+ autoCapturePerSession: 40
249
+ indexMaxLines: 12 # [ENGRAM] line cap
250
+ indexMaxChars: 700 # [ENGRAM] char cap (token budget)
251
+ minIndexSignal: 0.4 # auto-captures below this stay out of the index
252
+ # (git command echoes are excluded regardless,
253
+ # until promoted by recall hits)
254
+ promoteHits: 3 # ...until recalled this many times
255
+ expireDays: 180 # memory TTL (0 = never)
256
+ maxMemoriesPerWorkspace: 2000
257
+ gcEnabled: true # scheduled memory GC
258
+ gcIntervalHours: 24 # sweep cadence
259
+ gcStableRetentionDays: 120 # stable tasks leave [ESR] after this
260
+ engramIndexOrder: 40 # systemPrompt section order (before tools band)
261
+ esrOrder: 41
262
+ ```
263
+
264
+ ## Development
265
+
266
+ ```sh
267
+ npm test # 29 tests: core + web API + GC + usage-observability (node:test)
268
+ npm run eval # offline recall + structure benchmark (deterministic)
269
+ npm run build:client
270
+ ```
271
+
272
+ ### Testing & evaluation (two layers of real testing)
273
+
274
+ **npm run eval** (eval/recall-bench.mjs) is the deterministic layer —
275
+ LongMemEval-style: a controlled corpus (ASCII + CJK, known tags/entities/
276
+ timestamps) measured through the real store/recall path (openEngramDomain +
277
+ domain.recall), reporting Precision@k / Recall@k / MRR / Hit@1 per probe
278
+ (exact tag, substring, multi-term, CJK, phrase-single, ordering, negative)
279
+ plus StructMemEval-flavoured structure metrics (exact-duplicate dedup rate,
280
+ entity anchoring coverage, dangling-link hygiene). Honest, reproducible
281
+ numbers — everyone gets the same output. Current run: AVG P@k 0.770 /
282
+ AVG R@k 1.000 / MRR 0.889 / hit@1 0.889 / 0 negative false-positives /
283
+ dedup 1.0.
284
+
285
+ **/api/dsh-engram/stats + the observability panel** is the real-session layer —
286
+ it answers how the model actually uses the memory in production (ESR
287
+ proactivity ratio, recall hit rate, detail conversion), while the eval
288
+ answers how good the retrieval layer itself is.
289
+
290
+ Repo layout: `lib/` (host half: store / capture / index-block / tools / api /
291
+ settings), `client/` (browser half, TSX + `build.mjs`), `test/` (node:test).
292
+
293
+ ## Troubleshooting
294
+
295
+ **The web GUI opens and immediately shows “Failed to load plugins”**, with a
296
+ loader error like:
297
+
298
+ ```
299
+ failed to apply loader entry … (@linxin666/dsh-client-ui-web-ui-settings):
300
+ keyed slot "settings.plugin.item" requires options.key
301
+ ```
302
+
303
+ Cause: DSH hosts since `0.1.0-rc.7` declare the config-card slot
304
+ `settings.plugin.item` as **keyed by the settings namespace a card edits** —
305
+ which is exactly how dsh-engram's own config card registers (under
306
+ `key: "dsh-engram"`). The `@linxin666/dsh-web-ui-all` family **before 0.2.0**
307
+ (`dsh-client-ui-web-ui-settings`) registered its group card into that slot
308
+ **without a `key`**, and because a single failed loader entry aborts the whole
309
+ boot, the GUI stays stuck on the failure page.
310
+
311
+ Fixes:
312
+
313
+ - **Proper fix — upgrade the family**: `@linxin666/dsh-web-ui-all@^0.2.x`.
314
+ The 0.2 line moved its settings surface out of the keyed slot into a
315
+ first-level `settings.section` (the upstream fix for exactly this error).
316
+ - **Immediate unblock**: add `key: "web-ui-plugins"` to that one
317
+ `settings.plugin.item` registration in the installed
318
+ `node_modules/@linxin666/dsh-client-ui-web-ui-settings/lib/client.js`, then
319
+ restart `dsh web`. (Under namespace-keyed dispatch the group card simply
320
+ stays hidden; nothing else on the page is affected.)
321
+
322
+ ## Related
323
+
324
+ - [symbolic-index](https://github.com/skepsun/symbolic-index) — the original cross-session
325
+ memory plugin (5-signal RRF fusion, sqlite-vec, Dream Engine).
326
+ - [pi-esr](https://github.com/skepsun/pi-esr) — project-lifetime evidence-driven
327
+ task states; the closure protocol here is its lite form.
328
+
329
+ ## License
330
+
331
+ MIT
package/README.zh.md ADDED
@@ -0,0 +1,276 @@
1
+ # dsh-engram
2
+
3
+ > **[中文](README.zh.md) · [English](README.md)**
4
+
5
+ 为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 设计的极简长期记忆插件,融合了
6
+ [symbolic-index](https://github.com/skepsun/symbolic-index) 与 [pi-esr](https://github.com/skepsun/pi-esr) 的思想——
7
+ 目标只有一个:**省 token**。
8
+
9
+ - **零 LLM 摄入** — 纯模式匹配从工具结果自动捕获有意义的事件(带书面 `-m` 提交信息的 git 里程碑、
10
+ 关键文件编辑、反复出现的错误),另有显式 `engram_store`。热路径上没有任何模型调用,纯粹的操作
11
+ ——`git push` / `git stash` / 无提交信息的 commit——刻意**从不记录**(见下方「自动捕获策略」)。
12
+ - **符号索引 + 渐进披露** — 一个紧凑的 `[ENGRAM]` 块(默认预算 700 字符 ≈ 175 token;每条记忆一行)在
13
+ 组装提示词时注入,并**按会话冻结**,让请求前缀字节稳定以复用 KV 缓存。模型需要细节时用
14
+ `engram_recall` / `engram_detail` 下钻,而不是把命中的原文灌进上下文。
15
+ - **ESR-lite 证据闭环** — `esr_task` / `esr_close` / `esr_link` 给任务一个 `draft → active → stable`
16
+ 生命周期,其中 `stable` 必须要有真实证据(`artifact` / `evaluation` / `memory_ref`),把"缺什么"
17
+ 摊在明面上,而不是让 agent 没有证据就宣布完成。
18
+ - **记忆 GC(pi-esr 约束)** — 定时、机械、只归档的回收:TTL 过期记忆归档、超容量工作区淘汰低价值条目、
19
+ stable 任务超保留窗离开 `[ESR]` 表面、悬空链接边清理。工作集(active 任务引用 / 任务记忆 / 已入索引命中)
20
+ 永不触碰;**不硬删任何东西**——归档条目保留 id、始终可重取。
21
+ - **Web 查看器** — 一个带统计的记忆浏览器和配置卡片,完全构建在 DSH 原生设置槽位上(不碰任何第三方 UI 包)。
22
+
23
+ ```
24
+ MIT · node >= 22.19 · host 半边 + 浏览器半边合在一包
25
+ ```
26
+
27
+ ## 为什么还要一个记忆插件?
28
+
29
+ 对既有 DSH 插件生态的调研显示,记忆领域里"召回桥 / 审批门 / LLM 蒸馏 / 向量+图"这几个方向已经挤满了。
30
+ dsh-engram 补的是对 token 纪律真正重要的三个空白:
31
+
32
+ 1. **写入路径没有模型** — 捕获是确定性的模式匹配。
33
+ 2. **提示词里不灌原文** — 只注入有界的符号索引,检索按需进行("检索到 ≠ 注入")。
34
+ 3. **诚实的任务闭环** — 没有证据就不能宣布 STABLE。
35
+
36
+ DSH 已经提供跨会话 FTS(`ctx.sessionQuery`)、存储(`ctx.storageDomain`)、提示词注入钩子和设置槽位;
37
+ dsh-engram 只是这些能力之上的一层薄组合层,而非重新实现。
38
+
39
+ ## 安装
40
+
41
+ ```sh
42
+ # 从 GitHub(本仓库)
43
+ dsh plugin --profile web add github:skepsun/dsh-engram
44
+
45
+ # 发布到 npm 之后
46
+ dsh plugin --profile web add dsh-engram
47
+
48
+ # 本地开发(符号链接——改动立即生效)
49
+ dsh plugin --profile web add link:/path/to/dsh-engram
50
+ ```
51
+
52
+ 然后**重启 `dsh web`**。数据保存在 `~/.dsh/storages/dsh_engram.json`。
53
+
54
+ > npm 与 GitHub 两种装法**都不需要手动补依赖**:pnpm 会自动安装 `zod`,
55
+ > 并把可选的 `@deepseek-ai/*` peers 嵌套装进插件自身的 `node_modules`,CLI 也会
56
+ > 自动把插件登记进 profile 的 `dsh.profile.bundles`。下面的 `setup-links` 只在
57
+ > `link:` 开发工作流里需要——pnpm 故意不为符号链接目录安装依赖。
58
+
59
+ > 需要新建会话才能看到注入的 `[ENGRAM]`/`[ESR]` 块和全部工具——提示词与工具注册表都是按会话装配的。
60
+
61
+ ### `link:` 安装的依赖准备
62
+
63
+ 符号链接安装的插件从**自身 checkout 的 `node_modules`** 解析 import,而这层依赖
64
+ 不被 git 跟踪,换机器(尤其 Windows)会报
65
+ `ERR_MODULE_NOT_FOUND: Cannot find package 'zod'`(接着是 `@deepseek-ai/*` peers)。
66
+ 一条命令重建依赖层:
67
+
68
+ ```sh
69
+ cd /path/to/dsh-engram
70
+ node scripts/setup-links.mjs # 把 @deepseek-ai 工作区包软链进 node_modules,
71
+ # 并安装 zod(优先复用 harness pnpm store,
72
+ # 找不到则回退 `npm install`)
73
+ ```
74
+
75
+ 脚本会自动定位 harness:`../deepseek-harness`(仓库上一级平级),也支持「仓库父级平级」布局
76
+ (如 `E:\deepseek-harness` 与 `E:\kototoro_demo\dsh-engram`)——都找不到再用 `DSH_HARNESS_DIR` 指定。
77
+ `node scripts/setup-links.mjs --check` 只打印状态不写入。
78
+
79
+ ## 在 Web 端能得到什么
80
+
81
+ 重启后,全部落在 **DSH 原生**设置界面里:
82
+
83
+ - **设置 → Engram 记忆** — 独立的一级设置页签(位于「插件」之后),不再是「插件」页里的子 tab;默认
84
+ 「全部工作区」视图完整展示所有工作区的记忆/任务/关系(按工作区分组,工作区下拉 + 上一/下一工作区
85
+ 翻页;记忆表格另行 10 条/页分页 + 跳页下拉,仅「类型 / 内容 / 操作」三列——正文列占满,时间、
86
+ 标签、signal/hits/TTL 等全部折叠进内容行内(meta 行 + 标签行),正文限高三行省略、
87
+ 行内「展开全文/收起」与 hover 均可看全文,归档/删除按钮竖向堆叠)。概览统计卡片(各工作区/类型的计数、自动捕获总量、各工作区 `[ENGRAM]` 索引
88
+ token 估算、GC 累计统计)、可搜索/可过滤的记忆表格(含归档与删除操作)、ESR 任务看板(「新建任务」
89
+ 表单 + 点击「填写证据关闭…」补 artifact/evaluation/memory_ref 转 STABLE)、节点与关系清单
90
+ (节点 = 模型用 esr_node 登记的领域对象,如包/服务/仓库/概念;关系 = esr_link),
91
+ 以及记忆 GC 面板(dry-run 开关 + 运行按钮 + 指针报告)。ESR 的 GUI 新建/关闭走宿主新增的
92
+ `POST /api/dsh-engram/tasks` 与 `POST /api/dsh-engram/tasks/close`(与 esr_task / esr_close 同一证据门)。
93
+ 模型侧的主动行为由 [ENGRAM]/[ESR] 注入块驱动:多步工作即时建任务、反复出现的领域对象即时登记节点、相关任务/节点即时互连。
94
+
95
+ **真实行为观测(agent 遥测)** — ESR 页顶部新增「agent 行为观测」面板:每次模型调用 `engram_*`/`esr_*` 工具都实时累计到
96
+ 按(工作区 × 天)的 usage 滚动行(新增 `usage` 表 + `GET /api/dsh-engram/stats`),折算成指标:
97
+ **ESR 主动性** = esr 工具调用数 /(记忆 + esr 工具调用总数);**召回命中率** = 有命中的 engram_recall 次数 / 总次数;
98
+ **平均命中/查询**;**detail 转化** = 命中召回后很快跟一次 engram_detail 的比例(会话内 8 事件窗口);失败数按工具记。
99
+ 面板同时列出各工具调用计数与最近 14 天逐日滚动。这些是真实会话的真实数字——想提升 ESR 主动性,
100
+ 就观察面板上 esr 占比并调整注入提示。
101
+ - **设置 → 插件 → 插件配置 → dsh-engram** — 与内置「终端 / Agent 循环 / 网页搜索」同款的
102
+ **默认折叠卡片**:标题 + 一行描述 + 箭头,点击展开/收起;展开后 12 个设置项按
103
+ 「捕获与检索 / 索引 / 生命周期与 GC / 安全」四个分组展示。改动对新建会话即时生效
104
+ (已冻结的块保持稳定);支持放弃修改 / 保存,有未保存改动时标题上出现「未保存」徽标。
105
+ 卡片通过连接自身的 settings RPC 直连命名空间(不走 isLoopback 门控的 scope),因此即使
106
+ GUI 经运营商授权的隧道访问也保持可编辑。
107
+
108
+ 浏览器半边由 DSH 的 client-module loader 直接从本包提供(`dsh.client` + `exports["./client"]`,无需重建
109
+ web 应用);数据来自 loopback 围栏保护的 `/api/dsh-engram/*` 路由族。围栏默认关闭隧道访问;如需经授权的
110
+ 隧道域名访问记忆查看器,把域名加进插件的 `trustedHosts` 配置(如通过 registry 或 profile patch):
111
+
112
+ ```jsonc
113
+ // patch/engram.json
114
+ { "engram": { "trustedHosts": ["cream-club-fragrances-caught.trycloudflare.com"] } }
115
+ ```
116
+
117
+ 修改 `client/src` 后重建 bundle:
118
+
119
+ ```sh
120
+ npm run build:client
121
+ ```
122
+
123
+ ## 测试与评测
124
+
125
+ ```sh
126
+ npm test # 29 项单元测试(含 usage 滚动 / /stats 路由)
127
+ npm run eval # 离线召回 + 结构基准(确定性语料,跑真实 store/recall 路径)
128
+ ```
129
+
130
+ `npm run eval` 的检索部分参照 LongMemEval 的问答式评测:受控语料(ASCII + CJK、标签/实体/时间戳已知),
131
+ 对真实 `domain.recall()` 逐一校准 **Precision@k / Recall@k / MRR / Hit@1**(probe 覆盖 tag 精确、子串、
132
+ 多词、CJK、短语唯一、标签排序、负样本无回);结构部分借鉴 StructMemEval:精确去重率(同文本存 3 次折叠为 1)、
133
+ 实体锚定覆盖率、节点/链接卫生(无悬空链接)。数字诚实、非调优——任何人跑出来都一样,复现即所得。
134
+
135
+ 两层「真实测试」的分工:`npm run eval` 回答「检索层本身有多好」(确定性、可复现);
136
+ `/api/dsh-engram/stats` + 观测面板回答「真实会话里模型实际怎么用」(ESR 主动性、召回命中率、detail 转化),
137
+ 两者结合才能判断:召回层没问题但命中率低 = 模型没学会问;反之亦然。
138
+
139
+ ## 工具
140
+
141
+ | 工具 | 用途 | 类型 |
142
+ |---|---|---|
143
+ | `engram_store` | 显式存入一条记忆(kind、tags、可选实体锚点) | 写 |
144
+ | `engram_recall` | 工作区记忆的确定性关键词召回;可选 `search_sessions` 跨会话 FTS | 读 |
145
+ | `engram_detail` | 一条记忆 id 的完整记录(来源、标签、命中数) | 读 |
146
+ | `esr_task` | 创建任务实体(draft → active) | 写 |
147
+ | `esr_close` | 按证据协议关闭任务(artifact + evaluation + memory_ref) | 写 |
148
+ | `esr_link` | 在两个实体之间添加类型化关系(迷你图) | 写 |
149
+ | `esr_gc` | 运行本工作区的记忆 GC(`dry_run:true` 预览不落库) | 写 |
150
+
151
+ ## 记忆 GC
152
+
153
+ 定时回收(`gcIntervalHours`,默认 24h)+ `esr_gc` 手动触发 + GUI 按钮,按 pi-esr 方式把存储保持在
154
+ 有界内——**机械、工作集保护、只归档**:
155
+
156
+ - TTL 过期记忆归档(软删;id 保留,可通过 GUI 的 archived 筛选检索);
157
+ - 超容量工作区淘汰最低价值的*非保护*记忆;
158
+ - stable 任务超 `gcStableRetentionDays` 归档、离开 `[ESR]`;
159
+ - 两端点都已消失的链接被清理(悬空边)。
160
+
161
+ GC 永不触碰工作集:active 任务 `memory_refs` 引用的记忆、task 类记忆、已入索引的命中
162
+ (`hits >= promoteHits`)。用 `esr_gc` + `dry_run: true` 先预览。**不硬删**——报告的末尾为所有
163
+ 归档项附上重取指针,归档可恢复、不是丢失。
164
+
165
+
166
+ ## 自动捕获策略
167
+
168
+ 捕获是确定性、离线的——只看到工具*结果*,从不看对话本身。什么会被记录成一条记忆:
169
+
170
+ | 工具结果 | 行为 | 信号 |
171
+ |---|---|---|
172
+ | `git commit … -m "提交信息"` | 记录——书面提交信息就是这条记忆 | 0.55 |
173
+ | `git merge` / `rebase` / `cherry-pick` / `tag` / `checkout -b` | 记录(里程碑) | 0.5 |
174
+ | `git push` / `git stash` / 无 `-m` 的 commit | **跳过**——操作回显,不是决策 | — |
175
+ | 写入/编辑关键配置与文档路径 | 记录 | 0.3 |
176
+ | 读取配置路径 | 记录 | 0.3 |
177
+ | 反复出现的工具错误 | 记录(按消息去重) | 0.25 |
178
+
179
+ 显式 `engram_store` 的记录不受上述规则约束(按会话限流)。
180
+
181
+ **谁能拿到 `[ENGRAM]` 索引行**(这才是真正进提示词的部分):
182
+ `signal >= minIndexSignal` **或** `hits >= promoteHits` **或** `kind === "task"`,
183
+ 再由 `indexMaxLines` / `indexMaxChars` 封顶。另有一道额外的闸保持管道干净:
184
+ 自动捕获的 git 命令回显——文本里嵌着 shell 命令链(`git push: cd … && …`)——
185
+ 即使信号超阈值也不进索引,直到被召回命中晋升为止。其余条目安静地躺在存储里,
186
+ 按需用 `engram_recall` / `engram_detail` 取用——「检索到 ≠ 注入」。
187
+
188
+ ## 注入块
189
+
190
+ 模型实际看到的内容(每个会话渲染一次,然后冻结):
191
+
192
+ ```
193
+ [ENGRAM] workspace: symbolic-index · 2 memories · 1 task(s) active · 0 links
194
+ [D] 06-18 Decided: use sqlite-vec for retrieval #a2331d87
195
+ [T] 06-18 Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref #tsk_8b26
196
+ drill: engram_store (user asks to remember) | engram_recall <query> | engram_detail <id> | esr_task / esr_close / esr_link
197
+
198
+ [ESR] tasks: 1 active / 1 stable
199
+ - tsk_0d: Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref
200
+ - closed: tsk_9a (RAG eval) · +1
201
+ ```
202
+
203
+ 前缀:`[D]` 决定 · `[E]` 错误 · `[P]` 流程 · `[F]` 事实 · `[I]` 洞察 · `[H]` 交接 · `[T]` 任务。
204
+ 入选规则遵循「自动捕获策略」(信号阈值 / 命中晋升 / git 回显守卫),并按配置的行数与字符预算封顶。
205
+ `#` id 通过 `engram_detail` 取完整记录。工作区没有任务时,`[ESR]` 仍会渲染一行点名 `esr_task`/`esr_close`,
206
+ 让机制对模型保持可见,而不是整体消失。
207
+
208
+ ## 配置
209
+
210
+ 默认值以 token 为优先;可通过 profile 补丁(`~/.dsh/profiles/web/cordis.patch.yml`)或 Web 配置卡片覆盖任意键:
211
+
212
+ ```yaml
213
+ - id: engram
214
+ config:
215
+ autoCapture: true # 零 LLM 工具结果捕获
216
+ sessionSearch: true # engram_recall 也可对历史会话 FTS
217
+ autoCapturePerSession: 40
218
+ indexMaxLines: 12 # [ENGRAM] 行数上限
219
+ indexMaxChars: 700 # [ENGRAM] 字符上限(token 预算)
220
+ minIndexSignal: 0.4 # 低于此信号的自动捕获不进索引
221
+ # (git 命令回显即便超阈值也不进,直到命中晋升)
222
+ promoteHits: 3 # ……直到被召回这么多次才进索引
223
+ expireDays: 180 # 记忆 TTL(0 = 永不过期)
224
+ maxMemoriesPerWorkspace: 2000
225
+ gcEnabled: true # 定时记忆 GC
226
+ gcIntervalHours: 24 # 回收节奏
227
+ gcStableRetentionDays: 120 # 超过此天数的 stable 任务离开 [ESR]
228
+ engramIndexOrder: 40 # systemPrompt section 顺序(位于 tools 段之前)
229
+ esrOrder: 41
230
+ ```
231
+
232
+ ## 开发
233
+
234
+ ```sh
235
+ npm test # 21 个测试:核心 + Web API + GC(node:test)
236
+ npm run build:client
237
+ ```
238
+
239
+ 仓库结构:`lib/`(宿主半边:store / capture / index-block / tools / api / settings)、
240
+ `client/`(浏览器半边,TSX + `build.mjs`)、`test/`(node:test)。
241
+
242
+ ## 故障排查
243
+
244
+ **Web 界面一打开就停在 “Failed to load plugins”**,loader 报错形如:
245
+
246
+ ```
247
+ failed to apply loader entry … (@linxin666/dsh-client-ui-web-ui-settings):
248
+ keyed slot "settings.plugin.item" requires options.key
249
+ ```
250
+
251
+ 原因:DSH 自 `0.1.0-rc.7` 起把配置卡槽位 `settings.plugin.item` 声明为**按
252
+ settings 命名空间键控**(卡片用自身编辑的命名空间作 `key` 注册——dsh-engram
253
+ 的配置卡正是用 `key: "dsh-engram"` 这样注册的)。`@linxin666/dsh-web-ui-all`
254
+ **0.2.0 之前的** `dsh-client-ui-web-ui-settings` 向该槽位注册分组卡片时**没有
255
+ 提供 `key`**;而 loader 只要有一个 entry 失败就会中止整个启动流程,于是 GUI
256
+ 一直卡在失败页。
257
+
258
+ 修复方式:
259
+
260
+ - **正确修复——升级全家桶**:`@linxin666/dsh-web-ui-all@^0.2.x`。0.2
261
+ 系列已把自身设置面从键控槽位迁出,改为一级 `settings.section`(上游正是
262
+ 为这个报错做的修复)。
263
+ - **临时解阻**:在已安装的
264
+ `node_modules/@linxin666/dsh-client-ui-web-ui-settings/lib/client.js` 中给那
265
+ 个 `settings.plugin.item` 注册补上 `key: "web-ui-plugins"`,然后重启
266
+ `dsh web`。(在按命名空间键控的派发下,分组卡片只是不显示,不影响页面其
267
+ 他部分。)
268
+
269
+ ## 相关项目
270
+
271
+ - [symbolic-index](https://github.com/skepsun/symbolic-index) — 原始跨会话记忆插件(5 信号 RRF 融合、sqlite-vec、Dream Engine)。
272
+ - [pi-esr](https://github.com/skepsun/pi-esr) — 项目全周期的证据驱动任务状态;这里的闭环协议是它的简化形态。
273
+
274
+ ## 许可
275
+
276
+ MIT
@@ -0,0 +1,10 @@
1
+ # dsh-engram bundle patch: inserts the host plugin row into the web profile
2
+ # roster. Activate with:
3
+ # dsh plugin --profile web add file:/d1/chuxiong/code/dsh-engram
4
+ #
5
+ # The row is a bare host-half plugin by package name. It does not need to
6
+ # enable storage-domain / storage-json / session-query-sqlite — the standard
7
+ # web profile (dsh-base x dsh-web-app) already mounts them.
8
+ - insert:
9
+ - id: engram
10
+ name: dsh-engram