dsh-engram 0.6.0 → 0.7.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/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.6.0`; 已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` — 见下方矩阵)
268
- - **最后验证日期 / Last verified**: 2026-10-19 — Context Ledger / 默认值纠偏 / JIT 推送,npm test 359/359 通过
267
+ - **DSH 版本 / DSH versions**: `>=0.1.2-alpha.2 <0.3.0-0` (本版 `0.7.0`; 已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` — 见下方矩阵)
268
+ - **最后验证日期 / Last verified**: 2026-10-19 — episode / entity pages / anchor revalidation,npm test 381/381 通过
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
@@ -281,7 +281,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
281
281
  | `0.3.7` / `0.4.0` (npm) | published | **new DSH, settings `installSection`** — `>=0.1.2-alpha.2 <0.2.0-0` | `engines.dsh` + peers `>=0.1.2-alpha.2 <0.2.0-0` |
282
282
  | `0.5.0` (npm) | published | **DSH `0.1.2-alpha.2` … `<0.3.0-0`** — 设置卡迁移到 entry-id volatile Config(`0.1.7+`/`0.2.x`),`≤0.1.5` 自动回退 `installSection` | `engines.dsh` + peers `>=0.1.2-alpha.2 <0.3.0-0` |
283
283
  | `0.5.1` | published | 同 `0.5.0` — 会话扫描修复(`session.v<N>.jsonl.zstd`,issue #8)、数据导出/导入 GUI、替换/冲突可视化(issue #3) | 同 `0.5.0` |
284
- | `0.6.0` (本期 / this release) | published | 同 `0.5.0` — Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
284
+ | `0.6.0` | published | 同 `0.5.0` — Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
285
+ | `0.7.0` (本期 / this release) | published | 同 `0.5.0` — session episode、resume brief、entity pages/cards、revalidatable anchors + layered TTL | 同 `0.5.0` |
285
286
 
286
287
  ### Where the API family broke — `0.1.2-alpha.2`
287
288
 
@@ -315,7 +316,7 @@ the loader), and the settings form is keyed by the profile entry id (`engram`),
315
316
  so nothing needs "mounting". `0.5.0` feature-detects the two generations in
316
317
  `installEngramSettings`:
317
318
 
318
- - DSH `≥0.1.7` / `0.2.x` — the 31 GUI knobs are the volatile `Config` fields
319
+ - DSH `≥0.1.7` / `0.2.x` — the 34 GUI knobs are the volatile `Config` fields
319
320
  (`lib/settings.js` `makeEngineConfigSchema`, exported as `Config` from
320
321
  `lib/index.js`); the card reads/writes entry `engram` through the same
321
322
  `/api/dsh-engram/settings` HTTP route, and the `service.settings` seam
@@ -331,27 +332,29 @@ drives mutations).
331
332
  So: keep DSH on `0.1.1-rc.2` or older while running a release ≤ `0.3.6`, and
332
333
  upgrade the plugin to `>=0.3.7` before moving DSH to `0.1.2-alpha.2`.
333
334
 
334
- ### Current release — `0.6.0`
335
-
336
- From a "memory plugin" to a **context portfolio**: make token savings auditable
337
- first, then use the ledger to correct defaults, veto unprofitable prunes and
338
- push memory at deterministic moments.
339
-
340
- - **Context Ledger** — per session / workspace / day
341
- `net = avoided − injected − rewrite penalty`; exposed on `/stats` and
342
- `/overview`, rendered on the telemetry page, persisted to
343
- `~/.dsh/engram/ledger.json`.
344
- - **Default correction** — `boundaryPrune` / `resultPack` default ON (pruning is
345
- lossless via the archive); `gcReplacesCompaction` / `autoWebCompaction` are
346
- now opt-in (no preset rewriting by default).
347
- - **Ledger veto** — `boundaryPruneAdaptive` stops pruning a workspace whose own
348
- accounting is net negative after ≥3 real fires (fail-open).
349
- - **JIT push** — error recurrence / file focus / goal completion, append-only,
350
- ≤3 lines per session, silent without a hit; `[JIT]` section + `jitEnabled`.
351
- - **Migration** — restart `dsh web` after upgrading; if you still want Context
352
- GC takeover, set `gcReplacesCompaction:true` (plus `autoWebCompaction:true`
353
- for web presets). The `0.5.1` versioned session-log fix (issue #8) and data
354
- backup GUI (issue #3) are unchanged.
335
+ ### Current release — `0.7.0`
336
+
337
+ From event records to **maintainable current truth**: sessions sediment an
338
+ episode at teardown, new sessions get a resume brief, `esr_node` becomes a
339
+ readable page, and file anchors make stale truth detectable.
340
+
341
+ - **Session episode** — zero-LLM digest at `session/disposed` (files / commands /
342
+ failures→fixed / decisions / open loops), never indexed, newest 20 per
343
+ workspace kept.
344
+ - **Resume brief** — the newest episode from the last 7 days is injected as a
345
+ `[RESUME]` line (≤240 chars + drill pointer) at session start.
346
+ - **Entity pages + cards** — `truth / timeline / open loops / conflicts /
347
+ relations`; up to 2 one-line cards in the index, full page via
348
+ `engram_detail(entity=…)`.
349
+ - **Revalidatable anchors** — file-backed memories record `mtime/size/sha256`;
350
+ a bounded revalidation at session start tags changed/missing sources `stale`
351
+ (dropped from index/truth, still re-fetchable).
352
+ - **Layered TTL** — preference/procedure permanent, episode 14d, error 60d,
353
+ others 180d.
354
+ - **Migration** — only new writes are anchored (no historical backfill);
355
+ restart `dsh web` after upgrading; an episode appears after one session ends,
356
+ the resume brief on the next new session. The `0.6.0` ledger / defaults / JIT
357
+ behaviour is unchanged.
355
358
 
356
359
  The published `0.4.0` declared `>=0.1.2-alpha.2 <0.2.0-0` — which the semver
357
360
  gate **rejects on every DSH `0.2.0-0` prerelease** (`<0.2.0-0` excludes
@@ -372,7 +375,7 @@ feature-detected legacy fallback).
372
375
  `0.1.2-alpha.2` is the minimum version for this API family: host settings are
373
376
  **feature-detected** — on DSH `≤0.1.5` they register the `dsh-engram`
374
377
  namespace via `settings.installSection`; on `≥0.1.7` / `0.2.x` the form is the
375
- plugin's `Config` with the 31 GUI knobs marked `.volatile()` (entry id
378
+ plugin's `Config` with the 34 GUI knobs marked `.volatile()` (entry id
376
379
  `engram`). The browser settings card reads and writes through the plugin's own
377
380
  HTTP route (`/api/dsh-engram/settings`, see Route B above) on both. The full
378
381
  regression suite (npm test 329/329, incl. the new settings-v2 coverage for the
@@ -898,6 +901,60 @@ and silence when there is nothing relevant to point at:
898
901
  remaining-requests ROI gate — today the 3-line budget plus the ledger are the
899
902
  guardrails.
900
903
 
904
+ ### Session episode + resume brief (v0.7-A/D)
905
+
906
+ - **episode** — at `session/disposed`, a zero-LLM digest of the event log: files
907
+ touched, commands run, failures that later recovered (`cmd×N→fixed`), written
908
+ commit subjects, and still-open todos. Written only when there is substance (a
909
+ bare `ls` session produces none); **never indexed**; the newest 20 per
910
+ workspace are kept and older ones archived.
911
+ - **resume brief** — at the first assembly of a new session, the newest episode
912
+ from the last 7 days is injected as a `[RESUME]` line (≤240 chars) plus an
913
+ `engram_detail <id>` pointer, frozen with `[ENGRAM]`/`[RECALL]`. No recent
914
+ episode → no line; returning to a project does not need a re-briefing.
915
+ - Settings: `episodeEnabled`, `resumeBriefEnabled` (both default on), plus
916
+ `episodeMaxPerWorkspace` / `resumeBriefMaxAgeDays` / `resumeBriefMaxChars`.
917
+
918
+ ### Entity pages + entity cards (v0.7-B)
919
+
920
+ `esr_node` grows from a dot on the graph into a readable **page**: current
921
+ truth, timeline, open loops, conflicts and relations for one thing
922
+ (package / repo / service / bug…).
923
+
924
+ - **Entity card (in the index, default on)** — `[ENGRAM]` renders ONE line for
925
+ each active entity (anchored memories or open tasks), at most 2:
926
+ `[ENT] dsh-engram · package · 3 mem · 1 open · truth: … #id · page: engram_detail(entity="dsh-engram")`
927
+ Those entities' flat memory lines are not repeated; other entities stay in a
928
+ single `nodes:` line.
929
+ - **Full page (on drill)** — `engram_detail(entity="<name|id>")` returns
930
+ `truth / timeline / open loops / conflicts / relations`, with memory ids and
931
+ evidence gaps. A name with anchored memories but no registered node still
932
+ gets a virtual page.
933
+ - Pure, deterministic: preference leads `truth`, superseded rows never enter
934
+ it, conflicts are de-duplicated by unordered pair (`#a vs #b`).
935
+ - Settings `entityCardsEnabled` (default on); `entityCardsMax` /
936
+ `entityCardChars` tunable.
937
+
938
+ ### Revalidatable anchors + layered TTL (v0.7-C)
939
+
940
+ "Treating the past as truth" is the strongest credibility critique against
941
+ memory plugins. v0.7 gives file-backed memories an **anchor**:
942
+
943
+ - on write, record `{path, mtimeMs, size, hash?}` (sha256 for files ≤256KB,
944
+ stat only for larger ones) in the memory row; relative paths resolve against
945
+ the workspace;
946
+ - at session start a bounded revalidation (≤40 rows) tags changed/missing
947
+ sources `stale`; the `[ENGRAM]` index and entity `truth` stop showing them,
948
+ but nothing is deleted — `engram_detail` still returns them and the entity
949
+ timeline marks `· stale`;
950
+ - **layered TTL**: `preference` / `procedure` permanent; `episode` 14 days;
951
+ `error` 60 days; everything else `expireDays` (180). Hard deletes still do not
952
+ exist — GC only archives.
953
+ - Settings: `anchorRevalidate` (default on), `episodeTtlDays`, `errorTtlDays`.
954
+ - **Not done**: the offline consolidation job (episode abstraction/merge) —
955
+ deterministic rules can only roll up, not abstract semantically; deferred
956
+ until v0.8 alongside playbooks, rather than shipping a lossy pseudo-merge.
957
+
901
958
  ## Auto-capture policy
902
959
 
903
960
  Capture is deterministic and offline — it only sees tool *results*, never the
@@ -1011,6 +1068,11 @@ Defaults are token-conscious; override any key via the profile patch
1011
1068
  gcNarrative: true # scoped LLM narrative for un-provenanced turns; false = fully mechanical (zero LLM)
1012
1069
  autoWebCompaction: false # rewrite stock web presets (default off; explicit opt-in, revert with the CLI)
1013
1070
  jitEnabled: true # JIT push: error recurrence / file focus / goal completion → one memory line
1071
+ episodeEnabled: true # session episode: zero-LLM digest at teardown (never indexed)
1072
+ resumeBriefEnabled: true # resume brief: newest episode from the last 7 days at session start
1073
+ entityCardsEnabled: true # entity card: one truth/tasks/drill line per active entity in the index
1074
+ anchorRevalidate: true # re-stat filePath anchors at session start (changed/missing → stale)
1075
+ episodeTtlDays: 14 # episode TTL (preference/procedure permanent; error 60d; others expireDays)
1014
1076
  ledgerEnabled: true # Context Ledger: injected cost / prune savings / rewrite penalty → net tokens
1015
1077
  ledgerCacheDiscount: 0.1 # cached-prefix price share; rewrites pay (1−discount)
1016
1078
  engramIndexOrder: 40 # systemPrompt section order (before tools band)
@@ -1020,7 +1082,7 @@ Defaults are token-conscious; override any key via the profile patch
1020
1082
  ## Development
1021
1083
 
1022
1084
  ```sh
1023
- npm test # 359 tests: core + web API + GC + usage-observability + Context GC + ESR triggers + BoundaryPrune + preference learning + Context Ledger + JIT push (node:test)
1085
+ npm test # 381 tests: core + web API + GC + usage-observability + Context GC + ESR triggers + BoundaryPrune + preference learning + Context Ledger + JIT push (node:test)
1024
1086
  npm run eval # offline recall + structure benchmark (deterministic)
1025
1087
  npm run build:client
1026
1088
  ```
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.6.0`;已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` —— 见下文矩阵)
170
- - **最后验证日期**:2026-10-19 —— Context Ledger / 默认值纠偏 / JIT 推送,npm test 359/359 通过
169
+ - **DSH 版本**:`>=0.1.2-alpha.2 <0.3.0-0`(本版 `0.7.0`;已发布的 `0.3.7`、`0.4.0` 仍声明旧的 `0.1.x` 范围 `>=0.1.2-alpha.2 <0.2.0-0` —— 见下文矩阵)
170
+ - **最后验证日期**:2026-10-19 —— episode / 实体页 / 锚点重验证,npm test 381/381 通过
171
171
  - **安装方式已验证**:`dsh plugin --profile web add dsh-engram`
172
172
 
173
173
  > **一句话结论**:所有**已真正发布**的版本(`0.2.0` … `0.3.6`)**只支持旧版
@@ -181,7 +181,8 @@ curl -s -X POST http://127.0.0.1:3080/api/dsh-engram/import \
181
181
  | `0.3.7` / `0.4.0`(npm) | 已发布 | **新版 DSH,settings `installSection`** —— `>=0.1.2-alpha.2 <0.2.0-0` | `engines.dsh` + peers 同范围 |
182
182
  | `0.5.0`(npm) | 已发布 | **DSH `0.1.2-alpha.2` … `<0.3.0-0`** —— 设置卡迁移到 entry-id volatile Config(`0.1.7+`/`0.2.x`),`≤0.1.5` 自动回退 `installSection` | `engines.dsh` + peers `>=0.1.2-alpha.2 <0.3.0-0` |
183
183
  | `0.5.1` | 已发布 | 同 `0.5.0` —— 会话扫描修复(`session.v<N>.jsonl.zstd`,issue #8)、数据导出/导入 GUI、替换/冲突可视化(issue #3) | 同 `0.5.0` |
184
- | `0.6.0`(本期 / this release) | 已发布 | 同 `0.5.0` —— Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
184
+ | `0.6.0` | 已发布 | 同 `0.5.0` —— Context Ledger、BoundaryPrune/ResultPack 默认开 + 账本否决、JIT 推送、Context GC 改 opt-in | 同 `0.5.0` |
185
+ | `0.7.0`(本期 / this release) | 已发布 | 同 `0.5.0` —— 会话 episode、恢复简报、实体页/实体卡、可重验证锚点 + 分层 TTL | 同 `0.5.0` |
185
186
 
186
187
  ### API 族断裂点:`0.1.2-alpha.2`
187
188
 
@@ -208,7 +209,7 @@ HTTP 路由(`/api/dsh-engram/settings`,与记忆 viewer 同样的同源 fetc
208
209
  profile 条目 id(`engram`)为命名空间,不再需要任何“挂载”调用。`0.5.0`
209
210
  在 `installEngramSettings` 里对两代际做特性检测:
210
211
 
211
- - DSH `≥0.1.7` / `0.2.x` —— 31 个 GUI 旋钮即 `Config` 的 volatile 字段
212
+ - DSH `≥0.1.7` / `0.2.x` —— 34 个 GUI 旋钮即 `Config` 的 volatile 字段
212
213
  (`lib/settings.js` `makeEngineConfigSchema`,由 `lib/index.js` 导出为
213
214
  `Config`);卡片仍走同一 `/api/dsh-engram/settings` HTTP 路由读写条目 `engram`,
214
215
  `service.settings` 接缝在 `loader/volatile-update` 事件上把 `resolved`
@@ -221,23 +222,21 @@ profile 条目 id(`engram`)为命名空间,不再需要任何“挂载”
221
222
  所以:跑 ≤ `0.3.6` 的已发布版本时请把 DSH 保持在 `0.1.1-rc.2` 或更旧;升级
222
223
  DSH 到 `0.1.2-alpha.2` 之前,先升级插件到 `>=0.3.7`。
223
224
 
224
- ### 当前发布版本 —— `0.6.0`
225
+ ### 当前发布版本 —— `0.7.0`
225
226
 
226
- 从「记忆插件」转向**上下文投资组合**:先把省 token 变成可审计的账本,再用账本
227
- 纠正默认值、否决不划算的剪枝,并按确定性事件把记忆推给模型。
227
+ 把「记忆」从**事件记录**推进到**可维护的当前真相**:会话结束自动沉淀 episode、
228
+ 新会话给恢复简报、`esr_node` 升级为实体页、文件锚点让过期真相可被检测。
228
229
 
229
- - **Context Ledger**:每会话/工作区/天记录 `net = 节省 − 注入 − 重写罚金`,
230
- `/stats` 与 `/overview` 返回,GUI 遥测页有面板;落盘 `~/.dsh/engram/ledger.json`。
231
- - **默认值纠偏**:`boundaryPrune` / `resultPack` 默认开(剪枝由归档兜底),
232
- `gcReplacesCompaction` / `autoWebCompaction` 改 opt-in(不再主动改写宿主预设)。
233
- - **账本否决**:`boundaryPruneAdaptive` 在某 workspace 净收益为负且 ≥3 次剪枝时
234
- 自动停剪(fail-open)。
235
- - **JIT 推送**:错误复发 / 文件焦点 / goal 完成三类事件,append-only、每会话 ≤3 行、
236
- 无命中沉默;`[JIT]` section + `jitEnabled`。
237
- - **迁移**:升级后重启 `dsh web`;若仍要 Context GC 接管,显式打开
238
- `gcReplacesCompaction:true`(web 还需 `autoWebCompaction:true`)。
239
- - `0.5.1` 的会话日志版本化修复(`session.v<N>.jsonl.zstd`,issue #8)与数据备份
240
- GUI(issue #3)保持不变。
230
+ - **会话 episode**:`session/disposed` 零 LLM 生成紧凑纪要(文件/命令/失败→修复/
231
+ 决策/未闭环),不进索引,每 workspace 保留 20 条。
232
+ - **恢复简报**:新会话注入最近 7 天最新 episode(`[RESUME]` ≤240 字符 + 下钻指针)。
233
+ - **实体页 + 实体卡**:`truth / timeline / open loops / conflicts / relations`;
234
+ 索引前置最多 2 张实体卡,`engram_detail(entity=…)` 下钻。
235
+ - **可重验证锚点**:带 `filePath` 的记忆记录 `mtime/size/sha256`;会话启动有界
236
+ 重验证,改动/缺失 → `stale`(不进索引/truth,仍可 detail 重取)。
237
+ - **分层 TTL**:preference/procedure 永久、episode 14 天、error 60 天、其余 180 天。
238
+ - **迁移**:只有新写入会落锚(不回溯历史);升级后重启 `dsh web`;episode 要等一个
239
+ 会话结束,resume brief 在下一个新会话生效。`0.6.0` 的账本/默认值/JIT 保持不变。
241
240
 
242
241
  已发布的 `0.4.0` 声明 `>=0.1.2-alpha.2 <0.2.0-0`——这个范围被 semver 门槛在
243
242
  **所有 DSH `0.2.0-0` 预发布**上拒绝(`<0.2.0-0` 排除 `0.2.0-rc.x`),且其宿主设置端
@@ -255,13 +254,13 @@ Config** 模型(并带特性检测的旧代际回退)。
255
254
 
256
255
  `0.1.2-alpha.2` 是这组 API 的最低版本:宿主设置注册按代际**特性检测**——DSH `≤0.1.5`
257
256
  走 `settings.installSection` 注册 `dsh-engram` 命名空间;`≥0.1.7` / `0.2.x` 走插件
258
- `Config`(31 个 GUI 旋钮标记为 `.volatile()`,条目 id `engram`)。浏览器设置卡在两代际
257
+ `Config`(34 个 GUI 旋钮标记为 `.volatile()`,条目 id `engram`)。浏览器设置卡在两代际
259
258
  都经插件自有 HTTP 路由(`/api/dsh-engram/settings`,见上文 Route B)读写。完整回归
260
259
  (npm test 329/329,含新增的 volatile-Config 模型 settings-v2 覆盖)在 DSH
261
260
  `0.2.0-rc.2` 依赖集(cordis 4.0.4 + schemastery 3.18.4 + cosmokit,与全局 DSH CLI 同版本)
262
261
  上通过;`.volatile()`/loader 热提交路径与 describe 侧投影已针对真实 `dsh-settings
263
262
  0.2.0-rc.2` 工具函数验证;并在隔离的 `dsh 0.2.0-rc.2` profile 上完成了**真机启动烟测并
264
- 通过**:插件启动无版本范围拒绝、设置卡表单以条目 `engram` 对外提供(全部 31 个 GUI
263
+ 通过**:插件启动无版本范围拒绝、设置卡表单以条目 `engram` 对外提供(全部 34 个 GUI
265
264
  旋钮,含 `trustedHosts`)、`/api/dsh-engram/settings` 的 GET → PUT(mutate ops,
266
265
  revision 0→1)→ unset 往返落盘并读回。旧的 `0.1.0-rc.7` 与 `0.1.1` API 图不在
267
266
  本发布版本的兼容范围内。
@@ -392,7 +391,7 @@ npm run build:client
392
391
  ## 测试与评测
393
392
 
394
393
  ```sh
395
- npm test # 359 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发 / BoundaryPrune / Context Ledger / JIT 推送)
394
+ npm test # 381 项单元测试(含 usage 滚动 / /stats 路由 / Context GC / ESR 触发 / BoundaryPrune / Context Ledger / JIT 推送)
396
395
  npm run eval # 离线召回 + 结构基准(确定性语料,跑真实 store/recall 路径)
397
396
  ```
398
397
 
@@ -664,6 +663,50 @@ v0.6 增加按事件**推送**:三个确定性事件,命中才推、每次
664
663
  - **尚未做**:resume brief(隔天继续)与「预计剩余请求不足时跳过推送」的显式 ROI 门——
665
664
  当前靠 3 行硬预算 + 账本观测。
666
665
 
666
+ ### 会话纪要 + 恢复简报(v0.7-A/D)
667
+
668
+ - **episode**:会话结束(`session/disposed`)时,用零 LLM 规则把事件日志压成一条紧凑纪要——
669
+ 改过哪些文件、跑过哪些命令、哪些失败后修复(`cmd×N→fixed`)、git commit 的书面主题、
670
+ 仍挂着的 todo。只在有实质内容时生成(纯 `ls` 会话不写);**永不进 `[ENGRAM]` 索引**;
671
+ 每个 workspace 只保留最近 20 条,超出自动归档。
672
+ 数据源全部是已有事件(`tool/call` 的 arguments、`tool/result`、`todo/write`),不调模型。
673
+ - **恢复简报**:新会话第一次组装时,取最近 7 天最新的 episode,注入 ≤240 字符的
674
+ `[RESUME]` 行 + `engram_detail <id>` 指针;与 `[ENGRAM]`/`[RECALL]` 一起按会话冻结。
675
+ 没有近期 episode 就不注入——隔天继续项目时不用重新交代背景。
676
+ - 设置键:`episodeEnabled`、`resumeBriefEnabled`(均默认开),
677
+ `episodeMaxPerWorkspace` / `resumeBriefMaxAgeDays` / `resumeBriefMaxChars` 可调。
678
+
679
+ ### 实体页 + 实体卡(v0.7-B)
680
+
681
+ `esr_node` 从「图上的点」升级成**可读的页**:对一个实体(package / repo / 服务 / bug…)
682
+ 聚合当前真相、时间线、未闭环任务、冲突与关系。
683
+
684
+ - **实体卡(索引内,默认开)**:`[ENGRAM]` 为活跃实体(有锚定记忆或未闭环任务)
685
+ 渲染一行,最多 2 张:
686
+ `[ENT] dsh-engram · package · 3 mem · 1 open · truth: … #id · page: engram_detail(entity="dsh-engram")`
687
+ 这些实体的平铺记忆行不再重复(真相由卡片承载);其余实体仍列在 `nodes:` 一行。
688
+ - **完整页(按需下钻)**:`engram_detail(entity="<name|id>")` 返回
689
+ `truth / timeline / open loops / conflicts / relations` 五段,带 memory id 与证据缺口;
690
+ 没有注册节点但有记忆锚定的名字也会生成虚拟页。
691
+ - 纯函数、确定性排序:preference 排在 truth 最前,superseded 旧真相不进 truth,
692
+ 冲突按无序对去重(`#a vs #b`)。
693
+ - 设置键 `entityCardsEnabled`(默认开);`entityCardsMax` / `entityCardChars` 可调。
694
+
695
+ ### 可重验证锚点 + 分层 TTL(v0.7-C)
696
+
697
+ 「把过去当真相」是记忆插件最大的可信度问题。v0.7 给带 `filePath` 的记忆加**锚点**:
698
+
699
+ - 写入时记录 `{path, mtimeMs, size, hash?}`(≤256KB 的文件存 sha256,大文件只存 stat),
700
+ 落盘在记忆行的 `anchor` 字段;相对路径按 workspace 解析为绝对路径。
701
+ - 会话启动时做一次**有界重验证**(最多 40 条):文件缺失/改动 → 记忆打 `stale` 标签;
702
+ `[ENGRAM]` 索引与实体页 `truth` **不再自动展示**它,但**不删除**——`engram_detail`
703
+ 仍可重取,实体页 timeline 会标 `· stale`。
704
+ - **分层 TTL**:`preference` / `procedure` 永久;`episode` 默认 14 天;`error` 默认 60 天;
705
+ 其余按 `expireDays`(默认 180 天)。真正删除仍只发生在 GC 的归档(不硬删)。
706
+ - 设置键:`anchorRevalidate`(默认开)、`episodeTtlDays`、`errorTtlDays`。
707
+ - **未做**:离线「巩固任务」(episode 抽象/合并)——确定性规则只能做 rollup,不是语义抽象;
708
+ 在没有证据前不引入,留到 v0.8 与 playbook 一起考虑。
709
+
667
710
  ## 自动捕获策略
668
711
 
669
712
  捕获是确定性、离线的——只看到工具*结果*,从不看对话本身。什么会被记录成一条记忆:
@@ -763,6 +806,11 @@ promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-pr
763
806
  gcReplacesCompaction: false # Context GC:接管 DSH 自动 compact(默认关=宿主 LLM 压缩;true=opt-in 指针摘要)
764
807
  gcNarrative: true # 无锚轮次走 scoped LLM 叙事;false=纯机械(零 LLM)
765
808
  jitEnabled: true # JIT 推送:错误复发/文件焦点/goal 完成时推一行已有记忆
809
+ episodeEnabled: true # 会话纪要:结束时零 LLM 生成 episode(不进索引,供恢复简报)
810
+ resumeBriefEnabled: true # 恢复简报:新会话注入最近 7 天最新 episode
811
+ entityCardsEnabled: true # 实体卡:[ENGRAM] 为活跃实体渲染一行真相 + 任务 + 下钻指针
812
+ anchorRevalidate: true # 会话启动重验证 filePath 锚点(改动/缺失 → stale,不进索引/truth)
813
+ episodeTtlDays: 14 # episode TTL(preference/procedure 永久,error 60 天,其余 expireDays)
766
814
  ledgerEnabled: true # Context Ledger:注入成本/剪枝节省/前缀重写罚金 → 净 token
767
815
  ledgerCacheDiscount: 0.1 # 缓存折价:重写按 (1−0.1) 计罚金;0.1 = 命中只付 10%
768
816
  autoWebCompaction: false # web 预设自动改写(默认关;开启需显式确认,可用 revert 还原)
@@ -774,7 +822,7 @@ promote: 2 pending todo(s) vs 1 ESR task(s) — esr_task(name="…") #suggest-pr
774
822
  ## 开发
775
823
 
776
824
  ```sh
777
- npm test # 359 个测试:核心 + Web API + GC + Context GC + ESR 触发 + Context Ledger + JIT(node:test)
825
+ npm test # 381 个测试:核心 + Web API + GC + Context GC + ESR 触发 + Context Ledger + JIT(node:test)
778
826
  npm run build:client
779
827
  ```
780
828
 
@@ -343,7 +343,7 @@ net : avoided − injected − rewrites×(1−cacheDiscount)
343
343
  - **状态**:已实现(2026-10-19)——`lib/ledger.js`(纯函数 + 原子落盘 + delta flush)、
344
344
  `test/ledger.test.mjs` 12 例、`/stats` 与 `/overview` 的 `ledger` 字段、
345
345
  `ledgerEnabled` / `ledgerCacheDiscount` 两个设置键、GUI 遥测页账本面板 +
346
- 概览「上下文净 token」卡;`npm test` 359/359。
346
+ 概览「上下文净 token」卡;`npm test` 381/381。
347
347
  - 反事实基线:同一会话关闭机制时的 avoided 由 oracle 离线算,账本只报「实际发生」。
348
348
 
349
349
  **B. 自适应默认(`boundaryPrune: "auto"`)**
@@ -424,6 +424,13 @@ conflicts : contradicts 边 + 待裁决标记
424
424
  - 注入时只给**实体卡**(≤3 行:truth 首行 + open loops 数 + 更新时间),
425
425
  详情走 `engram_detail`/页——继续渐进披露。
426
426
  - 索引从「平铺行」升级为「实体卡 + 少量独立事实」,同样 700c 预算内信息密度更高。
427
+ - **状态(2026-10-19)**:**B 已实现**——`lib/entity-page.js`
428
+ (`buildEntityPage` / `resolveEntity` / `renderEntityCard` / `renderEntityPage`);
429
+ `selectEntityCards` 在 `[ENGRAM]` 前置最多 2 张实体卡(truth 首条 + mem/open 计数 +
430
+ `engram_detail(entity=…)` 指针),并把这些实体的平铺记忆行从索引中去重;
431
+ `engram_detail` 新增可选 `entity` 参数返回完整页(truth/timeline/open loops/
432
+ conflicts/relations);`entityCardsEnabled` 默认开。`test/entity-page.test.mjs` 7 例。
433
+ **C(锚点重验证 / 分层 TTL / 巩固任务)仍未做**。
427
434
 
428
435
  **C. 巩固任务(可离线)**
429
436
 
@@ -438,12 +445,29 @@ conflicts : contradicts 边 + 待裁决标记
438
445
  召回/索引前抽查;锚点失效 → 自动降权并标 `stale`,而不是继续当真相。
439
446
  - `expireDays` 从「整条过期」改为「分层 TTL」:episode 短、fact 长、preference 永久。
440
447
 
448
+ - **状态(2026-10-19)**:**C 的锚点 + 分层 TTL 已实现**——`lib/anchor.js`
449
+ (`makeFileAnchor` / `anchorStatus` / `memoryIsStale` / `collectStaleAnchors`),
450
+ 记忆行新增 `anchor` 字段(path/mtimeMs/size/hash?/at;≤256KB 存 sha256);
451
+ 会话启动时有界重验证(≤40 条),改动/缺失 → 打 `stale` 标签,索引与实体 truth
452
+ 不再展示、timeline 标 `· stale`、`engram_detail` 仍可重取;分层 TTL:
453
+ preference/procedure 永久、episode 14d、error 60d、其余 expireDays。
454
+ `test/anchor.test.mjs` 6 例。**离线巩固任务(episode 抽象/合并)未做**:
455
+ 纯规则只能 rollup、不是语义抽象,在没有证据前不引入(与 v0.8 playbook 一起考虑)。
456
+
441
457
  **D. Resume Brief(隔天继续)**
442
458
 
443
459
  打开旧会话/同 workspace 新会话时,注入 ≤5 行:上次停在哪、未闭环任务、
444
460
  最近的失败与修复、下一步建议。这是本项目对「73% 请求在长会话」最直接的产品回答,
445
461
  也是与宿主 checkpoint 的差异化(宿主存状态,我们存**意义**)。
446
462
 
463
+ - **状态(2026-10-19)**:**A + D 已实现**——`lib/episode.js` 的 `buildEpisode()`
464
+ 在 `session/disposed` 生成零 LLM 纪要(文件/命令/失败→修复/决策/未闭环,仅实质会话写入,
465
+ 永不进索引,每 workspace 保留 20 条);`renderResumeBrief()` 在新会话注入最近 7 天
466
+ 最新 episode(`[RESUME]` ≤240 字符 + `engram_detail` 指针);设置键
467
+ `episodeEnabled` / `resumeBriefEnabled`;`test/episode.test.mjs` 9 例。
468
+ **B(实体页)/ C(锚点重验证、分层 TTL)待做**——它们需要 schema 迁移与索引渲染改造,
469
+ 留到下一轮。
470
+
447
471
  **门:**
448
472
 
449
473
  - 跨会话问答集(自建 40 题 + LongMemEval 子集)Hit@3 提升 ≥ 20%;
@@ -0,0 +1,57 @@
1
+ ## dsh-engram v0.7.0
2
+
3
+ 自 v0.6.0 以来:把「记忆」从**事件记录**推进到**可维护的当前真相**——会话结束自动沉淀
4
+ episode、新会话给恢复简报、`esr_node` 升级为实体页、文件锚点让过期真相可被检测。
5
+
6
+ ### 功能
7
+
8
+ - **会话 episode(v0.7-A)** — `session/disposed` 时用零 LLM 规则把事件日志压成一条紧凑
9
+ 纪要:改过哪些文件、跑过哪些命令、哪些失败后修复(`cmd×N→fixed`)、git commit 主题、
10
+ 仍挂着的 todo。只在有实质内容时写(纯 `ls` 会话不写),**永不进 `[ENGRAM]` 索引**,
11
+ 每个 workspace 保留最近 20 条。
12
+ - **恢复简报(v0.7-D)** — 新会话第一次组装时,取最近 7 天最新的 episode,注入
13
+ ≤240 字符的 `[RESUME]` 行 + `engram_detail` 指针;与 `[ENGRAM]`/`[RECALL]` 一起按会话冻结。
14
+ 没有近期 episode 就不注入。
15
+ - **实体页 + 实体卡(v0.7-B)** — `esr_node` 升级为可读页面:`truth`(preference 优先、
16
+ superseded 剔除)/ `timeline` / `open loops`(active 任务 + 证据缺口)/ `conflicts`
17
+ (无序对去重)/ `relations`。`[ENGRAM]` 为活跃实体前置最多 2 张一行卡
18
+ (truth 首条 + mem/open + 下钻指针),被代表的实体不再重复平铺行;
19
+ `engram_detail(entity="<name|id>")` 返回完整页。
20
+ - **可重验证锚点(v0.7-C)** — 带 `filePath` 的记忆在写入时记录
21
+ `{path, mtimeMs, size, hash?}`(≤256KB 存 sha256);会话启动时有界重验证(≤40 条),
22
+ 文件改动/缺失 → 打 `stale` 标签,索引与实体 truth 不再展示,timeline 标注 `· stale`,
23
+ `engram_detail` 仍可重取(不删除)。
24
+ - **分层 TTL(v0.7-C)** — `preference` / `procedure` 永久;`episode` 14 天;`error` 60 天;
25
+ 其余按 `expireDays`(默认 180 天)。仍然只有 GC 归档、没有硬删。
26
+
27
+ ### 兼容性说明
28
+
29
+ - 存储 schema 只**新增**字段:`memories.anchor`(nullable,默认 null)与新的 memory kind
30
+ `episode`。旧数据读取自动补默认值,无需迁移;真实实例冒烟用 256 条既有记忆验证通过。
31
+ - 只有**新写入**且带 `filePath` 的记忆会落锚;既有记忆不会被回溯锚定(避免一次性把历史
32
+ 事实全部标 stale)。需要时可后续加显式 backfill 命令。
33
+ - 新增设置键(均默认开):`episodeEnabled`、`resumeBriefEnabled`、`entityCardsEnabled`、
34
+ `anchorRevalidate`;隐藏可调 `episodeMaxPerWorkspace`、`resumeBriefMaxAgeDays`、
35
+ `resumeBriefMaxChars`、`entityCardsMax`、`entityCardChars`、`episodeTtlDays`、
36
+ `errorTtlDays`。GUI 旋钮总数 34。
37
+ - 升级后需重启 `dsh web`;episode 要等一个会话结束、resume brief 在下一个新会话生效。
38
+
39
+ ### 其他
40
+
41
+ - 测试 **381/381**(新增 episode 9 例、entity 7 例、anchor 6 例);`npm run eval` BENCH PASS;
42
+ `npm run typecheck` 通过;`lib/client.js` 重建。
43
+ - 文档:中英 README(episode / 实体页 / 锚点三节)、`docs/PROPOSAL-evolution.zh.md`
44
+ (v0.7 A/B/C/D 状态)、设置卡新增「会话纪要 / 恢复简报 / 实体卡」开关。
45
+
46
+ ## DSH 版本支持
47
+
48
+ - **支持的 DSH 范围**:`>=0.1.2-alpha.2 <0.3.0-0`
49
+ - **peer 声明**:
50
+ - `@deepseek-ai/dsh-compaction`: `>=0.1.2-alpha.2 <0.3.0-0`
51
+ - `@deepseek-ai/dsh-compaction-basic`: `>=0.1.2-alpha.2 <0.3.0-0`
52
+ - `@deepseek-ai/dsh-llm`: `>=0.1.2-alpha.2 <0.3.0-0`
53
+ - `@deepseek-ai/dsh-settings`: `>=0.1.2-alpha.2 <0.3.0-0`
54
+ - `@deepseek-ai/dsh-storage-domain`: `>=0.1.2-alpha.2 <0.3.0-0`
55
+ - **验证版本**:`dsh 0.2.0-rc.2`
56
+ - **最后验证日期**:2026-10-19
57
+ - **安装方式已验证**:`dsh plugin --profile web add dsh-engram`
package/lib/anchor.js ADDED
@@ -0,0 +1,124 @@
1
+ /**
2
+ * dsh-engram revalidatable file anchors (PROPOSAL-evolution §4 v0.7-C).
3
+ *
4
+ * "The past is treated as truth" is the strongest critique against memory
5
+ * plugins: a fact written last week may describe code that changed yesterday.
6
+ * An anchor makes staleness DETECTABLE instead of trusted:
7
+ *
8
+ * { path, mtimeMs, size, hash?, at }
9
+ *
10
+ * - `makeFileAnchor` records the file's identity when a memory is written
11
+ * (sha256 for small files, mtime+size for every file — cheap default);
12
+ * - `anchorStatus` re-stats (and re-hashes when a hash exists) and returns
13
+ * `valid | changed | missing`;
14
+ * - `collectStaleAnchors` scans a bounded set of active memories so the index
15
+ * / entity truth can drop rows whose source moved, and the caller can tag
16
+ * them `stale` (never deleted — still re-fetchable via engram_detail).
17
+ *
18
+ * Zero LLM, fail-contained: any filesystem error reads as "missing", never
19
+ * throws into a render path.
20
+ */
21
+
22
+ import { createHash } from "node:crypto";
23
+ import { readFileSync, statSync } from "node:fs";
24
+ import path from "node:path";
25
+
26
+ const DEFAULT_MAX_HASH_BYTES = 256 * 1024;
27
+
28
+ function hashFile(file, size) {
29
+ try {
30
+ return createHash("sha256").update(readFileSync(file)).digest("hex").slice(0, 32);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ function absolute(filePath, baseDir) {
37
+ const raw = String(filePath ?? "").trim();
38
+ if (raw.length === 0) return "";
39
+ if (path.isAbsolute(raw)) return raw;
40
+ return typeof baseDir === "string" && baseDir.length > 0 ? path.resolve(baseDir, raw) : path.resolve(raw);
41
+ }
42
+
43
+ /**
44
+ * Build an anchor for an existing file, or null when it cannot be stat'ed.
45
+ *
46
+ * @param {string} filePath file path as stored on the memory
47
+ * @param {object} [opts]
48
+ * @param {string} [opts.baseDir] directory for relative paths (the workspace)
49
+ * @param {number} [opts.now]
50
+ * @param {number} [opts.maxHashBytes] hash only files up to this size
51
+ */
52
+ export function makeFileAnchor(filePath, { baseDir = "", now = Date.now(), maxHashBytes = DEFAULT_MAX_HASH_BYTES } = {}) {
53
+ const abs = absolute(filePath, baseDir);
54
+ if (abs.length === 0) return null;
55
+ try {
56
+ const stat = statSync(abs);
57
+ if (!stat.isFile()) return null;
58
+ return {
59
+ path: abs,
60
+ mtimeMs: stat.mtimeMs,
61
+ size: stat.size,
62
+ hash: stat.size <= maxHashBytes ? hashFile(abs, stat.size) : null,
63
+ at: now,
64
+ };
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Revalidate one anchor: `valid` | `changed` | `missing` (`none` when absent).
72
+ */
73
+ export function anchorStatus(anchor) {
74
+ if (anchor === null || anchor === void 0 || typeof anchor.path !== "string" || anchor.path.length === 0) {
75
+ return "none";
76
+ }
77
+ let stat;
78
+ try {
79
+ stat = statSync(anchor.path);
80
+ } catch {
81
+ return "missing";
82
+ }
83
+ if (!stat.isFile()) return "missing";
84
+ if (stat.size !== anchor.size || stat.mtimeMs !== anchor.mtimeMs) {
85
+ // mtime can survive content changes only in exotic cases; when a hash was
86
+ // recorded, let it decide before declaring the file changed.
87
+ if (typeof anchor.hash !== "string") return "changed";
88
+ const hash = hashFile(anchor.path, stat.size);
89
+ return hash === anchor.hash ? "valid" : "changed";
90
+ }
91
+ if (typeof anchor.hash === "string") {
92
+ const hash = hashFile(anchor.path, stat.size);
93
+ return hash === anchor.hash ? "valid" : "changed";
94
+ }
95
+ return "valid";
96
+ }
97
+
98
+ /** Is this memory's source still the same file it was written against? */
99
+ export function memoryIsStale(memory) {
100
+ if (memory === null || typeof memory !== "object") return false;
101
+ const status = anchorStatus(memory.anchor);
102
+ return status === "changed" || status === "missing";
103
+ }
104
+
105
+ /**
106
+ * Bounded scan of active anchored memories whose source moved/vanished.
107
+ * Returns [{ id, status, path }] and never throws.
108
+ */
109
+ export function collectStaleAnchors(store, workspace, { limit = 40 } = {}) {
110
+ if (store === null || typeof store.listMemories !== "function") return [];
111
+ const cap = Number.isFinite(limit) && limit > 0 ? Math.trunc(limit) : 40;
112
+ const rows = store.listMemories(workspace, 200);
113
+ const out = [];
114
+ for (const memory of rows) {
115
+ if (out.length >= cap) break;
116
+ if (memory?.anchor === null || memory?.anchor === void 0) continue;
117
+ if (Array.isArray(memory.tags) && memory.tags.includes("stale")) continue;
118
+ const status = anchorStatus(memory.anchor);
119
+ if (status === "changed" || status === "missing") {
120
+ out.push({ id: memory.id, status, path: memory.anchor.path });
121
+ }
122
+ }
123
+ return out;
124
+ }
package/lib/client.js CHANGED
@@ -2714,6 +2714,7 @@ function signedTokens(n) {
2714
2714
  }
2715
2715
  var KIND_COLORS2 = {
2716
2716
  preference: "#b45309",
2717
+ episode: "#0e7490",
2717
2718
  decision: "#2563eb",
2718
2719
  error: "#dc2626",
2719
2720
  procedure: "#7c3aed",
@@ -3703,6 +3704,9 @@ var COMMON_FIELDS = [
3703
3704
  { key: "resultPack", label: "\u7ED3\u679C\u5F52\u6863\u53EC\u56DE", hint: "ResultPack L2\uFF1A\u526A\u6389\u7684\u4E2D\u6BB5\u8131\u654F\u5F52\u6863\uFF0C\u8282\u70B9\u5C3E\u90E8\u9644 handle\uFF0C\u6A21\u578B\u53EF\u7528 result_recall(callId) \u6309\u9875\u53EC\u56DE\uFF08\u5B8C\u6574 ObservationPack\uFF1B\u9ED8\u8BA4\u5F00\uFF0C\u4FDD\u8BC1\u9ED8\u8BA4\u526A\u679D\u65E0\u635F\uFF1B\u9700\u4E0E\u8FB9\u754C\u526A\u679D\u4E00\u8D77\u5F00\uFF09", kind: "bool" },
3704
3705
  { key: "autoRecallOnStart", label: "\u4F1A\u8BDD\u542F\u52A8\u81EA\u52A8\u53EC\u56DE", hint: "\u65B0\u4F1A\u8BDD\u6309\u9996\u6761\u6D88\u606F\u81EA\u52A8\u628A\u6700\u76F8\u5173\u8BB0\u5FC6\u6CE8\u5165 [RECALL] \u5757\uFF08\u65E0\u9700\u6A21\u578B\u60F3\u8D77\u8C03 engram_recall\uFF09\uFF1B\u5173\u6389=\u56DE\u5230\u7EAF\u62C9\u53D6", kind: "bool" },
3705
3706
  { key: "jitEnabled", label: "JIT \u63A8\u9001", hint: "\u9519\u8BEF\u590D\u53D1 / \u6587\u4EF6\u7126\u70B9 / goal \u5B8C\u6210\u65F6\uFF0C\u628A\u5DF2\u6709\u8BB0\u5FC6\u538B\u6210\u4E00\u884C\u63A8\u8FDB\u4E0A\u4E0B\u6587\uFF08append-only\u3001\u6BCF\u4F1A\u8BDD\u22643 \u884C\u3001\u65E0\u547D\u4E2D\u6C89\u9ED8\uFF1B\u6BCF\u6B21\u63A8\u9001\u6309\u524D\u7F00\u91CD\u5199\u8BA1\u5165\u8D26\u672C\uFF09", kind: "bool" },
3707
+ { key: "episodeEnabled", label: "\u4F1A\u8BDD\u7EAA\u8981", hint: "\u4F1A\u8BDD\u7ED3\u675F\u65F6\u7528\u96F6 LLM \u89C4\u5219\u751F\u6210\u4E00\u6761 episode\uFF08\u6587\u4EF6/\u547D\u4EE4/\u5931\u8D25\u2192\u4FEE\u590D/\u51B3\u7B56/\u672A\u95ED\u73AF\uFF09\uFF0C\u4E0D\u8FDB\u7D22\u5F15\uFF0C\u4F9B\u6062\u590D\u7B80\u62A5\u4F7F\u7528\uFF1B\u6BCF workspace \u4FDD\u7559\u6700\u8FD1 20 \u6761", kind: "bool" },
3708
+ { key: "resumeBriefEnabled", label: "\u6062\u590D\u7B80\u62A5", hint: "\u65B0\u4F1A\u8BDD\u542F\u52A8\u65F6\u6CE8\u5165\u6700\u8FD1 7 \u5929\u7684\u6700\u65B0 episode\uFF08\u2264240 \u5B57\u7B26 + engram_detail \u6307\u9488\uFF09\uFF0C\u9694\u5929\u7EE7\u7EED\u4E0D\u7528\u91CD\u65B0\u4EA4\u4EE3\u80CC\u666F", kind: "bool" },
3709
+ { key: "entityCardsEnabled", label: "\u5B9E\u4F53\u5361", hint: "[ENGRAM] \u7D22\u5F15\u4E3A\u6D3B\u8DC3\u5B9E\u4F53\uFF08\u6709\u951A\u5B9A\u8BB0\u5FC6\u6216\u672A\u95ED\u73AF\u4EFB\u52A1\uFF09\u6E32\u67D3\u4E00\u884C\u300C\u5F53\u524D\u771F\u76F8 + \u6253\u5F00\u4EFB\u52A1 + engram_detail(entity=\u2026) \u6307\u9488\u300D\uFF0C\u6700\u591A 2 \u5F20\uFF0C\u66FF\u4EE3\u8FD9\u4E9B\u5B9E\u4F53\u7684\u5E73\u94FA\u8BB0\u5FC6\u884C", kind: "bool" },
3706
3710
  { key: "maxRecallPerSession", label: "\u6BCF\u4F1A\u8BDD\u53BB\u91CD\u4E0A\u9650", hint: "recall \u6BCF\u4F1A\u8BDD\u6700\u591A\u4FDD\u7559 N \u6761\uFF08\u8DE8\u4F1A\u8BDD\u53BB\u91CD\uFF09", kind: "num", min: 1, max: 10 },
3707
3711
  { key: "expireDays", label: "TTL\uFF08\u5929\uFF09", hint: "0 = \u4E0D\u8FC7\u671F", kind: "num", min: 0, max: 3650 },
3708
3712
  { key: "indexMaxChars", label: "\u7D22\u5F15\u5B57\u7B26\u4E0A\u9650", hint: "[ENGRAM] \u5757 token \u9884\u7B97", kind: "num", min: 0, max: 4e3 }