dsh-engram 0.3.2 → 0.3.3
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 +107 -20
- package/README.zh.md +79 -13
- package/cordis.patch.yml +12 -0
- package/lib/api.js +24 -2
- package/lib/client.js +15 -1
- package/lib/compaction.js +85 -0
- package/lib/context-gc.js +626 -0
- package/lib/index-block.js +169 -39
- package/lib/index.js +78 -18
- package/lib/settings.js +4 -0
- package/lib/store.js +15 -0
- package/lib/tools.js +29 -0
- package/package.json +15 -2
package/README.md
CHANGED
|
@@ -39,30 +39,39 @@ with one goal: **save tokens**.
|
|
|
39
39
|
anchor immediately shows a relation; and `esr_dep` writes the dependency edge
|
|
40
40
|
into both the task's `deps` (blocker logic) and the shared links table, making
|
|
41
41
|
every task dependency a first-class visible edge in the graph.
|
|
42
|
-
- **
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
moves behaviour.
|
|
42
|
+
- **ESR trigger mechanism (pi-esr-aligned, hybrid: static protocol + frozen
|
|
43
|
+
snapshot + monotonically accumulated actionables + pull)** — prefix-cache
|
|
44
|
+
stability is the point. Teaching the model WHEN to use ESR is a **static
|
|
45
|
+
methodology** section (byte-identical every turn); the `[ESR]` block is a
|
|
46
|
+
deterministic **per-session frozen snapshot** (set at session start,
|
|
47
|
+
deterministically sorted, timestamps excluded, explicitly "WILL NOT
|
|
48
|
+
auto-refresh") **plus the session's monotonically accumulated actionables**:
|
|
49
|
+
the `promote:` / `root-cause:` / `close:` / `stale:` / `escalate:` lines are
|
|
50
|
+
APPENDED exactly once, the moment each matures, and stay frozen for the rest
|
|
51
|
+
of the session (never removed) — so the prefix changes only when genuinely
|
|
52
|
+
new decision-point info appears, never per-turn, while the timely nudges
|
|
53
|
+
(narrow funnel / recurring failure / work done / stale / balance) stay in
|
|
54
|
+
context. Live state is still **pulled** via `esr_status` (full deterministic
|
|
55
|
+
view + `since_revision` short "unchanged" response + derived actionables).
|
|
56
|
+
The `tools/result` recorder feeds the actionables' data sources; P4
|
|
57
|
+
conversion metering stays (`#suggest-*` tags + 10-minute attribution +
|
|
58
|
+
`GET /api/dsh-engram/triggerstats`). Pure rules, zero LLM.
|
|
60
59
|
- **Memory GC (pi-esr constraints)** — a scheduled, mechanical, archive-only
|
|
61
60
|
sweep: TTL-expired memories are archived, over-cap workspaces evict the
|
|
62
61
|
lowest-value entries, stable tasks past their retention window leave the
|
|
63
62
|
`[ESR]` surface, and dangling link edges are dropped. The working set (active
|
|
64
63
|
task refs, task memories, indexed hits) is never touched, and nothing is
|
|
65
64
|
hard-deleted — every archived entry keeps its id and stays re-fetchable.
|
|
65
|
+
- **Context GC (auto-GC = replaces DSH's auto-compact)** — NOT the memory-panel
|
|
66
|
+
GC. It takes over the host's `compaction` service and swaps DSH's lossy
|
|
67
|
+
LLM-summary compression for **mechanical eviction + re-fetch pointers**:
|
|
68
|
+
evicted turns are scanned for engram/ESR anchors (`#memory-id`, `tsk_*`,
|
|
69
|
+
`ent_*`, `file_path`) and replaced with one line "this detail lives at
|
|
70
|
+
`engram_detail` / `engram_recall` / the [ESR] block"; only un-provenanced
|
|
71
|
+
turns get a scoped LLM narrative (`gcNarrative`, default on, off = fully
|
|
72
|
+
mechanical). All six GC constraints hold (working-set-protected,
|
|
73
|
+
pointer-salience, no-provenance-no-evict…); any error falls back to the
|
|
74
|
+
default compressor — compaction is never broken.
|
|
66
75
|
- **Web viewer** — a memory browser with benchmark-ish stats and a config card,
|
|
67
76
|
built entirely on DSH's native settings slots (no third-party UI package).
|
|
68
77
|
|
|
@@ -272,9 +281,16 @@ npm run build:client
|
|
|
272
281
|
| `esr_task` | Create a task entity (draft → active) | write |
|
|
273
282
|
| `esr_close` | Close a task via the evidence protocol (artifact + evaluation + memory_ref) | write |
|
|
274
283
|
| `esr_link` | Add a typed relation between two entities (mini graph) | write |
|
|
284
|
+
| `esr_ready` | List claimable tasks (no open blocker, unclaimed) | read |
|
|
285
|
+
| `esr_status` | Pull live ESR state + derived hints (`since_revision` short "unchanged" response) | read |
|
|
275
286
|
| `esr_gc` | Run the memory GC for the workspace (`dry_run:true` previews) | write |
|
|
276
287
|
|
|
277
|
-
|
|
288
|
+
The `[ESR]` block is a frozen per-session snapshot and never auto-refreshes —
|
|
289
|
+
call `esr_status` for the live truth.
|
|
290
|
+
|
|
291
|
+
## GC: two planes — memory-panel reclamation + Context GC (replacing auto-compact)
|
|
292
|
+
|
|
293
|
+
### Memory-panel reclamation (store maintenance)
|
|
278
294
|
|
|
279
295
|
A scheduled sweep (`gcIntervalHours`, default 24h) plus a manual `esr_gc` /
|
|
280
296
|
GUI button keeps the store bounded the pi-esr way — **mechanical, working-set
|
|
@@ -292,6 +308,67 @@ GC never touches the working set: memories referenced by an active task
|
|
|
292
308
|
is hard-deleted — the report ends with re-fetch pointers for everything it
|
|
293
309
|
archived, so archives are recoverable, not lost.
|
|
294
310
|
|
|
311
|
+
### Context GC (replaces DSH's auto-compact)
|
|
312
|
+
|
|
313
|
+
DSH's default context compression is a **lossy LLM full summary**
|
|
314
|
+
(`compaction-basic`): evicted history is crushed into prose — un-queryable, and
|
|
315
|
+
the summary itself burns tokens. dsh-engram's auto-GC replaces it:
|
|
316
|
+
`ContextGcEngine extends BasicCompactionEngine` overrides the single
|
|
317
|
+
`summarize()` hook and swaps the summary body for:
|
|
318
|
+
|
|
319
|
+
1. **Scan** — the evicted messages are scanned for provenance anchors: memory
|
|
320
|
+
ids echoed by `engram_store`/`engram_recall`/`engram_detail`, `tsk_*`/`ent_*`
|
|
321
|
+
touched by `esr_*`, and `file_path` anchors (`esr_gc` and other management
|
|
322
|
+
calls are NOT anchors);
|
|
323
|
+
2. **Pointer summary** — every evicted category carries an explicit re-fetch
|
|
324
|
+
call (`engram_detail(id: "…")` / `engram_recall(query)` / the [ESR] block /
|
|
325
|
+
`esr_ready`); the active working set is restated, never evicted;
|
|
326
|
+
3. **Narrative safety net** — only un-provenanced turns (pure chat / reasoning)
|
|
327
|
+
get a scoped LLM summary (`gcNarrative`, default on; off = the whole path is
|
|
328
|
+
zero-LLM and un-provenanced turns are kept as truncated verbatim).
|
|
329
|
+
|
|
330
|
+
Triggers follow DSH's own compaction timing (step pressure / context-overflow /
|
|
331
|
+
`/compact`); the lock, replay validation, tool-call/result pairing and token
|
|
332
|
+
pricing are all reused from the basic engine. Any error falls back to the
|
|
333
|
+
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:
|
|
352
|
+
```yaml
|
|
353
|
+
- id: compaction
|
|
354
|
+
name: cordis:group
|
|
355
|
+
group: true
|
|
356
|
+
isolate:
|
|
357
|
+
compaction: true
|
|
358
|
+
toolResultPruner: true
|
|
359
|
+
config:
|
|
360
|
+
- id: engram-compaction # replaces the original compaction-basic
|
|
361
|
+
name: dsh-engram/compaction
|
|
362
|
+
config:
|
|
363
|
+
gcReplacesCompaction: true # optional; false = default LLM summary for this session
|
|
364
|
+
gcNarrative: true
|
|
365
|
+
- id: command-compact
|
|
366
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
367
|
+
- id: tool-result-pruner
|
|
368
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
369
|
+
# keep the original config
|
|
370
|
+
```
|
|
371
|
+
|
|
295
372
|
## Auto-capture policy
|
|
296
373
|
|
|
297
374
|
Capture is deterministic and offline — it only sees tool *results*, never the
|
|
@@ -323,6 +400,11 @@ injected".
|
|
|
323
400
|
What the model actually sees (rendered once per session, then frozen):
|
|
324
401
|
|
|
325
402
|
```
|
|
403
|
+
ESR operating protocol (static, byte-identical every turn)
|
|
404
|
+
1. Before starting: esr_ready to see claimable work; esr_status for live state.
|
|
405
|
+
2. Multi-step work → esr_task (draft first); claim → esr_claim; finish → esr_close (all three gates).
|
|
406
|
+
state is the single source of truth: unsure → call esr_status.
|
|
407
|
+
|
|
326
408
|
[ENGRAM] workspace: symbolic-index · 2 memories · 1 task(s) active · 0 links
|
|
327
409
|
[D] 06-18 Decided: use sqlite-vec for retrieval #a2331d87
|
|
328
410
|
[T] 06-18 Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref #tsk_8b26
|
|
@@ -331,6 +413,9 @@ drill: engram_store (user asks to remember) | engram_recall <query> | engram_det
|
|
|
331
413
|
[ESR] tasks: 1 active / 1 stable
|
|
332
414
|
- tsk_0d: Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref
|
|
333
415
|
- closed: tsk_9a (RAG eval) · +1
|
|
416
|
+
snapshot from session start — WILL NOT auto-refresh; call esr_status for live state
|
|
417
|
+
# this-session actionables (frozen)
|
|
418
|
+
promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-promote
|
|
334
419
|
```
|
|
335
420
|
|
|
336
421
|
Prefixes: `[D]` decision · `[E]` error · `[P]` procedure · `[F]` fact ·
|
|
@@ -367,6 +452,8 @@ Defaults are token-conscious; override any key via the profile patch
|
|
|
367
452
|
gcEnabled: true # scheduled memory GC
|
|
368
453
|
gcIntervalHours: 24 # sweep cadence
|
|
369
454
|
gcStableRetentionDays: 120 # stable tasks leave [ESR] after this
|
|
455
|
+
gcReplacesCompaction: true # Context GC: take over DSH auto-compact (false = keep the default LLM summarizer)
|
|
456
|
+
gcNarrative: true # scoped LLM narrative for un-provenanced turns; false = fully mechanical (zero LLM)
|
|
370
457
|
engramIndexOrder: 40 # systemPrompt section order (before tools band)
|
|
371
458
|
esrOrder: 41
|
|
372
459
|
```
|
|
@@ -374,7 +461,7 @@ Defaults are token-conscious; override any key via the profile patch
|
|
|
374
461
|
## Development
|
|
375
462
|
|
|
376
463
|
```sh
|
|
377
|
-
npm test #
|
|
464
|
+
npm test # 152 tests: core + web API + GC + usage-observability + Context GC + ESR triggers (node:test)
|
|
378
465
|
npm run eval # offline recall + structure benchmark (deterministic)
|
|
379
466
|
npm run build:client
|
|
380
467
|
```
|
package/README.zh.md
CHANGED
|
@@ -27,19 +27,24 @@
|
|
|
27
27
|
`task --relates_to--> entity` 边(幂等),挂着领域锚点的任务立刻带关系;
|
|
28
28
|
`esr_dep` 双写——依赖边同时进 `tasks[].deps`(blocker 逻辑)与 links 表,图谱里每条
|
|
29
29
|
任务依赖都是一等可见的边。
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
返回各 kind 的 shown/converted/rate。
|
|
30
|
+
- **ESR 触发机制(pi-esr 对齐,混合:静态协议 + 冻结快照 + 单调渐进 actionables + 拉取)** —
|
|
31
|
+
前缀缓存稳定是核心。模型「何时用 ESR」靠**静态方法论**(`engram:esr-method`,每轮逐字节相同);
|
|
32
|
+
`[ESR]` 块 = 确定性**冻结快照**(会话开始定下、确定性排序、时间戳排除、明示 WILL NOT
|
|
33
|
+
auto-refresh)+ 本会话**单调渐进的 actionables**:`promote:/root-cause:/close:/stale:/escalate:`
|
|
34
|
+
在它们成熟的那一刻被**一次追加进块并冻结**(永不回撤)——前缀只在「真正出现新决策点信息」时变化、
|
|
35
|
+
绝不逐轮漂移,同时保留决策点提醒(漏斗最窄处 / 复发失败 / 收工 / stale / 平衡)。实时状态仍可
|
|
36
|
+
**拉取**:`esr_status`(全量视图 + `since_revision` 增量短响应 + 派生 actionables)。
|
|
37
|
+
recorder 由 `tools/result` 订阅喂入(actionables 的数据源);P4 转化度量照常(`#suggest-*` 标记 +
|
|
38
|
+
10 分钟归因 + `GET /api/dsh-engram/triggerstats`)。纯规则、零 LLM。
|
|
40
39
|
- **记忆 GC(pi-esr 约束)** — 定时、机械、只归档的回收:TTL 过期记忆归档、超容量工作区淘汰低价值条目、
|
|
41
40
|
stable 任务超保留窗离开 `[ESR]` 表面、悬空链接边清理。工作集(active 任务引用 / 任务记忆 / 已入索引命中)
|
|
42
41
|
永不触碰;**不硬删任何东西**——归档条目保留 id、始终可重取。
|
|
42
|
+
- **Context GC(自动 GC = 替代 DSH 自动 compact)** — 不是记忆面板 GC:接管 DSH 自带的上下文压缩
|
|
43
|
+
(`compaction` 服务),把「有损 LLM 全量摘要」换成**机械驱逐 + 重取指针**——扫描被驱逐轮次里的
|
|
44
|
+
engram/ESR 锚(`#记忆id`、`tsk_*`、`ent_*`、`file_path`),只留一行「细节在 `engram_detail` /
|
|
45
|
+
`engram_recall` / `[ESR]` 里」;只有无锚轮次才走 scoped LLM 叙事兜底(`gcNarrative`,默认开、可关成纯机械)。
|
|
46
|
+
六条 GC 约束(working-set-protected / pointer-salience / no-provenance-no-evict…)全部落地;任何错误回退
|
|
47
|
+
默认压缩,永不破坏 compact。
|
|
43
48
|
- **Web 查看器** — 一个带统计的记忆浏览器和配置卡片,完全构建在 DSH 原生设置槽位上(不碰任何第三方 UI 包)。
|
|
44
49
|
|
|
45
50
|
```
|
|
@@ -176,7 +181,7 @@ npm run build:client
|
|
|
176
181
|
## 测试与评测
|
|
177
182
|
|
|
178
183
|
```sh
|
|
179
|
-
npm test #
|
|
184
|
+
npm test # 152 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发)
|
|
180
185
|
npm run eval # 离线召回 + 结构基准(确定性语料,跑真实 store/recall 路径)
|
|
181
186
|
```
|
|
182
187
|
|
|
@@ -199,9 +204,15 @@ npm run eval # 离线召回 + 结构基准(确定性语料,跑真实 store/
|
|
|
199
204
|
| `esr_task` | 创建任务实体(draft → active) | 写 |
|
|
200
205
|
| `esr_close` | 按证据协议关闭任务(artifact + evaluation + memory_ref) | 写 |
|
|
201
206
|
| `esr_link` | 在两个实体之间添加类型化关系(迷你图) | 写 |
|
|
207
|
+
| `esr_ready` | 列出可认领任务(无 blocker、无人认领) | 读 |
|
|
208
|
+
| `esr_status` | 拉取实时 ESR 状态 + 派生提示(`since_revision` 增量短响应) | 读 |
|
|
202
209
|
| `esr_gc` | 运行本工作区的记忆 GC(`dry_run:true` 预览不落库) | 写 |
|
|
203
210
|
|
|
204
|
-
|
|
211
|
+
`[ESR]` 块是每会话冻结快照,绝不中途刷新——要最新状态调 `esr_status`。
|
|
212
|
+
|
|
213
|
+
## GC:两块——记忆面板回收 + Context GC(替代自动 compact)
|
|
214
|
+
|
|
215
|
+
### 记忆面板回收(存储维护)
|
|
205
216
|
|
|
206
217
|
定时回收(`gcIntervalHours`,默认 24h)+ `esr_gc` 手动触发 + GUI 按钮,按 pi-esr 方式把存储保持在
|
|
207
218
|
有界内——**机械、工作集保护、只归档**:
|
|
@@ -215,6 +226,51 @@ GC 永不触碰工作集:active 任务 `memory_refs` 引用的记忆、task
|
|
|
215
226
|
(`hits >= promoteHits`)。用 `esr_gc` + `dry_run: true` 先预览。**不硬删**——报告的末尾为所有
|
|
216
227
|
归档项附上重取指针,归档可恢复、不是丢失。
|
|
217
228
|
|
|
229
|
+
### Context GC(替换 DSH 自动 compact)
|
|
230
|
+
|
|
231
|
+
DSH 默认的上下文压缩是**有损 LLM 全量摘要**(`compaction-basic`):被驱逐的历史被压缩成散文,
|
|
232
|
+
不可查询、摘要本身还烧 token。dsh-engram 的自动 GC 替代它:`ContextGcEngine extends
|
|
233
|
+
BasicCompactionEngine` 只重写唯一的 `summarize()` 钩子,把摘要正文换成:
|
|
234
|
+
|
|
235
|
+
1. **扫描**被驱逐消息的 provenance 锚——`engram_store`/`engram_recall`/`engram_detail` 回显的
|
|
236
|
+
`#记忆id`、`esr_*` 涉及的 `tsk_*`/`ent_*`、`file_path` 锚(`esr_gc` 等管理工具**不算**锚);
|
|
237
|
+
2. **指针摘要**:每条被驱逐类别都带显式重取调用(`engram_detail(id: "…")` / `engram_recall(query)`
|
|
238
|
+
/ `[ESR]` 块 / `esr_ready`),active 工作集在摘要里**复述**不驱逐;
|
|
239
|
+
3. **兜底叙事**:只有无锚轮次(纯对话/推理)才走 scoped LLM 摘要(`gcNarrative`,默认开;关掉后整条
|
|
240
|
+
路径零 LLM,无锚轮次截断原文保留)。
|
|
241
|
+
|
|
242
|
+
触发时机完全跟随 DSH 现有 compact(step pressure / context-overflow / `/compact`);锁、回放校验、
|
|
243
|
+
tool-call/result 配对、token 定价全部复用基本引擎。任何错误 → 回退默认压缩,**永不破坏 compact**。
|
|
244
|
+
装配即注册 `compaction` 服务,卸载/reload engram 自动还回默认引擎。
|
|
245
|
+
|
|
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 生效:
|
|
255
|
+
```yaml
|
|
256
|
+
- id: compaction
|
|
257
|
+
name: cordis:group
|
|
258
|
+
group: true
|
|
259
|
+
isolate:
|
|
260
|
+
compaction: true
|
|
261
|
+
toolResultPruner: true
|
|
262
|
+
config:
|
|
263
|
+
- id: engram-compaction # ← 替换原 compaction-basic
|
|
264
|
+
name: dsh-engram/compaction
|
|
265
|
+
config:
|
|
266
|
+
gcReplacesCompaction: true # 可选;false=该 session 用默认 LLM 摘要
|
|
267
|
+
gcNarrative: true
|
|
268
|
+
- id: command-compact
|
|
269
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
270
|
+
- id: tool-result-pruner
|
|
271
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
272
|
+
# 原有配置保留
|
|
273
|
+
```
|
|
218
274
|
|
|
219
275
|
## 自动捕获策略
|
|
220
276
|
|
|
@@ -243,6 +299,11 @@ GC 永不触碰工作集:active 任务 `memory_refs` 引用的记忆、task
|
|
|
243
299
|
模型实际看到的内容(每个会话渲染一次,然后冻结):
|
|
244
300
|
|
|
245
301
|
```
|
|
302
|
+
ESR 操作协议(静态,每轮逐字节相同)
|
|
303
|
+
1. 动工前:esr_ready 看可认领工作;esr_status 拿实时状态。
|
|
304
|
+
2. 多步工作 → esr_task(draft 起步);动工 → esr_claim;收工 → esr_close(三证齐)。
|
|
305
|
+
state 是唯一真相:拿不准 state 就 call esr_status。
|
|
306
|
+
|
|
246
307
|
[ENGRAM] workspace: symbolic-index · 2 memories · 1 task(s) active · 0 links
|
|
247
308
|
[D] 06-18 Decided: use sqlite-vec for retrieval #a2331d87
|
|
248
309
|
[T] 06-18 Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref #tsk_8b26
|
|
@@ -251,6 +312,9 @@ drill: engram_store (user asks to remember) | engram_recall <query> | engram_det
|
|
|
251
312
|
[ESR] tasks: 1 active / 1 stable
|
|
252
313
|
- tsk_0d: Retrieval upgrade — ACTIVE · gap: artifact, evaluation, memory_ref
|
|
253
314
|
- closed: tsk_9a (RAG eval) · +1
|
|
315
|
+
snapshot from session start — WILL NOT auto-refresh; call esr_status for live state
|
|
316
|
+
# this-session actionables (frozen)
|
|
317
|
+
promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-promote
|
|
254
318
|
```
|
|
255
319
|
|
|
256
320
|
前缀:`[D]` 决定 · `[E]` 错误 · `[P]` 流程 · `[F]` 事实 · `[I]` 洞察 · `[H]` 交接 · `[T]` 任务。
|
|
@@ -281,6 +345,8 @@ drill: engram_store (user asks to remember) | engram_recall <query> | engram_det
|
|
|
281
345
|
gcEnabled: true # 定时记忆 GC
|
|
282
346
|
gcIntervalHours: 24 # 回收节奏
|
|
283
347
|
gcStableRetentionDays: 120 # 超过此天数的 stable 任务离开 [ESR]
|
|
348
|
+
gcReplacesCompaction: true # Context GC:接管 DSH 自动 compact(false=回退默认 LLM 压缩)
|
|
349
|
+
gcNarrative: true # 无锚轮次走 scoped LLM 叙事;false=纯机械(零 LLM)
|
|
284
350
|
engramIndexOrder: 40 # systemPrompt section 顺序(位于 tools 段之前)
|
|
285
351
|
esrOrder: 41
|
|
286
352
|
```
|
|
@@ -288,7 +354,7 @@ drill: engram_store (user asks to remember) | engram_recall <query> | engram_det
|
|
|
288
354
|
## 开发
|
|
289
355
|
|
|
290
356
|
```sh
|
|
291
|
-
npm test #
|
|
357
|
+
npm test # 152 个测试:核心 + Web API + GC + Context GC + ESR 触发(node:test)
|
|
292
358
|
npm run build:client
|
|
293
359
|
```
|
|
294
360
|
|
package/cordis.patch.yml
CHANGED
|
@@ -5,6 +5,18 @@
|
|
|
5
5
|
# The row is a bare host-half plugin by package name. It does not need to
|
|
6
6
|
# enable storage-domain / storage-json / session-query-sqlite — the standard
|
|
7
7
|
# web profile (dsh-base x dsh-web-app) already mounts them.
|
|
8
|
+
|
|
9
|
+
# dsh-engram REPLACES the host-plane auto-compact: its Context GC engine
|
|
10
|
+
# provides the `compaction` service (see docs/PROPOSAL-contextgc.zh.md).
|
|
11
|
+
# cordis refuses two sibling rows registering the same service key, so the
|
|
12
|
+
# base row must be disabled here — this patch layer wins because bundle patch
|
|
13
|
+
# layers apply in bundle order and dsh-engram loads after dsh-base. Removing
|
|
14
|
+
# dsh-engram from the profile re-arms the base row automatically.
|
|
15
|
+
# NOTE: with `gcReplacesCompaction:false`, dsh-engram still mounts a plain
|
|
16
|
+
# BasicCompactionEngine, so compaction never goes away while it is installed.
|
|
17
|
+
- id: compaction-basic
|
|
18
|
+
disabled: true
|
|
19
|
+
|
|
8
20
|
- insert:
|
|
9
21
|
- id: engram
|
|
10
22
|
name: dsh-engram
|
package/lib/api.js
CHANGED
|
@@ -477,10 +477,33 @@ export function makeEngramRoutes(service) {
|
|
|
477
477
|
handler: (req, res) => guard(req, res, method, handler),
|
|
478
478
|
});
|
|
479
479
|
|
|
480
|
+
/** One exact route serving several methods on the same path. The host
|
|
481
|
+
webServer's exact table is keyed by pathname only — two separate routes
|
|
482
|
+
on `/tasks` (GET + POST) collide and the second register() throws
|
|
483
|
+
"duplicate route", which the mount silently skips. So a shared path must
|
|
484
|
+
be a single route that dispatches on req.method itself. */
|
|
485
|
+
const makeUnion = (path, byMethod) => {
|
|
486
|
+
const methods = Object.keys(byMethod);
|
|
487
|
+
return {
|
|
488
|
+
kind: "exact",
|
|
489
|
+
path: `${API_PREFIX}${path}`,
|
|
490
|
+
method: methods,
|
|
491
|
+
handler: (req, res) => {
|
|
492
|
+
const m = String(req.method ?? "GET").toUpperCase();
|
|
493
|
+
const inner = byMethod[m];
|
|
494
|
+
if (inner === void 0) {
|
|
495
|
+
writeError(res, 405, "method not allowed");
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
return guard(req, res, m, inner);
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
};
|
|
502
|
+
|
|
480
503
|
return [
|
|
481
504
|
make("GET", "/overview", overview),
|
|
482
505
|
make("GET", "/memories", memories),
|
|
483
|
-
|
|
506
|
+
makeUnion("/tasks", { GET: tasks, POST: createTask }),
|
|
484
507
|
make("GET", "/links", links),
|
|
485
508
|
make("GET", "/nodes", entities),
|
|
486
509
|
make("GET", "/stats", stats),
|
|
@@ -492,7 +515,6 @@ export function makeEngramRoutes(service) {
|
|
|
492
515
|
make("GET", "/config", readConfig),
|
|
493
516
|
make("POST", "/memories/archive", archive),
|
|
494
517
|
make("POST", "/memories/delete", remove),
|
|
495
|
-
make("POST", "/tasks", createTask),
|
|
496
518
|
make("POST", "/tasks/close", closeTask),
|
|
497
519
|
make("POST", "/gc", gcRun),
|
|
498
520
|
];
|
package/lib/client.js
CHANGED
|
@@ -329,6 +329,7 @@ function EngramTaskDock({ sessionId, useSessions, useWorkspaces, useProjection,
|
|
|
329
329
|
const [expandedId, setExpandedId] = (0, import_react2.useState)(null);
|
|
330
330
|
const [taskPage, setTaskPage] = (0, import_react2.useState)(0);
|
|
331
331
|
const [promoted, setPromoted] = (0, import_react2.useState)(/* @__PURE__ */ new Set());
|
|
332
|
+
const [promoteNote, setPromoteNote] = (0, import_react2.useState)(null);
|
|
332
333
|
const [promoteBusy, setPromoteBusy] = (0, import_react2.useState)(false);
|
|
333
334
|
const [toolStats, setToolStats] = (0, import_react2.useState)(null);
|
|
334
335
|
const [closeArtifact, setCloseArtifact] = (0, import_react2.useState)("");
|
|
@@ -456,18 +457,26 @@ function EngramTaskDock({ sessionId, useSessions, useWorkspaces, useProjection,
|
|
|
456
457
|
const promoteAll = async () => {
|
|
457
458
|
if (!effWs || promoteBusy || toPromote.length === 0) return;
|
|
458
459
|
setPromoteBusy(true);
|
|
460
|
+
setPromoteNote(null);
|
|
459
461
|
const done = [];
|
|
462
|
+
const failed = [];
|
|
460
463
|
for (const it of toPromote) {
|
|
461
464
|
try {
|
|
462
465
|
await api.createTask(effWs, it.content, "\u6E90\u81EA\u4F1A\u8BDD\u8BA1\u5212\uFF08todo_write \u4E00\u952E\u6C89\u6DC0\uFF09");
|
|
463
466
|
done.push(it.content);
|
|
464
467
|
} catch {
|
|
468
|
+
failed.push(it.content);
|
|
465
469
|
}
|
|
466
470
|
}
|
|
467
471
|
if (done.length > 0) {
|
|
468
472
|
setPromoted((prev) => /* @__PURE__ */ new Set([...prev, ...done]));
|
|
469
473
|
void load();
|
|
470
474
|
}
|
|
475
|
+
if (failed.length > 0) {
|
|
476
|
+
setPromoteNote(
|
|
477
|
+
failed.length === done.length ? `\u6C89\u6DC0\u5931\u8D25\uFF1A${failed.length} \u9879\u672A\u80FD\u521B\u5EFA\uFF08\u5DE5\u4F5C\u533A\u5DF2\u6EE1\u6216\u670D\u52A1\u4E0D\u53EF\u8FBE\uFF09\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5` : `\u90E8\u5206\u5931\u8D25\uFF1A${failed.length}/${toPromote.length} \u9879\u672A\u80FD\u521B\u5EFA`
|
|
478
|
+
);
|
|
479
|
+
}
|
|
471
480
|
setPromoteBusy(false);
|
|
472
481
|
};
|
|
473
482
|
const submitClose = async (task) => {
|
|
@@ -726,6 +735,10 @@ function EngramTaskDock({ sessionId, useSessions, useWorkspaces, useProjection,
|
|
|
726
735
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "ed-btn", style: { ...btn, ...btnSolid, padding: "3px 9px", fontSize: 11.5 }, disabled: promoteBusy || toPromote.length === 0, onClick: () => void promoteAll(), title: "\u628A\u672C\u8F6E\u5F85\u529E\u8F6C\u6210 ESR \u4EFB\u52A1\u8349\u7A3F\uFF08\u8DE8\u4F1A\u8BDD\u4FDD\u7559\uFF09", children: promoteBusy ? "\u2026" : promoted.size > 0 ? toPromote.length > 0 ? `\u5DF2\u6C89\u6DC0 ${promoted.size} \u9879 \xB7 \u518D\u6C89\u6DC0 ${toPromote.length} \u9879` : `\u5DF2\u6C89\u6DC0 ${promoted.size} \u9879` : `\u6C89\u6DC0\u5230 ESR\uFF08${toPromote.length} \u9879\uFF09` }),
|
|
727
736
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: 10.5, color: "var(--dsw-alias-label-dimmed, var(--dsh-color-muted-weak, #9ca3af))" }, children: "\u8F6C\u8349\u7A3F \xB7 \u8DE8\u4F1A\u8BDD\u95ED\u73AF" })
|
|
728
737
|
] }),
|
|
738
|
+
promoteNote && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 11, color: "#dc2626", lineHeight: 1.4 }, role: "alert", children: [
|
|
739
|
+
"\u26A0 ",
|
|
740
|
+
promoteNote
|
|
741
|
+
] }),
|
|
729
742
|
activeTasks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: 6, alignItems: "flex-start", fontSize: 11.5, lineHeight: 1.5, color: "var(--dsw-alias-label-dimmed, var(--dsh-color-muted, #6b7280))", paddingTop: 2 }, children: [
|
|
730
743
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": true, style: { color: badgeEsr.color }, children: "\u21B3" }),
|
|
731
744
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { children: [
|
|
@@ -3330,10 +3343,11 @@ var ADVANCED_GROUPS = [
|
|
|
3330
3343
|
{
|
|
3331
3344
|
id: "retention-adv",
|
|
3332
3345
|
title: "\u751F\u547D\u5468\u671F\u4E0E GC\uFF08\u9AD8\u7EA7\uFF09",
|
|
3333
|
-
description: "\u5BB9\u91CF\u4E0A\u9650\u4E0E\u5B9A\u671F\u56DE\u6536",
|
|
3346
|
+
description: "\u5BB9\u91CF\u4E0A\u9650\u4E0E\u5B9A\u671F\u56DE\u6536\uFF08\u81EA\u52A8 GC = \u66FF\u4EE3 DSH \u81EA\u52A8 compact\uFF09",
|
|
3334
3347
|
fields: [
|
|
3335
3348
|
{ key: "maxMemoriesPerWorkspace", label: "\u5DE5\u4F5C\u533A\u8BB0\u5FC6\u4E0A\u9650", kind: "num", min: 0, max: 1e4 },
|
|
3336
3349
|
{ key: "gcEnabled", label: "\u8BB0\u5FC6 GC", hint: "\u5B9A\u65F6\u56DE\u6536\uFF08\u8FC7\u671F/\u8D85\u5BB9\u91CF/stable \u8D85\u7A97/\u60AC\u7A7A\u94FE\u63A5\uFF09", kind: "bool" },
|
|
3350
|
+
{ key: "gcNarrative", label: "Context GC \u53D9\u4E8B\u515C\u5E95", hint: "\u81EA\u52A8 compact \u65F6\u65E0\u951A\u8F6E\u6B21\u7528 LLM \u6458\u8981\uFF1B\u5173\u6389=\u7EAF\u673A\u68B0\uFF08\u96F6 LLM\uFF09", kind: "bool" },
|
|
3337
3351
|
{ key: "gcStableRetentionDays", label: "stable \u4EFB\u52A1\u4FDD\u7559\uFF08\u5929\uFF09", hint: "\u8D85\u7A97\u540E\u7531 GC \u5F52\u6863\u3001\u79BB\u5F00 [ESR] \u8868\u9762", kind: "num", min: 0, max: 3650 },
|
|
3338
3352
|
{ key: "verifyArtifact", label: "\u6821\u9A8C artifact \u5B58\u5728", hint: "esr_close \u7684 artifact \u987B\u5728\u5DE5\u4F5C\u533A\u78C1\u76D8\u4E0A\u771F\u5B9E\u5B58\u5728\uFF08URL \u8C41\u514D\uFF09\uFF1Bforce:true/\u5173\u6389\u53EF\u8DF3\u8FC7", kind: "bool" }
|
|
3339
3353
|
]
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-engram — Context GC as a mountable HOST plugin entry (agent-preset row).
|
|
3
|
+
*
|
|
4
|
+
* Web/agent-preset compositions keep compaction on the AGENT plane: the
|
|
5
|
+
* `standard` preset (and friends) mount `@deepseek-ai/dsh-compaction-basic`
|
|
6
|
+
* inside a `compaction` group carrying its own isolate realm. A host-plane row
|
|
7
|
+
* cannot reach that realm, so replacing the default summarizer per-session
|
|
8
|
+
* needs a row that can be listed IN the preset's `compaction` group:
|
|
9
|
+
*
|
|
10
|
+
* ```yaml
|
|
11
|
+
* - id: compaction
|
|
12
|
+
* name: cordis:group
|
|
13
|
+
* group: true
|
|
14
|
+
* isolate:
|
|
15
|
+
* compaction: true
|
|
16
|
+
* toolResultPruner: true
|
|
17
|
+
* config:
|
|
18
|
+
* - id: engram-compaction
|
|
19
|
+
* name: dsh-engram/compaction # this entry
|
|
20
|
+
* - id: command-compact
|
|
21
|
+
* name: '@deepseek-ai/dsh-command-compact'
|
|
22
|
+
* ...
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Config (all optional): `gcReplacesCompaction` (true → Context GC; false →
|
|
26
|
+
* plain BasicCompactionEngine), `gcNarrative`, `gcNarrativeMaxTokens`,
|
|
27
|
+
* `gcNarrativeMaxChars`. Values default to the plugin DEFAULTS.
|
|
28
|
+
*
|
|
29
|
+
* The engine reads engram/ESR state through the storage-domain service shared
|
|
30
|
+
* with the host plane (best-effort reads for pointer classification — the
|
|
31
|
+
* entry never writes).
|
|
32
|
+
*/
|
|
33
|
+
import { openEngramDomain } from "./store.js";
|
|
34
|
+
import { loadCompactionEngine, collectPointerContext } from "./context-gc.js";
|
|
35
|
+
import { workspaceKey as wk } from "./util.js";
|
|
36
|
+
|
|
37
|
+
export const name = "dsh-engram-compaction";
|
|
38
|
+
export const inject = ["llm", "tokenMeter", "sessions"];
|
|
39
|
+
|
|
40
|
+
/** One cached domain per storage-domain service (read-only for compaction). */
|
|
41
|
+
const domains = new WeakMap();
|
|
42
|
+
|
|
43
|
+
async function domainFor(ctx) {
|
|
44
|
+
const storageDomain = ctx.get("storageDomain");
|
|
45
|
+
if (!storageDomain) return null;
|
|
46
|
+
let domain = domains.get(storageDomain);
|
|
47
|
+
if (!domain) {
|
|
48
|
+
domain = await openEngramDomain(storageDomain).catch(() => null);
|
|
49
|
+
if (domain) domains.set(storageDomain, domain);
|
|
50
|
+
}
|
|
51
|
+
return domain;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function apply(ctx, config = {}) {
|
|
55
|
+
const mode = config.gcReplacesCompaction === false ? "default" : "context-gc";
|
|
56
|
+
const deps =
|
|
57
|
+
mode === "context-gc"
|
|
58
|
+
? {
|
|
59
|
+
narrativeEnabled: config.gcNarrative !== false,
|
|
60
|
+
narrativeMaxTokens: config.gcNarrativeMaxTokens ?? 1024,
|
|
61
|
+
narrativeMaxChars: config.gcNarrativeMaxChars ?? 4000,
|
|
62
|
+
readWorkspace: async (workspace, report) => {
|
|
63
|
+
if (!workspace) return null;
|
|
64
|
+
const domain = await domainFor(ctx);
|
|
65
|
+
if (!domain) return null;
|
|
66
|
+
return collectPointerContext(domain, wk(workspace), report);
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
: {};
|
|
70
|
+
|
|
71
|
+
return loadCompactionEngine(ctx, { auto: true }, deps, { mode })
|
|
72
|
+
.then((Engine) => {
|
|
73
|
+
if (!Engine) {
|
|
74
|
+
ctx.logger?.warn?.("engram context-gc: dsh-compaction-basic unavailable — no compaction engine");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
new Engine();
|
|
78
|
+
ctx.logger?.info?.(
|
|
79
|
+
`engram context-gc: compaction = ${mode === "context-gc" ? "Context GC (mechanical eviction + re-fetch pointers)" : "default LLM summarizer (gcReplacesCompaction:false)"}`,
|
|
80
|
+
);
|
|
81
|
+
})
|
|
82
|
+
.catch((error) => {
|
|
83
|
+
ctx.logger?.warn?.(`engram context-gc mount failed: ${String(error)}`);
|
|
84
|
+
});
|
|
85
|
+
}
|