dsh-layered-memory 0.8.11 → 0.9.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.en.md CHANGED
@@ -10,21 +10,27 @@
10
10
  [简体中文](README.md) · [Latest release](https://github.com/JunNanLYS/dsh-layered-memory/releases/latest) · [Report issues](https://github.com/JunNanLYS/dsh-layered-memory/issues)
11
11
 
12
12
  [![npm version](https://img.shields.io/npm/v/dsh-layered-memory?color=6f83ff&style=flat-square&label=npm)](https://www.npmjs.com/package/dsh-layered-memory)
13
- [![DSH 0.1.1-rc.2](https://img.shields.io/badge/DSH-0.1.1--rc.2-8b5cf6?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
13
+ [![DSH 0.1.2-alpha.x](https://img.shields.io/badge/DSH-0.1.2--alpha.x-8b5cf6?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
14
14
  [![MIT License](https://img.shields.io/badge/license-MIT-536990?style=flat-square)](LICENSE)
15
15
 
16
16
  </div>
17
17
 
18
18
  ## Getting Started
19
19
 
20
- Requires Node ≥ 22.16. Two invocation styles the `npx` prefix can replace `dsh` in
20
+ Requires Node ≥ 22.16 and DeepSeek Harness **0.1.2-alpha.1** (as of 0.8.12 only the
21
+ 0.1.2-alpha.x host line is supported; for older hosts see the
22
+ [release history](https://github.com/JunNanLYS/dsh-layered-memory/releases)).
23
+ Two invocation styles — the `npx` prefix can replace `dsh` in
21
24
  any command below:
22
25
 
23
26
  ```bash
24
- # Option 1: run the official CLI directly via npx (no pre-installed dsh; version can be pinned, e.g. dsh-layered-memory@0.8.4)
25
- npx -y @deepseek-ai/dsh plugin --profile web add dsh-layered-memory
27
+ # Option 1: run the official CLI directly via npx (no pre-installed dsh; the host version must
28
+ # be pinned to the alpha line — the npm "latest" tag still points to 0.1.1-rc.x)
29
+ npx -y @deepseek-ai/dsh@0.1.2-alpha.2 plugin --profile web add dsh-layered-memory
26
30
 
27
- # Option 2: with the dsh CLI installed (dsh is a pnpm forwarder; npm i -g pnpm first if missing)
31
+ # Option 2: with the dsh CLI installed (upgrade to the alpha line first:
32
+ # npm i -g @deepseek-ai/dsh@0.1.2-alpha.2, then restart; dsh is a pnpm forwarder —
33
+ # npm i -g pnpm first if missing)
28
34
  dsh plugin --profile web add dsh-layered-memory
29
35
 
30
36
  # Alternative sources: GitHub repo / local path (dev & debugging, link: points at the repo; npm run build + restart dsh to apply)
@@ -54,8 +60,8 @@ This package declares a `dsh.bundle` composition layer (`cordis.patch.yml`); aft
54
60
  installation the **plugin entry is mounted automatically** — no need to hand-edit
55
61
  `$DSH_HOME/profiles/web/cordis.patch.yml`. Then restart DeepSeek Harness and verify:
56
62
  the appearance of `conversations/ records/ scenes/` and `memory.db` under
57
- `~/.dsh/memory/` means the plugin applied successfully; the "Memory" page in settings
58
- and the mode pill in the input bar mean the client half is ready.
63
+ `~/.dsh/memory/` means the plugin applied successfully; the "Memory" page in settings (the Memory Workspace with five zones)
64
+ and the memory chip in the input bar (`Memory · Auto`) mean the client half is ready.
59
65
 
60
66
  > ⚠️ **Security note**: installing a plugin = running third-party code with your
61
67
  > privileges. This plugin reads session content, writes files in its data directory,
@@ -106,7 +112,7 @@ also registers three model-callable memory tools: `memory_search` /
106
112
  **Cost dashboard**: every distillation LLM call (extract / dedup / L2 / L3) writes its
107
113
  token cost to a SQLite detail table keyed by `provider/model` (configurable retention,
108
114
  default 365 days with rolling cleanup on write; accounting failures only log a warning
109
- and never block distillation). Visualize it under Settings → Memory → the **Cost** tab:
115
+ and never block distillation). Visualize it under Settings → Memory → Insights → the **Cost** sub-page:
110
116
  per-model trend lines (day/week/month granularity + last-N-days window + L1/L2/L3 layer
111
117
  filter), a layer × time-window table (calls / output & reasoning tokens / mean / median),
112
118
  and per-model totals — distillation overhead at a glance. Input is counted in characters
@@ -140,45 +146,67 @@ trajectory view):
140
146
  ## Per-Session Memory Modes
141
147
 
142
148
  <p align="center">
143
- <img src="./assets/img/Modes.png" width="100%"
144
- alt="Per-session memory modes: a glass capsule track with four stops (日常 · 工作 · 智能 · 关闭), the glowing orb resting on 智能 (default); a vignette above each personal chat bubbles for 日常, code/document panes for 工作, two streams merging brightest for 智能, a dim dashed ghost bubble for 关闭">
149
+ <img src="./assets/img/MemoryChip.png" width="72%"
150
+ alt="Session memory chip and cascade menu in dark theme: in the composer-left cluster, right of the Read Only chip sits the "Memory · Auto ▾" chip; clicking opens a rounded popover menu upward with two rows "Memory scope Auto ›" / "Data flow Follow global ›", and hovering the data-flow row reveals a secondary panel listing Follow global ✓ / Read & write / Write only / Paused">
145
151
  </p>
146
152
 
147
- - **Control**: the pill next to the mode selector in the input bar (`Memory · Auto`);
148
- clicking opens a macOS-style sliding picker above release to snap to the nearest
149
- mode; adapts to light/dark themes;
150
- - The lower half of the popover is a **per-session info area**: recall hits
151
- (hit/searched turns plus cumulative items), batching progress (this session's
152
- slice x/effective threshold; the off mode shows parked slices instead), memories
153
- produced for this session, and session message count plus status lines for
154
- anomalies (storage degraded / vector search unavailable) and a global summary
155
- (pending distill count, last distill time). Data comes from the
156
- `dsh-memory/session-stats` endpoint (in-memory registries + an indexed COUNT,
157
- zero file I/O), adaptively polled while open (2s busy / 5s idle) and stopped on
158
- close;
159
- - Each session's choice is persisted by sessionId to `session-modes.json`, surviving
160
- restarts/session restore; stacks with the global switches (global is the master gate);
161
- L2/L3 are fully family-isolated content never leaks across families.
162
- - **Write-only sessions (#38)**: a three-state "injection" switch inside the popover
163
- (follow global / on / off) set to "off" for a **write-only session**: capture and
164
- distillation continue as usual (conversation still settles into L0→L1→L2/L3), but
165
- nothing is injected into this session (recall injection, the persona/navigation
166
- stable section and the tools guide all stop; `memory_search` and the other read
167
- tools return a write-only notice). The pill face changes to `Memory · Write-only`;
168
- the override persists per session, and switching back to "follow global" clears it
169
- to the settings-page recall toggle. Ideal for debug/eval/sensitive sessions that
170
- should absorb without interference. Orthogonal to the off mode: off remains full
171
- stealth (capture off too), while write-only keeps the "in" and gates the "out".
153
+ The conversation side is a **distributed memory surface** each kind of information
154
+ lives in the native host seat designed for it; the plugin no longer owns a strip:
155
+
156
+ - **Memory chip** (composer-left cluster, right of the Read Only chip): a borderless
157
+ `Memory · {Auto|Personal|Work}` chip in official composer-chip grammar; the text is
158
+ the resolved truth `Memory · write-only` (injection off), `Memory · paused`
159
+ (gray dot), `Memory · degraded` (amber dot); zh/en bilingual, following the host
160
+ language.
161
+ - **Cascade menu** (click the chip, opens upward): two rows, `Memory scope {value} ›`
162
+ and `Data flow {value} ›`; data-flow options live in a **hover-only secondary
163
+ panel** (Follow global / Read & write / Write only / Paused) no click-pinning,
164
+ with bridge hot-zones so slow mouse travel never breaks the hover chain; full
165
+ keyboard path (arrow-key roving + focus reveal).
166
+ - **Inline slider** (click "Memory scope", grows in place): three stops
167
+ Personal / Work / Auto; crossing a stop updates the chip text **live** while
168
+ dragging; keyboard arrows/Home/End + `aria-valuetext`.
169
+ - **Pause-resume snapshot**: switching the data flow to "Paused" enters the off mode
170
+ and persists the pre-pause scope and injection override; resuming restores them
171
+ as-is. Per-session choices persist to `session-modes.json` (stacked with the
172
+ global switches global is the master gate); L2/L3 are fully family-isolated.
173
+ - **Memory occupancy lives only in the official context meter panel**: opening the
174
+ official ring shows the "Memory" section (recall snippets / memory stable zone);
175
+ the composer area has zero occupancy UI only a `N pending distill` telemetry
176
+ segment appended to the official stats line.
177
+ - **Write-only sessions (#38)**: pick "Write only" in the data-flow panel for a
178
+ **write-only session** — capture and distillation continue as usual (conversation
179
+ still settles into L0→L1→L2/L3), but nothing is injected into this session (recall
180
+ injection, the persona/navigation stable section and the tools guide all stop;
181
+ `memory_search` and the other read tools return a write-only notice). The override
182
+ persists per session; switching back to "Follow global" clears it to the recall
183
+ toggle in Automation. Ideal for debug/eval/sensitive sessions that should absorb
184
+ without interference. Orthogonal to Paused: paused is full stealth (capture off
185
+ too), while write-only keeps the "in" and gates the "out".
172
186
 
173
187
  ## UI Preview
174
188
 
175
189
  <p align="center">
176
- <img src="./assets/img/ui-dark.jpg" width="49.5%"
177
- alt="Settings memory browser overview in dark theme: status card (plugin version, capture/distill/recall switch states, FTS and vector capabilities, L1 memory count, distillation model) and stat tiles, glassy controls with a cold-blue accent">
178
- <img src="./assets/img/ui-light.jpg" width="49.5%"
179
- alt="The same settings memory browser overview in light theme: identical layout and information on light card backgrounds with the same accent family, theme switch without reload">
190
+ <img src="./assets/img/ui-dark.png" width="49.5%"
191
+ alt="Memory Workspace overview in dark theme: five-zone task nav (Overview selected), health summary card (running normally + storage/vector/distill-queue subsystem chips + a pending-distill attention chip), recent-activity list (New/Updated verb tags + Memory/Scene kind tags + relative times), key-number tiles (memories / scenes / weekly distill output / last distill) and jump buttons to the other zones">
192
+ <img src="./assets/img/ui-light.png" width="49.5%"
193
+ alt="The same Memory Workspace overview in light theme: identical five-zone nav and health/activity/numbers layout on light card backgrounds with the same accent family, theme switch without reload">
180
194
  </p>
181
195
 
196
+ Settings → Memory is the **Memory Workspace** (five-zone task nav, sticky tabs +
197
+ arrow-key roving):
198
+
199
+ - **Overview**: health summary + recent activity + key numbers + a guided empty state;
200
+ - **Library**: a **read-only asset activity feed** with L1 memories / L2 scenes /
201
+ L3 personas mixed by update time (search + type/scope/time filters + in-place
202
+ expansion + copy);
203
+ - **Automation**: basic switches + advanced disclosure (distill route chains and
204
+ budgets) + embedding-model disclosure;
205
+ - **Insights**: Cost / Activity (7-day asset activity + distill calls & failures) /
206
+ Recall (cumulative totals + disabled distribution);
207
+ - **Maintenance**: runtime health + diagnostic log + the danger-zone full rebuild
208
+ (confirm modal + progress + cancellable).
209
+
182
210
  ## Measured Comparison (DSH-MemBench: Automated Benchmark)
183
211
 
184
212
  Screenshots show what the plugin looks like — this section answers "**what does enabling it actually buy you?**" with measured numbers from an **automated benchmark** ([`bench/`](./bench/), one command to reproduce). Method: the same scenario bank with verbatim-identical inputs runs in **Group A (memory on)** with 3 merged repetitions and **Group B (memory off)** with 1 repetition (a memory-off long task burns multiples of the tokens per scenario — a cost guardrail); the dialog track now runs Group A only (memory-off probes in independent sessions cannot succeed, so the control carries no information — retired). Dialog-track environment: DeepSeek official `deepseek-v4-flash`, plugin 0.8.5 (judge same-source as tested; every answer archived for manual audit), Windows; taxonomy adapted from [LongMemEval](https://github.com/xiaowu0162/longmemeval) / [LoCoMo](https://snap-research.github.io/locomo/) / [AMB](https://github.com/vectorize-io/agent-memory-benchmark), with the extended probe types and lifecycle track informed by [MemoryAgentBench](https://arxiv.org/abs/2507.05257) / [GoodAI LTM](https://github.com/GoodAI/goodai-ltm-benchmark) / BEAM.
@@ -282,13 +310,13 @@ the bundle layer appends and causes `duplicate loader entry id` startup failure)
282
310
  | `embedding.allowLocalModels` | `true` | Allow the local embedding tier (deployment ceiling; when off, no model downloads and no local tier in settings) |
283
311
  | `embedding.mirror` | `https://hf-mirror.com` | Download mirror root for local models (can be changed back to `https://huggingface.co`) |
284
312
  | `embedding.proxy` | `''` | Three-state download proxy: `''` (default) = auto-detect proxy env vars (`HTTPS_PROXY`/`ALL_PROXY` etc., honoring `NO_PROXY`); `none` = disable, always direct; any other value = proxy URL (e.g. `http://127.0.0.1:7890`). Direct connections to the mirror are intermittently unreachable on some networks (connect timeouts and poisoned bytes have both been observed) — keep the default auto-detection on machines with a proxy |
285
- | `llm.provider/model` | empty | Static distillation route (deployment pin): when **both** fields are set the route is locked, outranking the settings-page runtime route chain and the default model (deployments can force distillation onto a specific route); when empty the route follows "settings-page route-chain primary → default model". At runtime, configure the primary route and fallback chain in the **route-chain editor** under Settings → Memory → Overviewdistillation parameters (pick from **configured providers**, including custom ones added in dsh Settings → Models; the primary row may stay empty to follow the default model) — a non-empty chain takes over this static config wholesale, effective immediately with no restart |
313
+ | `llm.provider/model` | empty | Static distillation route (deployment pin): when **both** fields are set the route is locked, outranking the settings-page runtime route chain and the default model (deployments can force distillation onto a specific route); when empty the route follows "settings-page route-chain primary → default model". At runtime, configure the primary route and fallback chain in the **route-chain editor** under Settings → Memory → AutomationAdvanced routing and budgets (pick from **configured providers**, including custom ones added in dsh Settings → Models; the primary row may stay empty to follow the default model) — a non-empty chain takes over this static config wholesale, effective immediately with no restart |
286
314
  | `llm.fallbacks` | `[]` | Distillation fallback chain: an ordered list of backup routes tried one by one when the primary route fails (error / cut-off / network error / **empty output**); each entry is `{provider, model, reasoningEffort?}` (a non-empty effort overrides the global `llm.reasoningEffort`, still clamped by model capability); entries identical to the primary route are skipped; **each route gets the full `timeoutMs`**; when all routes fail, the existing per-session backoff takes over. Empty list (default) = single-route behavior unchanged (see [Distillation fallback chain & slow-TTFT models](#distillation-fallback-chain--slow-ttft-models) below); a non-empty settings-page runtime chain (`distillChain`) takes over **both** the primary route and the fallback chain (a single-row chain = explicitly no fallbacks), empty = follow this config |
287
315
  | `llm.layerRoutes` | `{}` | **Per-layer distillation routing** (#34): keys `l1`/`l2`/`l3`, each holding a **complete chain** (entries like `llm.fallbacks`, **head row must have both provider+model explicitly**). A non-empty chain **fully replaces** that layer's resolution (its primary and fallbacks all come from the layer chain; the global chain no longer participates); empty/missing = the layer follows the global chain. `l1` covers both extraction and dedup call sites. Layers can also be edited at runtime in the segmented panel under distillation parameters on the settings page (takes priority over this static config); a deployment pin does not disable static layer chains (same deployer-owned config as the fallback-chain precedent). Orthogonal to and composable with the fallback chain — one complete chain per layer (ADR-0005) |
288
- | `llm.maxTokens` | `65536` | Fallback output cap for non-layered calls. Each distillation stage has its own budget (extraction 16k / dedup 8k / L2 32k / L3 16k; auto ×4 when the reasoning effort is high/xhigh/max, so thinking can't starve the text budget); the per-layer budgets are runtime-adjustable in Settings → Memory → Overviewdistillation parameters (empty/0 = built-in defaults) |
316
+ | `llm.maxTokens` | `65536` | Fallback output cap for non-layered calls. Each distillation stage has its own budget (extraction 16k / dedup 8k / L2 32k / L3 16k; auto ×4 when the reasoning effort is high/xhigh/max, so thinking can't starve the text budget); the per-layer budgets are runtime-adjustable in Settings → Memory → AutomationAdvanced routing and budgets (empty/0 = built-in defaults) |
289
317
  | `llm.reasoningEffort` | empty | Distillation reasoning effort: empty = **auto** (resolved from model capability: the model's default tier, else `high`); an explicit value (`off`/`none`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`) is only sent when the model declares support — effort vocabularies differ across providers (deepseek accepts `off`, OpenAI-style APIs use `none`, models that declare no tiers get nothing), and unsupported tiers degrade to not-sending with a one-time warning; output budgets auto-×4 at high/xhigh/max. At runtime, override the effort **per route** in the settings-page route-chain editor (per-row dropdown; the tier list follows each model's declared capability live, defaulting to this value) |
290
318
  | `llm.temperature` | `0.3` | Distillation temperature |
291
- | `llm.maxInputChars` | `700000` | Input character budget per distillation call (over-budget L1 inputs are chunked automatically); runtime-adjustable in Settings → distillation parameters → input budget (empty/0 = follow this value) |
319
+ | `llm.maxInputChars` | `700000` | Input character budget per distillation call (over-budget L1 inputs are chunked automatically); runtime-adjustable in Settings → Memory Automation Advanced routing and budgets → input budget (empty/0 = follow this value) |
292
320
  | `llm.timeoutMs` | `120000` | Per-call distillation timeout (ms) |
293
321
  | `tokenCost.retentionDays` | `365` | Retention (days) for distillation cost details (the `token_cost` table); rows older than this are rolled away on write. `0` = keep forever. Also the upper bound of the cost dashboard's "last N days" window |
294
322
  | `tools` | `true` | Whether to register model-callable memory tools |
@@ -298,7 +326,7 @@ the bundle layer appends and causes `duplicate loader entry id` startup failure)
298
326
 
299
327
  Free/slow tiers of some inference providers have **first-token latencies (TTFT) upwards of 20 seconds**, while some upstream gateways cut a silent connection at ~20s — distillation calls then fail at a fixed ~20s (`llm aborted`) long before the plugin's 120s timeout could ever matter (the scenario measured in [#31](https://github.com/JunNanLYS/dsh-layered-memory/issues/31)). Three mitigations, pick as needed:
300
328
 
301
- 1. **Switch route** (most direct): change the primary route live in the route-chain editor under Settings → Memory → Overviewdistillation parameters (or move a fast route to the head of the chain), or pin `llm.provider`/`llm.model` statically.
329
+ 1. **Switch route** (most direct): change the primary route live in the route-chain editor under Settings → Memory → AutomationAdvanced routing and budgets (or move a fast route to the head of the chain), or pin `llm.provider`/`llm.model` statically.
302
330
  2. **Fallback chain** (automatic demotion): when the primary route fails, backup routes are tried in order with no manual intervention:
303
331
 
304
332
  ```yaml
@@ -330,9 +358,9 @@ Free/slow tiers of some inference providers have **first-token latencies (TTFT)
330
358
  reasoningEffort: high
331
359
  ```
332
360
 
333
- Layers can also be edited at runtime in the **segmented panel** (global default / L1 / L2 / L3) under Settings → Memory → Overviewdistillation parameters. In-layer priority: runtime layer chain > this static YAML layer chain > global default chain, falling back level by level.
361
+ Layers can also be edited at runtime in the **segmented panel** (global default / L1 / L2 / L3) under Settings → Memory → AutomationAdvanced routing and budgets. In-layer priority: runtime layer chain > this static YAML layer chain > global default chain, falling back level by level.
334
362
 
335
- Failure = error / cut-off / network error / **empty output** (stream ends normally with 0 characters — worthless for distillation since parsing always fails, so it is treated as a route failure rather than an empty return); caller-initiated cancellation does not demote; each route gets the **full** `llm.timeoutMs` (a shared budget would give a slow-TTFT fallback route less time than its real first-packet needs, defeating the chain); token costs are recorded per attempt (failed attempts get a row too, with whatever tokens arrived before the stream broke), and successful calls are attributed to the route that actually served. The route chain can also be adjusted at runtime in the route-chain editor under Settings → Memory → Overviewdistillation parameters (no config edit or restart needed); the YAML below suits deployments that want to pin the static chain.
363
+ Failure = error / cut-off / network error / **empty output** (stream ends normally with 0 characters — worthless for distillation since parsing always fails, so it is treated as a route failure rather than an empty return); caller-initiated cancellation does not demote; each route gets the **full** `llm.timeoutMs` (a shared budget would give a slow-TTFT fallback route less time than its real first-packet needs, defeating the chain); token costs are recorded per attempt (failed attempts get a row too, with whatever tokens arrived before the stream broke), and successful calls are attributed to the route that actually served. The route chain can also be adjusted at runtime in the route-chain editor under Settings → Memory → AutomationAdvanced routing and budgets (no config edit or restart needed); the YAML below suits deployments that want to pin the static chain.
336
364
  4. **Raise the timeout**: `llm.timeoutMs` only helps when the route is genuinely slow but the gateway doesn't cut; if the gateway kills at 20s, raising the plugin timeout is futile — use the first two layers.
337
365
 
338
366
  ## Storage Layout
@@ -348,7 +376,7 @@ local), switchable at runtime in the settings page — see the next section.
348
376
 
349
377
  ## Semantic Retrieval (Embedding Source)
350
378
 
351
- Pick the embedding source in Settings → Memory → OverviewSemantic Retrieval;
379
+ Pick the embedding source in Settings → Memory → Automationembedding models;
352
380
  it takes effect immediately, no config edit or restart:
353
381
 
354
382
  <p align="center">
package/README.md CHANGED
@@ -10,20 +10,23 @@
10
10
  [English](README.en.md) · [最新发行版](https://github.com/JunNanLYS/dsh-layered-memory/releases/latest) · [反馈问题](https://github.com/JunNanLYS/dsh-layered-memory/issues)
11
11
 
12
12
  [![npm version](https://img.shields.io/npm/v/dsh-layered-memory?color=6f83ff&style=flat-square&label=npm)](https://www.npmjs.com/package/dsh-layered-memory)
13
- [![DSH 0.1.1-rc.2](https://img.shields.io/badge/DSH-0.1.1--rc.2-8b5cf6?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
13
+ [![DSH 0.1.2-alpha.x](https://img.shields.io/badge/DSH-0.1.2--alpha.x-8b5cf6?style=flat-square)](https://github.com/deepseek-ai/deepseek-harness)
14
14
  [![MIT License](https://img.shields.io/badge/license-MIT-536990?style=flat-square)](LICENSE)
15
15
 
16
16
  </div>
17
17
 
18
18
  ## 快速开始
19
19
 
20
- 需要 Node ≥ 22.16。两种调用方式任选(`npx` 前缀可替换下面任何 `dsh` 命令):
20
+ 需要 Node ≥ 22.16 DeepSeek Harness ≥ **0.1.2-alpha.1**(0.8.12 起仅支持
21
+ 0.1.2-alpha.x 宿主线;旧版插件请看 [历史版本](https://github.com/JunNanLYS/dsh-layered-memory/releases))。
22
+ 两种调用方式任选(`npx` 前缀可替换下面任何 `dsh` 命令):
21
23
 
22
24
  ```bash
23
- # 方式一:npx 直接跑官方 CLI(无需预装 dsh;可 pin 版本,如 dsh-layered-memory@0.8.4
24
- npx -y @deepseek-ai/dsh plugin --profile web add dsh-layered-memory
25
+ # 方式一:npx 直接跑官方 CLI(无需预装 dsh;宿主须带 alpha 版本号——npm latest 仍指向 0.1.1-rc.x
26
+ npx -y @deepseek-ai/dsh@0.1.2-alpha.2 plugin --profile web add dsh-layered-memory
25
27
 
26
- # 方式二:已装 dsh CLI(dsh pnpm 转发器,未装 pnpm 时先 npm i -g pnpm)
28
+ # 方式二:已装 dsh CLI(先升级到 alpha 线:npm i -g @deepseek-ai/dsh@0.1.2-alpha.2 并重启;
29
+ # dsh 是 pnpm 转发器,未装 pnpm 时先 npm i -g pnpm)
27
30
  dsh plugin --profile web add dsh-layered-memory
28
31
 
29
32
  # 包源备选:GitHub 仓库 / 本地路径(开发调试,link: 指向仓库,npm run build + 重启 dsh 即生效)
@@ -51,7 +54,8 @@ Agent 应当返回安装结果,并明确告诉你配置中是否已经出现 `
51
54
  本包声明了 `dsh.bundle` 组合包层(`cordis.patch.yml`),安装后会**自动挂载插件行**——
52
55
  不需要再手改 `$DSH_HOME/profiles/web/cordis.patch.yml`。然后重启 DeepSeek Harness,
53
56
  验证:`~/.dsh/memory/` 下出现 `conversations/ records/ scenes/` 目录和 `memory.db`
54
- 即插件 apply 成功;设置页出现"记忆"页面、输入栏出现档位 pill 即 client 半边就绪。
57
+ 即插件 apply 成功;设置页出现"记忆"页面(记忆工作台五区)、输入栏出现记忆芯片
58
+ (`记忆 · 智能`)即 client 半边就绪。
55
59
 
56
60
  **卸载**:`dsh plugin --profile web remove dsh-layered-memory` + 重启。数据保留在
57
61
  `~/.dsh/memory/`,不需要时手动删除整个目录即可。
@@ -78,7 +82,7 @@ npx tsc src/smoke.ts --outDir dist-smoke --module nodenext --moduleResolution no
78
82
 
79
83
  **成本看板**:每次蒸馏 LLM 调用(抽取/去重/L2/L3)的 token 成本按 `provider/model` 写入
80
84
  SQLite 明细表(保留期可配置,默认 365 天,写入时滚动清理;记账失败只告警、绝不阻塞蒸馏),
81
- 设置页 → 记忆 → **成本** Tab 可视化:按模型分色的趋势折线(日/周/月粒度 + 近 N 天窗口 +
85
+ 设置页 → 记忆 → 洞察 → **成本** 子页可视化:按模型分色的趋势折线(日/周/月粒度 + 近 N 天窗口 +
82
86
  L1/L2/L3 层级过滤)、层级 × 时间窗口表格(调用数 / 输出与思考 token / 均值 / 中位数)、
83
87
  按模型累计——蒸馏开销一目了然。输入按字符计(dsh 流式 usage 不含输入 token),
84
88
  输出与思考按 token 计。
@@ -112,34 +116,49 @@ L1/L2/L3 层级过滤)、层级 × 时间窗口表格(调用数 / 输出与
112
116
  ## 会话级记忆档位
113
117
 
114
118
  <p align="center">
115
- <img src="./assets/img/Modes.png" width="100%"
116
- alt="会话级记忆档位:一条玻璃胶囊滑轨四个停点(日常·工作·智能·关闭),发光圆球停在智能(默认)档;各档上方微场景——日常为个人聊天气泡、工作为代码文档窗格、智能为双流合流最亮、关闭为暗淡虚线幽灵泡">
119
+ <img src="./assets/img/MemoryChip.png" width="72%"
120
+ alt="深色主题下的会话记忆芯片与级联菜单:输入栏左簇 Read Only 芯片右侧是「记忆 · 智能 ▾」芯片;点击向上弹出圆角浮层菜单,两行「记忆范围 智能 ›」「数据流 跟随全局 ›」,数据流行右侧悬停出二级子面板列出 跟随全局✓/读写/只写/暂停 四个选项">
117
121
  </p>
118
122
 
119
- - **控件**:输入栏内、模式选择器右侧的 pill(`记忆·自动`),点击在上方浮出档位滑块深浅主题自适应;
120
- - 悬浮板下半部是**会话信息区**:召回命中(命中/检索轮次与累计条数)、攒批进度
121
- (本会话切片 x/生效阈值;关闭档显示挂起切片数)、本会话产出记忆条数、会话消息数,
122
- 外加异常状态行(存储降级 / 向量检索不可用)与全局摘要(待蒸馏条数、上次蒸馏时间);
123
- 数据走 `dsh-memory/session-stats` 端点(纯内存注册表 + 索引 COUNT,零文件 I/O),
124
- 打开期间自适应轮询(忙 2s / 5s),关闭即停;
125
- - 每会话的选择按 sessionId 持久化到 `session-modes.json`,重启/恢复会话不丢;
126
- 与全局开关叠加(全局是总闸);L2/L3 完全分类,分类内容不渗透。
127
- - **只写不读(#38)**:悬浮板内「注入」三态开关(跟随全局 / 开 / 关)——设为「关」
128
- 即**只写会话**:捕获与蒸馏照常(对话照常沉淀为 L0→L1→L2/L3),但不向本会话注入
129
- 任何记忆(召回注入、画像/导航稳定区、工具指南一并停止;`memory_search` 等读工具
130
- 返回只写提示)。pill 面文换作 `记忆·只写` 提示状态;覆盖按会话持久化,切回
131
- 「跟随全局」即清除、跟随设置页召回开关;适合调试/评测/敏感会话「只吸收不干扰」。
132
- off 档正交:off 仍是完全隐身(连捕获都关),只写保留「进」关「出」。
123
+ 会话侧是**分散式记忆面**——信息按类型住进宿主原生座位,插件不再有自有条带:
124
+
125
+ - **记忆芯片**(输入栏左簇、Read Only 芯片右侧):`记忆 · {智能|日常|工作}` 无边框
126
+ 芯片(官方 composer chip 语法),文案是解析真值——`记忆 · 只写`(注入关)、
127
+ `记忆 · 暂停`(灰点)、`记忆 · 降级`(琥珀点);zh/en 双语随宿主语言切换。
128
+ - **级联菜单**(点击芯片向上展开):两行 `记忆范围 {值} ›` / `数据流 {值} ›`;数据流
129
+ 选项在 **hover 二级子面板**(跟随全局/读写/只写/暂停),点击不固定、桥接热区保证
130
+ 慢速移动不断链;键盘通路齐备(方向键巡游 + focus 揭示)。
131
+ - **内联滑条**(点「记忆范围」原地展开):三停点 日常/工作/智能,拖拽跨档时**芯片文字
132
+ 实时联动**;键盘方向键/Home/End + `aria-valuetext`。
133
+ - **暂停恢复快照**:数据流切「暂停」进 off 档时记录暂停前范围与注入覆盖,恢复即原样
134
+ 还原;每会话选择持久化到 `session-modes.json`(与全局开关叠加,全局是总闸);
135
+ L2/L3 完全分类,分类内容不渗透。
136
+ - **记忆占用只住官方上下文环面板**:点开官方环即可见「记忆」分项小节(召回片段 /
137
+ 记忆稳定区);输入区没有任何占用 UI,只在官方统计行追加 `待蒸馏 N` 遥测段。
138
+ - **只写不读(#38)**:数据流选「只写」即**只写会话**——捕获与蒸馏照常(对话照常沉淀
139
+ 为 L0→L1→L2/L3),但不向本会话注入任何记忆(召回注入、画像/导航稳定区、工具指南
140
+ 一并停止;`memory_search` 等读工具返回只写提示)。覆盖按会话持久化,切回
141
+ 「跟随全局」即清除、跟随自动化区的召回开关;适合调试/评测/敏感会话「只吸收不干扰」。
142
+ 与暂停正交:暂停是完全隐身(连捕获都关),只写保留「进」关「出」。
133
143
 
134
144
  ## 界面预览
135
145
 
136
146
  <p align="center">
137
- <img src="./assets/img/ui-dark.jpg" width="49.5%"
138
- alt="深色主题下的设置页记忆浏览器概览:状态卡(插件版本、捕获/蒸馏/召回开关状态、FTS 与向量能力、L1 记忆计数、蒸馏模型)与统计瓦片,玻璃质感控件与冷蓝强调色">
139
- <img src="./assets/img/ui-light.jpg" width="49.5%"
140
- alt="浅色主题下的同一设置页记忆浏览器概览:同款布局与信息,浅色卡片底与同套强调色,主题切换无需重载">
147
+ <img src="./assets/img/ui-dark.png" width="49.5%"
148
+ alt="深色主题下的记忆工作台总览:五区任务导航(总览选中),健康摘要卡(运行正常 + 存储/向量检索/蒸馏队列子系统标签 + 待蒸馏注意提示)、最近活动列表(新增/更新动词标签 + 记忆/场景层标签 + 相对时间)、关键数字瓦片(记忆资产/场景/本周蒸馏输出/上次蒸馏)与四区跳转按钮">
149
+ <img src="./assets/img/ui-light.png" width="49.5%"
150
+ alt="浅色主题下的同一记忆工作台总览:同款五区导航与健康摘要/最近活动/关键数字布局,浅色卡片底与同套品牌蓝强调色,主题切换无需重载">
141
151
  </p>
142
152
 
153
+ 设置 → 记忆 是**记忆工作台**(五区任务导航,sticky 标签 + 箭头键巡游):
154
+
155
+ - **总览**:健康摘要 + 最近活动 + 关键数字 + 引导式空状态;
156
+ - **记忆库**:L1 记忆 / L2 场景 / L3 画像按更新时间混排的**只读资产活动流**
157
+ (搜索 + 类型/范围/时间筛选 + 原位展开 + 复制);
158
+ - **自动化**:基础开关 + 高级披露(蒸馏路由链与预算)+ 嵌入模型披露;
159
+ - **洞察**:成本 / 活动(近 7 天资产活动 + 蒸馏调用失败)/ 召回(累计与停用分布);
160
+ - **维护**:运行健康 + 诊断日志 + 危险区全量重建(二次确认 + 进度 + 可取消)。
161
+
143
162
  ## 实测对比(DSH-MemBench:自动化基准)
144
163
 
145
164
  图文回答"长什么样",这一节用**自动化基准**的实测数字回答"**开了到底有什么用**"([`bench/`](./bench/),一条命令可复现)。方法:同场景库、逐字相同输入,**A 组(记忆开)跑 3 次取合并值,B 组(记忆关)跑 1 次**(无记忆的长任务每场景要吞数倍 token,成本护栏);对话赛道只跑 A 组(B 组会话独立无记忆必然失败,对照无信息量,已下线)。对话赛道环境:DeepSeek 官方 `deepseek-v4-flash`、插件 0.8.5(判卷与被测同源,答案原文全部留痕可人工复核)、Windows;题型设计借鉴 [LongMemEval](https://github.com/xiaowu0162/longmemeval) / [LoCoMo](https://snap-research.github.io/locomo/) / [AMB](https://github.com/vectorize-io/agent-memory-benchmark),扩展题型与生命周期赛道参照 [MemoryAgentBench](https://arxiv.org/abs/2507.05257) / [GoodAI LTM](https://github.com/GoodAI/goodai-ltm-benchmark) / BEAM。
@@ -201,7 +220,7 @@ node bench/harness/retrieval-metrics.mjs <runDir> --flood 200,600
201
220
 
202
221
  ## 语义检索(嵌入源)
203
222
 
204
- 设置页(记忆 → 概览语义检索)选择嵌入源,即时生效、无需改配置重启:
223
+ 设置页(记忆 → 自动化嵌入模型)选择嵌入源,即时生效、无需改配置重启:
205
224
 
206
225
  <p align="center">
207
226
  <img src="./assets/img/EmbeddingSource.png" width="70%"
@@ -281,10 +300,10 @@ ONNX 量化 **CPU 推理**——无需 API Key,数据不出本机)。本地
281
300
  | `embedding.allowLocalModels` | `true` | 允许本地嵌入档(部署上限:关闭后设置页不能下载模型、不能切本地档) |
282
301
  | `embedding.mirror` | `https://hf-mirror.com` | 本地模型下载镜像根地址(可改回官方 `https://huggingface.co`) |
283
302
  | `embedding.proxy` | `''` | 模型下载代理三态:`''`(默认)= 自动探测代理环境变量(`HTTPS_PROXY`/`ALL_PROXY` 等,尊重 `NO_PROXY`);`none` = 禁用强制直连;其他值 = 代理 URL(如 `http://127.0.0.1:7890`)。镜像直连在国内网络间歇不可达(直连超时与污染字节交替出现过),开代理的机器建议保持默认自动探测 |
284
- | `llm.provider/model` | 空 | 蒸馏模型静态路由(部署 pin):provider 与 model **双字段齐**时锁定蒸馏路由,优先于设置页的运行时路由链与默认模型(部署可强制蒸馏走指定路由);留空则跟随"设置页路由链主路由 → 默认模型"。运行时可在设置页 → 记忆 → 概览蒸馏参数的**蒸馏路由链编辑器**里配置主路由与回退链(从**已配置的供应商**(含 dsh 设置 → 模型里添加的自定义供应商)中选择,主路由行可留空跟随默认模型),非空即整体接管本静态配置,即时生效无需重启 |
303
+ | `llm.provider/model` | 空 | 蒸馏模型静态路由(部署 pin):provider 与 model **双字段齐**时锁定蒸馏路由,优先于设置页的运行时路由链与默认模型(部署可强制蒸馏走指定路由);留空则跟随"设置页路由链主路由 → 默认模型"。运行时可在设置页 → 记忆 → 自动化高级路由与预算的**蒸馏路由链编辑器**里配置主路由与回退链(从**已配置的供应商**(含 dsh 设置 → 模型里添加的自定义供应商)中选择,主路由行可留空跟随默认模型),非空即整体接管本静态配置,即时生效无需重启 |
285
304
  | `llm.fallbacks` | `[]` | 蒸馏回退链:主路由失败(报错/被掐断/网络异常/**空输出**)后按条目顺序逐个降级尝试的备用路由列表,条目 = `{provider, model, reasoningEffort?}`(档位非空覆盖全局 `llm.reasoningEffort`,仍按模型能力钳制);与主路由完全相同的条目自动跳过;**每条路由各享全额 `timeoutMs`**;全部失败交既有按会话退避重试。空数组(缺省)= 单路由行为不变(详见下方[蒸馏回退链与慢 TTFT 模型](#蒸馏回退链与慢-ttft-模型));设置页运行时路由链(`distillChain`)非空时**整体接管**主路由与回退链(单行链 = 显式无回退),空 = 跟随本配置 |
286
305
  | `llm.layerRoutes` | `{}` | 蒸馏**按层路由**:层键 `l1`/`l2`/`l3` 各配一条**完整链**(条目同 `llm.fallbacks`,**头行必须 provider+model 双显式**),非空即**完整替换**该层解析(该层主路由与回退都归层链管,全局链对该层不参与),空/缺省 = 该层跟随全局;`l1` 同管抽取+去重两个调用点。运行时可在设置页「蒸馏参数」分段面板里按层编辑(优先于本静态配置);部署 pin 不废静态层链(同为部署配置,同回退链先例)。与回退链正交可组合——每层各自一条链(ADR-0005) |
287
- | `llm.maxTokens` | `65536` | 未分层调用的兜底输出总闸。各蒸馏层有独立预算(抽取 16k / 去重 8k / L2 32k / L3 16k;思考档 high/xhigh/max 时自动 ×4,防 reasoning 吃光预算),分层预算可在设置页 → 记忆 → 概览蒸馏参数运行时调整(留空/0 = 跟随内置默认) |
306
+ | `llm.maxTokens` | `65536` | 未分层调用的兜底输出总闸。各蒸馏层有独立预算(抽取 16k / 去重 8k / L2 32k / L3 16k;思考档 high/xhigh/max 时自动 ×4,防 reasoning 吃光预算),分层预算可在设置页 → 记忆 → 自动化高级路由与预算运行时调整(留空/0 = 跟随内置默认) |
288
307
  | `llm.reasoningEffort` | 空 | 蒸馏思考档位:空串 = **自动**(按模型能力解析:模型默认档 → `high`);显式值(`off`/`none`/`minimal`/`low`/`medium`/`high`/`xhigh`/`max`)仅在该模型声明支持时发送——跨供应商 effort 词汇表不同(deepseek 认 `off`,OpenAI 系是 `none`,未声明档位的模型不传),不支持的档位自动降级为不传并告警一次;思考档 high/xhigh/max 时输出预算自动 ×4。运行时可在设置页路由链编辑器里**逐路由**覆盖档位(行内下拉,词表按各模型声明的能力实时显示,缺省跟随本值) |
289
308
  | `llm.temperature` | `0.3` | 蒸馏温度 |
290
309
  | `llm.maxInputChars` | `700000` | 单次蒸馏输入字符预算(超限的 L1 输入自动分块抽取);运行时可在设置页 → 蒸馏参数 → 输入预算调整(留空/0 = 跟随本值) |
@@ -297,7 +316,7 @@ ONNX 量化 **CPU 推理**——无需 API Key,数据不出本机)。本地
297
316
 
298
317
  部分推理供应商的免费/慢速档位**首 token 延迟(TTFT)可达 20 秒以上**,而部分上游网关会在连接静默约 20 秒时掐断——蒸馏调用以固定 ~20s 失败(`llm aborted`),插件侧 120s 超时根本轮不到生效([#31](https://github.com/JunNanLYS/dsh-layered-memory/issues/31) 的实测场景)。三层缓解按需取用:
299
318
 
300
- 1. **换路由**(最直接):设置页 → 记忆 → 概览蒸馏参数的路由链编辑器即时改主路由(或把快路由排到链首),或静态 pin `llm.provider`/`llm.model`。
319
+ 1. **换路由**(最直接):设置页 → 记忆 → 自动化高级路由与预算的路由链编辑器即时改主路由(或把快路由排到链首),或静态 pin `llm.provider`/`llm.model`。
301
320
  2. **回退链**(自动降级):主路由失败时按序自动换备用路由,无需人工干预:
302
321
 
303
322
  ```yaml
@@ -331,11 +350,11 @@ ONNX 量化 **CPU 推理**——无需 API Key,数据不出本机)。本地
331
350
  reasoningEffort: high
332
351
  ```
333
352
 
334
- 也可在设置页 → 记忆 → 概览蒸馏参数的**分段面板**(全局默认 / L1 / L2 / L3)
353
+ 也可在设置页 → 记忆 → 自动化高级路由与预算的**分段面板**(全局默认 / L1 / L2 / L3)
335
354
  里按层运行时编辑,层内优先级:运行时层链 > 本 YAML 静态层链 > 全局默认链,
336
355
  逐级兜底。
337
356
 
338
- 失败 = 报错 / 被掐断 / 网络异常 / **空输出**(流正常结束但 0 字符——对蒸馏而言必然在解析阶段报废,改判为该路由失败而非返回空串);调用方主动取消不降级;每条路由各享**全额** `llm.timeoutMs`(共享预算会让慢 TTFT 的回退路由拿到的窗口小于它真实需要的首包时间,回退链形同虚设);token 成本逐次尝试记账(失败尝试也计一行,含流中断前已到的 token),成功调用归因到实际服务的路由。路由链也可在设置页 → 记忆 → 概览蒸馏参数的「蒸馏路由链」编辑器里运行时调整(无需改配置重启);本 YAML 适合部署者固化静态链。
357
+ 失败 = 报错 / 被掐断 / 网络异常 / **空输出**(流正常结束但 0 字符——对蒸馏而言必然在解析阶段报废,改判为该路由失败而非返回空串);调用方主动取消不降级;每条路由各享**全额** `llm.timeoutMs`(共享预算会让慢 TTFT 的回退路由拿到的窗口小于它真实需要的首包时间,回退链形同虚设);token 成本逐次尝试记账(失败尝试也计一行,含流中断前已到的 token),成功调用归因到实际服务的路由。路由链也可在设置页 → 记忆 → 自动化高级路由与预算的「蒸馏路由链」编辑器里运行时调整(无需改配置重启);本 YAML 适合部署者固化静态链。
339
358
  4. **调高超时**:`llm.timeoutMs` 只在路由确实慢但网关不掐时有用;网关 20s 掐断的场景调插件超时无效,请用前两层。
340
359
 
341
360
  ## 日志与故障排查
Binary file
Binary file
Binary file