dsh-engram 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -264,8 +264,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
264
264
  <!-- dsh-market compatibility block — keep labels bilingual and in sync with
265
265
  docs/RELEASE-NOTES.TEMPLATE.md (market README convention) -->
266
266
 
267
- - **DSH 版本 / DSH versions**: `>=0.1.2-alpha.2 <0.3.0-0` (本版 `0.7.1`; 已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` — 见下方矩阵)
268
- - **最后验证日期 / Last verified**: 2026-10-19 — web-plane Context GC 适配 DSH `0.2.0` 声明式预设(`dsh-web-app/presets/*.patch.yml`),npm test 396/396 通过
267
+ - **DSH 版本 / DSH versions**: `>=0.1.2-alpha.2 <0.3.0-0` (本版 `0.7.2`; 已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` — 见下方矩阵)
268
+ - **最后验证日期 / Last verified**: 2026-10-20 — DSH `0.2.0` live composition (`dsh-context` + `dshmarket` + `dsh-engram`) arms L1 boundary prune; npm test 406/406
269
269
  - **安装方式已验证 / Verified install**: `dsh plugin --profile web add dsh-engram`
270
270
 
271
271
  > **Short version.** Every **published** release (`0.2.0` … `0.3.6`) supports
@@ -283,7 +283,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
283
283
  | `0.5.1` | published | 同 `0.5.0` — 会话扫描修复(`session.v<N>.jsonl.zstd`,issue #8)、数据导出/导入 GUI、替换/冲突可视化(issue #3) | 同 `0.5.0` |
284
284
  | `0.6.0` | published | 同 `0.5.0` — Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
285
285
  | `0.7.0` | published | 同 `0.5.0` — session episode、resume brief、entity pages/cards、revalidatable anchors + layered TTL | 同 `0.5.0` |
286
- | `0.7.1` (本期 / this release) | prepared | 同 `0.5.0` — web 平面 Context GC 适配 DSH `0.2.0` 声明式预设(`dsh-web-app/presets/*.patch.yml`,14 空格缩进),状态/doctor CLI 同步 | 同 `0.5.0` |
286
+ | `0.7.1` (npm) | published | 同 `0.5.0` — web-plane Context GC on DSH `0.2.0` declarative presets (14-space rows), bundle patch re-enables the host `tool-result-pruner`, compaction savings feed the Context Ledger | 同 `0.5.0` |
287
+ | `0.7.2` (本期 / this release) | prepared | 同 `0.5.0` — L1 boundary prune arms through `ctx.inject`, fixing the boot-ordering race where a pruner activating one tick after the plugin stayed dormant for the whole process | 同 `0.5.0` |
287
288
 
288
289
  ### Where the API family broke — `0.1.2-alpha.2`
289
290
 
@@ -333,7 +334,33 @@ drives mutations).
333
334
  So: keep DSH on `0.1.1-rc.2` or older while running a release ≤ `0.3.6`, and
334
335
  upgrade the plugin to `>=0.3.7` before moving DSH to `0.1.2-alpha.2`.
335
336
 
336
- ### Current release — `0.7.1`
337
+ ### Current release — `0.7.2`
338
+
339
+ **L1 boundary prune was silently dormant on some profiles** — a boot-ordering
340
+ race, not a missing patch.
341
+
342
+ `BoundaryPrune` resolved DSH's `toolResultPruner` with a strict
343
+ `ctx.get("toolResultPruner")` at install time. On DSH `0.2.0` web profiles the
344
+ pruner's fiber activates **~100 ms after** the plugin (measured on the live
345
+ composition `dsh-context` + `dshmarket` + `dsh-engram`: `t0 strict=undefined /
346
+ loose=object`, strict resolves 100 ms later), so the strict lookup was empty,
347
+ `installBoundaryPrune` gave up, and L1 never armed for the life of the process —
348
+ taking the ledger's only prune-credit leg with it. The status snapshot also
349
+ reported that race as "row missing", pointing at the wrong fix.
350
+
351
+ - **Arm through `ctx.inject(["toolResultPruner"], …)`** — cordis runs the
352
+ callback the moment the service becomes active (and again after a reload), so
353
+ arming no longer depends on composition order; the durable status snapshot is
354
+ rewritten as soon as L1 is armed.
355
+ - **"Absent" and "late" now read differently** — `boundaryPruneAvailability`
356
+ falls back to a non-strict lookup: a disabled/missing row still says
357
+ `no toolResultPruner service … (the bundle patch must re-enable …)`, a
358
+ registered-but-inactive one says `registered but its fiber is not active yet`.
359
+ - **Verified** — the live bundle composition (with `dsh-context`) reproduces
360
+ `armed:false` in an isolated `DSH_HOME`; after the fix the same composition
361
+ reports `armed:true · threshold 8192`. `npm test` 406/406.
362
+
363
+ ### Previous release — `0.7.1`
337
364
 
338
365
  Web-plane Context GC on DSH `0.2.0`'s **declarative presets** — the preset
339
366
  format changed shape (one `@deepseek-ai/dsh-agent-preset` declaration inside
package/README.zh.md CHANGED
@@ -166,8 +166,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
166
166
 
167
167
  <!-- dsh-market 兼容信息块 —— 与 docs/RELEASE-NOTES.TEMPLATE.md 的市场 README 公约保持一致 -->
168
168
 
169
- - **DSH 版本**:`>=0.1.2-alpha.2 <0.3.0-0`(本版 `0.7.1`;已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` —— 见下文矩阵)
170
- - **最后验证日期**:2026-10-19 —— web 平面 Context GC 适配 DSH `0.2.0` 声明式预设(`dsh-web-app/presets/*.patch.yml`),npm test 396/396 通过
169
+ - **DSH 版本**:`>=0.1.2-alpha.2 <0.3.0-0`(本版 `0.7.2`;已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` —— 见下文矩阵)
170
+ - **最后验证日期**:2026-10-20 —— DSH `0.2.0` 实机组合(`dsh-context` + `dshmarket` + `dsh-engram`)验证 L1 边界剪枝武装;npm test 406/406 通过
171
171
  - **安装方式已验证**:`dsh plugin --profile web add dsh-engram`
172
172
 
173
173
  > **一句话结论**:所有**已真正发布**的版本(`0.2.0` … `0.3.6`)**只支持旧版
@@ -183,7 +183,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
183
183
  | `0.5.1` | 已发布 | 同 `0.5.0` —— 会话扫描修复(`session.v<N>.jsonl.zstd`,issue #8)、数据导出/导入 GUI、替换/冲突可视化(issue #3) | 同 `0.5.0` |
184
184
  | `0.6.0` | 已发布 | 同 `0.5.0` —— Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
185
185
  | `0.7.0` | 已发布 | 同 `0.5.0` —— 会话 episode、恢复简报、实体页/实体卡、可重验证锚点 + 分层 TTL | 同 `0.5.0` |
186
- | `0.7.1`(本期 / this release) | 待发布 | 同 `0.5.0` —— web 平面 Context GC 适配 DSH `0.2.0` 声明式预设(`dsh-web-app/presets/*.patch.yml`,14 空格缩进),status/doctor CLI 同步 | 同 `0.5.0` |
186
+ | `0.7.1`(npm) | 已发布 | 同 `0.5.0` —— web 平面 Context GC 适配 DSH `0.2.0` 声明式预设(14 空格缩进)、bundle patch 重新启用 host 平面 `tool-result-pruner`、压缩节省接入 Context Ledger | 同 `0.5.0` |
187
+ | `0.7.2`(本期 / this release) | 待发布 | 同 `0.5.0` —— L1 边界剪枝改经 `ctx.inject` 武装,修掉「pruner 行比插件晚激活一帧就永久休眠」的启动时序竞态 | 同 `0.5.0` |
187
188
 
188
189
  ### API 族断裂点:`0.1.2-alpha.2`
189
190
 
@@ -223,7 +224,25 @@ profile 条目 id(`engram`)为命名空间,不再需要任何“挂载”
223
224
  所以:跑 ≤ `0.3.6` 的已发布版本时请把 DSH 保持在 `0.1.1-rc.2` 或更旧;升级
224
225
  DSH 到 `0.1.2-alpha.2` 之前,先升级插件到 `>=0.3.7`。
225
226
 
226
- ### 当前发布版本 —— `0.7.1`
227
+ ### 当前发布版本 —— `0.7.2`
228
+
229
+ 修复 **L1 边界剪枝的启动时序竞态**(真机定位:组合里多挂两个 bundle 就复现)。
230
+
231
+ `BoundaryPrune` 在插件安装时用严格 `ctx.get("toolResultPruner")` 取宿主剪枝器;而 `0.2.0`
232
+ 的 web profile 里,剪枝器的 fiber 比插件**晚激活约 100 ms**(实测:`dsh-context` +
233
+ `dshmarket` + `dsh-engram` 组合下 `t0 strict=undefined / loose=object`,100 ms 后 strict 才
234
+ 拿到服务)。严格查找此时为空 → `installBoundaryPrune` 放弃 → **L1 整个进程生命周期都不武装**,
235
+ 账本唯一的「剪枝节省」入账腿静默断掉;设备快照还会把这种"还没到"报成"行不存在",把人引向错误的排查方向。
236
+
237
+ - **改经 `ctx.inject(["toolResultPruner"], …)` 武装** — cordis 在服务真正激活那一刻回调
238
+ (重载后也会重新武装),不再赌安装顺序;武装成功后立刻重写状态快照。
239
+ - **区分「缺失」与「晚到」** — `boundaryPruneAvailability` 增加非严格查找:行被禁用/没装
240
+ 报 `no toolResultPruner service … (the bundle patch must re-enable …)`,只是 fiber 未激活
241
+ 报 `registered but its fiber is not active yet`。两种原因以前在快照里长得一模一样。
242
+ - **验证** — 用真实 profile 的 bundle 组合(含 `dsh-context`)在隔离 `DSH_HOME` 复现
243
+ `armed:false`,修复后同一组合 `armed:true · threshold 8192`;`npm test` 406/406。
244
+
245
+ ### 上一发布版本 —— `0.7.1`
227
246
 
228
247
  修复 **DSH `0.2.0` 声明式预设下的 web 平面 Context GC 接管**:旧版自动装配以
229
248
  `agent.cordis.yml` 的 4 空格布局为锚,而 `0.2.0` 把预设换成
@@ -195,7 +195,9 @@ Fetch the exact records that are still in the store:
195
195
  | 日志 `dsh-compaction-basic unavailable` | 环境缺后端依赖 | 安装依赖;自动回退默认压缩,host 不崩 |
196
196
  | 极小被驱逐片段(span 小于 checkpoint 开销)不压缩 | harness 收缩闸门对**任何**后端一视同仁 | 属正常,没必要为几十 token 起 checkpoint |
197
197
  | 改配置后没变化 | web 行要重启才刷新 | 重启 dsh web |
198
- | 遥测「节省 · 剪枝/压缩」一直是 0、净 token 恒负 | 两条入账腿都没触发:L1 在 0.2.0 web profile 下拿不到 host 平面的 `toolResultPruner`;压缩节省此前进不了账本 | 确认状态快照 `boundaryPrune.armed: true`(`npx dsh-engram status` 会打印);升级到 0.7.1+ 后压缩会按 `区间字符数 − checkpoint 字符数` 入账 |
198
+ | 遥测「节省 · 剪枝/压缩」一直是 0、净 token 恒负 | 两条入账腿都没触发:L1 拿不到 host 平面的 `toolResultPruner`(0.2.0 的 host 行被禁),压缩节省此前进不了账本 | 确认状态快照 `boundaryPrune.armed: true`(`npx dsh-engram status` 会打印);压缩在 0.7.1+ 按 `区间字符数 − checkpoint 字符数` 入账 |
199
+ | L1 状态报 `no toolResultPruner service …` 但 `--dump-config` 里行是启用的 | 行存在却被**运行时**关掉(如 `- id: tool-result-pruner, disabled: true` 的 profile override) | 删掉该 override;bundle patch 只负责重新启用出厂行 |
200
+ | L1 状态报 `registered but its fiber is not active yet` | 启动时序:剪枝器 fiber 比插件晚激活(v0.7.2 前会因此永久休眠) | 升到 0.7.2+(改经 `ctx.inject` 武装),然后重启 |
199
201
 
200
202
  ---
201
203
 
@@ -0,0 +1,48 @@
1
+ ## dsh-engram v0.7.2
2
+
3
+ 自 v0.7.1 以来:**修复 L1 边界剪枝的启动时序竞态**——此前在部分 profile 上 L1 整个进程
4
+ 生命周期都不武装,账本的「剪枝节省」入账腿静默断掉,而状态快照把它报成"行不存在"。
5
+
6
+ ### 根因(真机定位)
7
+
8
+ `BoundaryPrune` 在插件安装时用**严格** `ctx.get("toolResultPruner")` 取宿主剪枝器,拿不到
9
+ 就 `log.warn` 后放弃。但 DSH `0.2.0` web profile 里 `tool-result-pruner` 行虽然由 bundle patch
10
+ 重新启用,它的 fiber 却比插件**晚激活约 100 ms**:
11
+
12
+ ```
13
+ t0 strict=undefined loose=object ← 服务已注册,fiber 未激活
14
+ inject 100 ms 后 strict=object ← 服务激活
15
+ ```
16
+
17
+ 严格查找在 `t0` 为空 → `installBoundaryPrune` 直接返回 noop → **L1 永久休眠**(本次问题
18
+ 只在组合里多挂 bundle 时出现:最小 profile 里剪枝器恰好先激活,所以此前的真机验证是绿的)。
19
+
20
+ ### 修复
21
+
22
+ - **改经 `ctx.inject(["toolResultPruner"], callback)` 武装**:cordis 在服务真正激活那一刻
23
+ 回调(插件重载后也会重新武装),武装顺序不再由 bundle 组合顺序决定;武装成功后立刻
24
+ 重写持久化状态快照(首刷可能早于剪枝器激活)。
25
+ - **区分「缺失」与「晚到」**:`boundaryPruneAvailability` 增加非严格查找——
26
+ - 行被禁用/没装:`no toolResultPruner service in the host plane (the bundle patch must re-enable the tool-result-pruner row)`
27
+ - 只是 fiber 未激活:`toolResultPruner is registered but its fiber is not active yet (L1 arms when it activates)`
28
+ - **CLI 同步**:`npx dsh-engram status` 的 L1 行直接复用该 reason,未武装时打印「节省不会入账」告警。
29
+
30
+ ### 验证
31
+
32
+ - 把真实 web profile(`dsh-context` + `dshmarket` + `dsh-engram`)克隆到隔离 `DSH_HOME`
33
+ 复现 `armed:false`;换成修复后的代码,同一组合 `armed:true · threshold 8192`。
34
+ `dshmarket` 组合对照组一直 `armed:true`(说明差异来自激活顺序,不是组合内容)。
35
+ - 单测新增「registered-but-inactive 报 pending 而非 missing」一例;`npm test` 406/406,
36
+ `npm run typecheck` 干净。
37
+
38
+ ### 升级步骤
39
+
40
+ ```bash
41
+ npm publish # 或先 npm pack 检查产物
42
+ dsh plugin --profile web add dsh-engram@0.7.2
43
+ # 重启 dsh web 后确认:
44
+ npx dsh-engram status | head -6 # 期望「L1 边界剪枝:已武装(threshold 8192)」
45
+ ```
46
+
47
+ > 升级不会回填历史账本:`avoidedChars` 从 0 开始累计,第一次边界剪枝/checkpoint 之后
48
+ > 「节省 · 剪枝/压缩」才会出现正数。
@@ -138,6 +138,12 @@ export function expectedSessionRequests(requestsSoFar, history, workspace, floor
138
138
  * plugin cannot see it. The shipped bundle patch re-enables the host row; when
139
139
  * a custom profile keeps it off, this reports why L1 stays dormant.
140
140
  *
141
+ * A non-strict lookup separates two failure modes that look identical through a
142
+ * strict `ctx.get`: the row is **absent** (patch missing / disabled) versus
143
+ * **registered but not yet active** (the pruner's fiber activates a tick or two
144
+ * after this plugin on a full profile — L1 arms through `ctx.inject`, so this
145
+ * state resolves itself, while the absent row never will).
146
+ *
141
147
  * @param {object} ctx - the host plugin context.
142
148
  * @param {object} config - resolved engram config.
143
149
  * @returns {{ armed: boolean, reason: string }}
@@ -146,6 +152,13 @@ export function boundaryPruneAvailability(ctx, config) {
146
152
  if (config?.boundaryPrune === false) return { armed: false, reason: "disabled by config (boundaryPrune:false)" };
147
153
  const pruner = ctx?.get?.("toolResultPruner");
148
154
  if (!pruner || typeof pruner.pruneSession !== "function") {
155
+ const registered = ctx?.get?.("toolResultPruner", false);
156
+ if (registered !== undefined && registered !== null) {
157
+ return {
158
+ armed: false,
159
+ reason: "toolResultPruner is registered but its fiber is not active yet (L1 arms when it activates)",
160
+ };
161
+ }
149
162
  return {
150
163
  armed: false,
151
164
  reason: "no toolResultPruner service in the host plane (the bundle patch must re-enable the tool-result-pruner row)",
package/lib/index.js CHANGED
@@ -1019,7 +1019,7 @@ export function apply(ctx, config) {
1019
1019
  // over-threshold tool results at todo/goal subtask boundaries through
1020
1020
  // DSH's own deterministic toolResultPruner, gated by a repayment clock.
1021
1021
  const l2Dir = resultPackDir();
1022
- const disposeBoundaryPrune = installBoundaryPrune(ctx, resolved, log, {
1022
+ const boundaryHooks = {
1023
1023
  // ResultPack L2 hook: archive the pruned middles (redacted) + append
1024
1024
  // a handle tail on each pruned node. Fail-contained in result-pack.js.
1025
1025
  // Also records durable activation telemetry (SoL-Pi M15/C13) —
@@ -1051,11 +1051,28 @@ export function apply(ctx, config) {
1051
1051
  log,
1052
1052
  });
1053
1053
  },
1054
- }, { allowFire: boundaryGate });
1055
- disposers.push(disposeBoundaryPrune);
1056
- // BoundaryPrune is installed here, so this is the moment L1 provably can
1057
- // (or cannot) fire — record it for the durable status snapshot.
1058
- boundaryPruneState = boundaryPruneAvailability(ctx, resolved);
1054
+ };
1055
+ // Arm L1 through `ctx.inject`, never a bare `ctx.get`: on a full web
1056
+ // profile the host pruner's fiber activates a tick or two after this
1057
+ // plugin (measured ~100 ms with dsh-context + dshmarket mounted), and a
1058
+ // strict lookup at install time is then empty — the old direct install
1059
+ // gave up and L1 stayed dormant for the whole process, silently killing
1060
+ // the only ledger credit path. `ctx.inject` runs the callback as soon as
1061
+ // the service is active (and re-runs it after a reload).
1062
+ if (resolved.boundaryPrune !== false) {
1063
+ ctx.inject(["toolResultPruner"], (prunerCtx) => {
1064
+ const dispose = installBoundaryPrune(prunerCtx, resolved, log, boundaryHooks, {
1065
+ allowFire: boundaryGate,
1066
+ });
1067
+ // L1 provably can (or cannot) fire from here on: record it for the
1068
+ // durable snapshot, then rewrite it — the first flush may have landed
1069
+ // before the pruner activated, and this status line is what users
1070
+ // read when "节省" stays at 0.
1071
+ boundaryPruneState = boundaryPruneAvailability(prunerCtx, resolved);
1072
+ flushStatus();
1073
+ return dispose;
1074
+ });
1075
+ }
1059
1076
 
1060
1077
  // ResultPack L2 tool (default ON with L1 since v0.6): `result_recall(callId,
1061
1078
  // offset?)` pages back an archived middle. Independent of the memory
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-engram",
3
3
  "description": "Minimalist long-term memory for DeepSeek Harness, distilling symbolic-index + pi-esr ideas: zero-LLM auto-capture, a symbolic [ENGRAM] index with progressive disclosure, and an ESR-lite evidence-closure protocol (esr_task / esr_node / esr_close / esr_link). Real agent-behaviour telemetry (usage rollup + /stats) and a deterministic offline recall benchmark (npm run eval). Hot path is model-free; storage on ctx.storageDomain; web memory viewer, observability panel and config card through DSH's native settings slots.",
4
- "version": "0.7.1",
4
+ "version": "0.7.2",
5
5
  "packageManager": "pnpm@11.22.0",
6
6
  "license": "MIT",
7
7
  "repository": {