dsh-hypatia-auto-memory 1.1.1 → 2.1.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 +90 -17
- package/README.zh-CN.md +67 -16
- package/lib/client.js +488 -17
- package/lib/client.js.map +1 -1
- package/package.json +2 -1
- package/skills/hypatia-memory/SKILL.md +47 -3
- package/src/cascade.js +39 -19
- package/src/client/MemoryView.module.css +38 -1
- package/src/client/MemoryView.tsx +94 -1
- package/src/client/SettingsCard.module.css +83 -0
- package/src/client/SettingsCard.tsx +160 -15
- package/src/client/consolidation-models.ts +151 -1
- package/src/client/index.tsx +2 -2
- package/src/client/locales.ts +48 -4
- package/src/client/memory-client.ts +96 -0
- package/src/config.js +20 -3
- package/src/consolidator.js +245 -115
- package/src/index.js +70 -9
- package/src/memory-api.js +81 -7
- package/src/model-log.js +64 -15
- package/src/on-demand.js +249 -0
- package/src/reasoning.js +209 -0
- package/src/writer.js +7 -3
package/README.md
CHANGED
|
@@ -101,6 +101,8 @@ collect fiber dispose ──▶ the same flush for every session still open, per
|
|
|
101
101
|
before the queue stops; the model call finishes at the next start
|
|
102
102
|
(a restart, a config-change restart, or a crash: the startup
|
|
103
103
|
consolidation backfill)
|
|
104
|
+
explicit request ──▶ /hypatia-summarize (no model turn) | hypatia_summarize |
|
|
105
|
+
the Memory tab button ──▶ the same consolidate task, thresholds waived
|
|
104
106
|
agent/created ──▶ rules/taboos inject()
|
|
105
107
|
```
|
|
106
108
|
|
|
@@ -205,12 +207,50 @@ agent/created ──▶ rules/taboos inject()
|
|
|
205
207
|
long. Cascade is the exception: it keeps thinking (it is compressing sixteen
|
|
206
208
|
summaries, not filling a shape) and pays for it in the same allowance.
|
|
207
209
|
|
|
210
|
+
A route's `reasoningEffort` overrides all of that. Set it to a level the model
|
|
211
|
+
declares and every call on that route is sent that level — the allowance
|
|
212
|
+
follows, because anything other than `off` shares the cap with the answer. The
|
|
213
|
+
vocabulary is the adapter's own (the settings card offers exactly what the
|
|
214
|
+
model declares: `off`, `low`, `high`, `max` on `llm-deepseek`), and a value the
|
|
215
|
+
route does not declare is **dropped with one warning** rather than sent: the
|
|
216
|
+
core rejects an undeclared effort before any provider I/O, so sending it would
|
|
217
|
+
turn a profile typo into a task that retries and then fails permanently. Leave
|
|
218
|
+
the field out to keep the per-purpose policy above.
|
|
219
|
+
|
|
208
220
|
- **Cascade** archives sixteen unarchived tier-N summaries into one tier-(N+1)
|
|
209
221
|
entry, using hypatia's own `$not-summaried` (which anti-joins on
|
|
210
222
|
`statement.tail` and orders `created_at ASC`, giving FIFO batching for free).
|
|
211
223
|
This is the protocol's log₁₆(n) archive: each tier compresses only material a
|
|
212
224
|
previous tier already distilled.
|
|
213
225
|
|
|
226
|
+
- **Consolidation can also be requested explicitly.** Three faces call one host
|
|
227
|
+
action: the slash command **`/hypatia-summarize`** (in the composer's `/` menu,
|
|
228
|
+
executed by the UI *without a model turn*), the **`hypatia_summarize`** tool
|
|
229
|
+
(the `hypatia-memory` skill's execution path, so the agent never hand-writes a
|
|
230
|
+
`sum-*` entry), and a **Summarise now** button on the Memory tab, which sits on
|
|
231
|
+
the row that reports how far behind the session is — the moment a reader wants
|
|
232
|
+
it. A request cuts the span `[lastConsolidatedSeq, min(seq, lastLoggedSeq))`
|
|
233
|
+
**at that moment** and queues the same `consolidate` task the automatic trigger
|
|
234
|
+
queues: same kind, same executor, same idempotent writer. `checkEveryTurns` and
|
|
235
|
+
`minNewTokens` are deliberately bypassed — they pace an ongoing conversation,
|
|
236
|
+
and an explicit request is the reason to cut now. Measured live, a session sat
|
|
237
|
+
151 logged events behind while the token floor had accumulated 645 of 3000, so
|
|
238
|
+
waiting for the gate is not an answer to accept. The config switches are *not*
|
|
239
|
+
bypassed, and a request is only a cut, never a freeze: events appended
|
|
240
|
+
afterwards wait for the next trigger as usual.
|
|
241
|
+
|
|
242
|
+
"Nothing left to summarise" and "this span is already queued" are reported as
|
|
243
|
+
such rather than queued again, so asking twice does not produce two summaries —
|
|
244
|
+
and because the writer is idempotent by name, even a race cannot. The button is
|
|
245
|
+
**disabled but kept in place** once the session is caught up: hiding it would
|
|
246
|
+
make the user rediscover it every time the session falls behind, which is
|
|
247
|
+
exactly when they look for it. It rides the Memory tab's own route family,
|
|
248
|
+
whose first **write** route (`POST /consolidate`) keeps all three
|
|
249
|
+
self-authentication checks the read routes use — loopback socket, loopback
|
|
250
|
+
`Host`, same-origin — and refuses anything but `POST`, so the write cannot be
|
|
251
|
+
triggered by a link or an image tag. Nothing on any of the three faces runs a
|
|
252
|
+
model call in the main session.
|
|
253
|
+
|
|
214
254
|
- **Work units** are adjudicated, not guessed. Candidates come from `similar`
|
|
215
255
|
(the only search that reports a distance), with the operational layer left
|
|
216
256
|
out — in the query, by `--exclude-tags`, on a binary that has it (hypatia
|
|
@@ -315,7 +355,7 @@ agent/created ──▶ rules/taboos inject()
|
|
|
315
355
|
verbatim, the reader just wrote them, and the agent is the right reader for
|
|
316
356
|
them.
|
|
317
357
|
|
|
318
|
-
The data arrives over a session-scoped
|
|
358
|
+
The data arrives over a session-scoped HTTP route family this plugin
|
|
319
359
|
registers on the DSH web server, `/api/dsh-hypatia-auto-memory/session`: status
|
|
320
360
|
from the in-memory state tables on every poll, the shelf content only when it
|
|
321
361
|
can have changed (on open, on a session switch, on a manual refresh, and when
|
|
@@ -325,7 +365,10 @@ agent/created ──▶ rules/taboos inject()
|
|
|
325
365
|
and 120 000 per response. JSE has no `ORDER BY`, so "newest" is decided only
|
|
326
366
|
after reading every candidate; when a scan reaches its cap the counts become
|
|
327
367
|
lower bounds, and the tab reports that response as truncated rather than
|
|
328
|
-
leaving a short list unexplained.
|
|
368
|
+
leaving a short list unexplained. The family's one **write** route,
|
|
369
|
+
`POST /consolidate`, is what the **Summarise now** button calls; it writes
|
|
370
|
+
nothing itself — it queues the same consolidation task the automatic trigger
|
|
371
|
+
queues (see *On-demand* above).
|
|
329
372
|
Two channels were rejected on the way here. A **session projection** would be
|
|
330
373
|
the best shape, but the event that would drive it cannot be written from
|
|
331
374
|
outside this repository: `Session.append` offers no way to set the envelope's
|
|
@@ -357,7 +400,26 @@ takes whichever seat exists and moves between them without ever appearing twice
|
|
|
357
400
|
save, without a profile reload. Stored as the plugin entry's `config:` in the
|
|
358
401
|
profile patch; a pre-0.1.7 `settings.yaml` section is imported once on first
|
|
359
402
|
launch. All fields optional, defaults shown. A blank consolidation route is
|
|
360
|
-
refused on save; a duplicate one is dropped at runtime with a warning
|
|
403
|
+
refused on save; a duplicate one is dropped at runtime with a warning.
|
|
404
|
+
|
|
405
|
+
**`consolidation.models` is a priority order, not a rotation.** A task's first
|
|
406
|
+
attempt takes the first route, and each retry degrades to the next one, reusing
|
|
407
|
+
the last route past the end. Two consequences are worth knowing: the chain a
|
|
408
|
+
task can reach is `min(queue.maxAttempts, models.length)` long — with the
|
|
409
|
+
default `maxAttempts: 3` a fourth and later route is never used — and each
|
|
410
|
+
downgrade costs one `queue.retryDelayMs` (5 s), because it happens on a retry
|
|
411
|
+
rather than inside one attempt. This replaced a round-robin that used every
|
|
412
|
+
selected route regardless of preference; reorder the list, or use the card's
|
|
413
|
+
↑/↓ buttons, to change which route is preferred.
|
|
414
|
+
|
|
415
|
+
**Each route may also set a thinking level.** `reasoningEffort` names an
|
|
416
|
+
adapter-owned level — the settings card offers exactly what the model declares,
|
|
417
|
+
which on `llm-deepseek` is `off`/`low`/`high`/`max`. Omitting it keeps the
|
|
418
|
+
per-purpose policy described under *Consolidator* above; setting it overrides
|
|
419
|
+
that policy for every call on that route, and the output cap grows to pay for
|
|
420
|
+
it. A level the model does not declare is dropped at call time with one warning
|
|
421
|
+
instead of being sent, because the core rejects an undeclared effort before any
|
|
422
|
+
provider I/O.
|
|
361
423
|
|
|
362
424
|
```yaml
|
|
363
425
|
hypatia-auto-memory:
|
|
@@ -373,8 +435,12 @@ hypatia-auto-memory:
|
|
|
373
435
|
toolLedger: false # off by default; true records a compact tool-call ledger
|
|
374
436
|
consolidation:
|
|
375
437
|
enabled: true
|
|
376
|
-
#
|
|
438
|
+
# PRIORITY order: every consolidation tries the first route, and a retry
|
|
439
|
+
# degrades to the next one (see the note above).
|
|
377
440
|
models: [] # select one or more { provider, model } routes
|
|
441
|
+
# Each route may also carry `reasoningEffort: <level>` — a thinking level the
|
|
442
|
+
# card offers from what the model declares (off/low/high/max on deepseek).
|
|
443
|
+
# Omit it to keep the per-purpose policy.
|
|
378
444
|
maxInputTokens: 16000 # transcript cap (chars/4 estimate)
|
|
379
445
|
maxOutputTokens: 2000
|
|
380
446
|
timeoutMs: 120000
|
|
@@ -389,8 +455,8 @@ hypatia-auto-memory:
|
|
|
389
455
|
batchSize: 16 # entries per tier before archiving one tier up
|
|
390
456
|
queue:
|
|
391
457
|
concurrency: 1 # parallel sessions
|
|
392
|
-
maxAttempts: 3
|
|
393
|
-
retryDelayMs: 5000
|
|
458
|
+
maxAttempts: 3 # retries per task; also caps the model fallback chain
|
|
459
|
+
retryDelayMs: 5000 # and the delay before the next route is tried
|
|
394
460
|
# NOT a batching knob. Spans are cut at turn/end; for a turn still running
|
|
395
461
|
# this long, the next step/end flushes what is complete so far.
|
|
396
462
|
flushWindowMs: 120000
|
|
@@ -474,14 +540,19 @@ now only projects plugin Config forms.)
|
|
|
474
540
|
5. Which model a background call actually ran on is recorded per attempt in
|
|
475
541
|
`~/.dsh/storages/hypatia_auto_memory_diag.json` (`tables.model_calls`,
|
|
476
542
|
newest first by `seq`, bounded to 100). The settings list is not the answer:
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
543
|
+
`consolidation.models` is a priority order, and one task's rows walk it —
|
|
544
|
+
the first attempt names the head, a retry names the next route down, which
|
|
545
|
+
is how a fallback is confirmed to have happened. `effort` is what the call
|
|
546
|
+
actually SENT, which is not the configured value when the route refused it;
|
|
547
|
+
`outputTokens` / `reasoningTokens` are the route's own usage report, and they
|
|
548
|
+
are what says whether the cap went to the answer or to the reasoning (both 0
|
|
549
|
+
when the route reports no usage). `outcome` is `pending` (in flight, or the
|
|
550
|
+
process died mid-call), `ok` (a usable result was produced), `incomplete`
|
|
551
|
+
(the call settled without one — output cap, abort, unparseable reply) or
|
|
552
|
+
`error` (the call threw); in the last two the attempt was spent and nothing
|
|
553
|
+
was stored. `detail` is a finish kind, a fixed label, or the provider's
|
|
554
|
+
message, capped at 200 characters — never message content and never model
|
|
555
|
+
output.
|
|
485
556
|
6. `hypatia backfill --status` reports the shelf's embedding debt. With a
|
|
486
557
|
current hypatia, logging a turn adds nothing to it; only summaries and work
|
|
487
558
|
units are pending until the next flush. `hypatia scope list --count` shows
|
|
@@ -500,7 +571,7 @@ now only projects plugin Config forms.)
|
|
|
500
571
|
| Entries appear only after a turn finishes | By design: spans are cut at `turn/end`, or at the first `step/end` once a turn has run longer than `flushWindowMs`, so an entry never lacks its own tool results |
|
|
501
572
|
| Logging works, no summaries | `consolidation.models` is empty or invalid — one warning at first trigger |
|
|
502
573
|
| Summaries but no `sum2-*` | Fewer than `cascade.batchSize` unarchived tier-1 summaries in that project yet |
|
|
503
|
-
| Work units have no relationships | No embedding model on the shelf (`similar` fails), every candidate was beyond `dedupMaxDistance`, or the adjudication call itself produced no verdict — `model_calls` shows that as `incomplete` / `max-tokens`, which is what a thinking-enabled route does when it spends the whole output budget on reasoning before answering. The call now asks a route for `reasoningEffort: 'off'` once the adapter advertises it, and carries a 1024-token cap for routes that cannot honour it. Since hypatia #19 the local model is looked up in `~/.hypatia/models/<org>/<name>` (or the Hugging Face cache), no longer beside the shelf: a shelf that used to answer `similar` and now says `is not installed` needs `hypatia model install <model>`, or `hypatia model register <model> <dir>` pointing at the files it already has |
|
|
574
|
+
| Work units have no relationships | No embedding model on the shelf (`similar` fails), every candidate was beyond `dedupMaxDistance`, or the adjudication call itself produced no verdict — `model_calls` shows that as `incomplete` / `max-tokens`, which is what a thinking-enabled route does when it spends the whole output budget on reasoning before answering. The call now asks a route for `reasoningEffort: 'off'` once the adapter advertises it, and carries a 1024-token cap for routes that cannot honour it — a route whose `reasoningEffort` names a real level instead gets that 1024 plus the thinking allowance. Since hypatia #19 the local model is looked up in `~/.hypatia/models/<org>/<name>` (or the Hugging Face cache), no longer beside the shelf: a shelf that used to answer `similar` and now says `is not installed` needs `hypatia model install <model>`, or `hypatia model register <model> <dir>` pointing at the files it already has |
|
|
504
575
|
| `similar` still returns `msg-*` rows | They were written before this plugin opted the log layer out of embedding, or by a hypatia without `--no-embed`. Retract the knowledge vectors once with `embedding.skip_tags = ["message"]` in the shelf's `shelf.toml` followed by `hypatia backfill` (a binary older than the key refuses to open the shelf, so upgrade every binary sharing it first). Do not add `session` to that list: `skip_tags` matches any entry carrying the tag, and knowledge people write about sessions carries it too — one such entry lost its vector on the shelf this was tried on. The plugin's own `session-*` nodes are few and now opt out per write. Statements have no tags and no update command, so `belongTo` / `summary` edges written before keep their vectors |
|
|
505
576
|
| Task in `deferred` state | Neither the live store nor persistence could supply its session. Not an error: it spends no attempts and runs as soon as one of them can. Normally storage answers immediately — the state persists only when `sessionPersistence` is absent from the composition, or its read failed (look for `could not be read` in the log). A deferred consolidation is re-queued by the next startup's backfill, so it does not wait for its session to be opened again |
|
|
506
577
|
| Task in `failed` state | A hypatia or model error persisted after `maxAttempts`. Failed `log-message` records are pruned at the next startup, since the watermark re-derives their range; other kinds are kept for inspection — delete one to let the next trigger re-create it |
|
|
@@ -512,7 +583,8 @@ now only projects plugin Config forms.)
|
|
|
512
583
|
| Duplicate `msg-*` after weird manual edits | Delete the entry in hypatia and lower `lastLoggedSeq` for that session in the state domain — backfill recreates it once |
|
|
513
584
|
| The agent's own `hypatia` write still asks for approval | `autoApprove: false`, the command pipes/redirects/chains outside quotes, or its first word is not one of `binaries` — only plain calls are answered, by design. Reads never reach approval at all, so nothing to fix there |
|
|
514
585
|
| The agent got a memory protocol that tells it to log messages by hand | Another plugin registered `hypatia-memory` first (`dsh-hypatia` still in the profile), or a `hypatia-memory` exists on disk — typically `~/.agents/skills/hypatia-memory`, written by `hypatia skill install --agent codex`. Agent presets load disk skills in a layer nearer than plugins, so it wins in sessions even though the skill center may list this plugin. Remove the copy the startup warning names |
|
|
515
|
-
| Can't tell which model actually ran |
|
|
586
|
+
| Can't tell which model actually ran | The list is a priority order, so a task's first attempt names the head of `consolidation.models` and a retry names the route it degraded to. Each attempt lands in `~/.dsh/storages/hypatia_auto_memory_diag.json` (`model_calls`) with purpose, provider/model, the `effort` actually sent, outcome, duration and the route's own token split — reading one task's rows top-down is how a fallback is confirmed, and `effort` is how a configured thinking level is confirmed to have been honoured. The usage ledger cannot answer this — it folds agent turns (`assistant/message`) and never sees a plugin's direct `llm.stream`. A `pending` row means the call is still in flight, or the process died mid-call: the row is written before the call and settled after it |
|
|
587
|
+
| A configured `reasoningEffort` seems to do nothing | The route does not declare that level, so the call drops it rather than risk the core's `UNSUPPORTED_REASONING_EFFORT`. One warning names the route, the value and what the route does declare; `model_calls.effort` then reads empty for those attempts. The card offers exactly the levels the model advertises, so this normally only happens after editing the profile by hand or pointing the route at a different model |
|
|
516
588
|
| Startup warnings never appear in the terminal | `dsh web` mounts no log exporter, so plugin log lines of any level go nowhere; the console exporter's default threshold would also drop warnings (warn is level 2, above info's 1). Mount a logger such as `dsh-logbook` or `dsh-boot-doctor` temporarily to read them |
|
|
517
589
|
| Project scope looks wrong | Scope = basename of the session cwd's git top level (`git rev-parse --show-toplevel`), or of the cwd itself when git finds no work tree or cannot answer (not installed, a repo it refuses as unsafe). A `rev-parse` slower than 3 s leaves that one session on the cwd's own name. Two same-named checkouts share a scope by design; a linked worktree is scoped by its own directory, not the main checkout's; git resolves symlinks, so a checkout opened through a link named differently from its target gets the target's name. A name hypatia would rewrite is normalized first: a session at `/` is scoped `/`, commas become `_`, surrounding whitespace goes. Entries written before these fixes stay where they were stored: a subdirectory session's under that directory's name (`repo/src` wrote under `src` — the git root was never read), a session at `/` with no scope, `a,b` under both `a` and `b`, `foo,` under `foo` and global. Padded names were stored trimmed, which is what the query now asks for. Nothing migrates them; `knowledge-update --scopes` moves one entry and keeps its `created_at`. `hypatia scope list --count` (hypatia #30) shows every spelling in use and how many entries each holds |
|
|
518
590
|
|
|
@@ -611,7 +683,8 @@ dsh-hypatia-auto-memory/
|
|
|
611
683
|
│ ├── cascade.js # log₁₆(n) hierarchical summary archive
|
|
612
684
|
│ ├── model-log.js # bounded record of which model each attempt ran on
|
|
613
685
|
│ ├── memory-status.js # per-session fold of the two state tables (pure)
|
|
614
|
-
│ ├── memory-api.js # Memory tab + shelf listing
|
|
686
|
+
│ ├── memory-api.js # Memory tab + shelf listing; its one write route too
|
|
687
|
+
│ ├── on-demand.js # /hypatia-summarize + the hypatia_summarize tool
|
|
615
688
|
│ ├── recall.js # rules/taboos preload at session start
|
|
616
689
|
│ ├── auto-approve.js # approves the agent's own plain bash hypatia calls
|
|
617
690
|
│ ├── skills.js # bundled skill registration (never shadows another provider)
|
package/README.zh-CN.md
CHANGED
|
@@ -87,6 +87,8 @@ session/disposed ──▶ final flush + consolidation, thresholds waived (DSH
|
|
|
87
87
|
collect fiber 拆除 ──▶ 对每个仍打开的会话做同样的收尾,并在队列停止前落盘;模型
|
|
88
88
|
调用留到下次启动完成(重启、配置变更重启,或崩溃:启动时的
|
|
89
89
|
consolidation backfill)
|
|
90
|
+
显式请求 ──▶ /hypatia-summarize(不过模型)| hypatia_summarize | 记忆标签页按钮
|
|
91
|
+
──▶ 同一个 consolidate 任务,但跳过阈值
|
|
90
92
|
agent/created ──▶ rules/taboos inject()
|
|
91
93
|
```
|
|
92
94
|
|
|
@@ -163,11 +165,37 @@ agent/created ──▶ rules/taboos inject()
|
|
|
163
165
|
正是工作单元让回复变长。Cascade 是例外:它保留 thinking(它是在压缩十六条摘要,不是
|
|
164
166
|
在填一个形状),并用同一份余量来支付。
|
|
165
167
|
|
|
168
|
+
路由上的 `reasoningEffort` 覆盖以上全部策略。把它设成该模型声明过的档位,这条路由上的
|
|
169
|
+
每次调用都会带上它,预算余量也随之生效(任何非 `off` 的档位都与正文共用上限)。档位
|
|
170
|
+
词表属于适配器自己(设置卡片只列出该模型声明的档位:`llm-deepseek` 上是
|
|
171
|
+
`off`/`low`/`high`/`max`);而**路由没有声明的值会被丢弃并记一条警告**,不会发出去——
|
|
172
|
+
核心会在任何 provider I/O 之前拒绝未声明的档位,发出去等于把一处配置笔误变成反复重试
|
|
173
|
+
后永久失败的任务。留空即保持上面的按用途策略。
|
|
174
|
+
|
|
166
175
|
- **Cascade** 把十六个未归档的 tier-N 摘要归档为一条 tier-(N+1) 条目,用 hypatia 自带
|
|
167
176
|
的 `$not-summaried`(它对 `statement.tail` 做反连接并按 `created_at ASC` 排序,
|
|
168
177
|
免费得到 FIFO 分批)。这就是协议的 log₁₆(n) 归档:每一层只压缩上一层已经蒸馏过的
|
|
169
178
|
内容。
|
|
170
179
|
|
|
180
|
+
- **也可以显式要求立刻整合。** 三个面对应同一个 host 侧动作:斜杠命令
|
|
181
|
+
**`/hypatia-summarize`**(在 composer 的 `/` 菜单里,由 UI 执行、**不过模型**)、工具
|
|
182
|
+
**`hypatia_summarize`**(`hypatia-memory` 技能的执行路径,Agent 因此不会自己手写
|
|
183
|
+
`sum-*` 条目),以及记忆标签页上的**「立即整理」按钮**——它就在报告「落后多少」的那
|
|
184
|
+
一行上,正是读者需要它的时刻。一次请求按**触发那一刻**的水位切出
|
|
185
|
+
`[lastConsolidatedSeq, min(seq, lastLoggedSeq))`,入队与自动触发**完全相同**的
|
|
186
|
+
`consolidate` 任务:同一种 kind、同一个执行器、同一个幂等写手。`checkEveryTurns` 与
|
|
187
|
+
`minNewTokens` 被刻意绕过——它们用来给进行中的对话定节奏,而显式请求本身就是「现在
|
|
188
|
+
就切」的理由。实测中一个会话落后 151 个事件,而门槛只累计到 3000 中的 645,所以
|
|
189
|
+
「等闸门开」不是一个应当被接受的答案。配置开关**不**绕过;而且请求只是一次切分,
|
|
190
|
+
不是冻结:之后产生的事件照旧等下一次触发。
|
|
191
|
+
|
|
192
|
+
「没有需要整理的内容」和「这个区间已经入队」会如实回报而不是再入队一次,所以问两遍
|
|
193
|
+
不会产生两条摘要——何况写手按名字幂等,即使并发也不会。结清后按钮**禁用但保留在原
|
|
194
|
+
位**:隐藏它会让用户每次落后时都要重新发现它,而那正是他们找它的时候。它走记忆标签
|
|
195
|
+
页自己的路由族,其中第一条**写**路由(`POST /consolidate`)完整保留读路由的三条自
|
|
196
|
+
认证——回环 socket、回环 `Host`、同源——并且只接受 `POST`,所以链接或 `<img>` 无法触
|
|
197
|
+
发这次写入。三个面都不会在主会话里发起模型调用。
|
|
198
|
+
|
|
171
199
|
- **工作单元是裁决出来的,不是猜出来的。** 候选来自 `similar`(唯一会报告距离的
|
|
172
200
|
检索),且排除操作层——在具备该能力的二进制上(hypatia #35)通过查询里的
|
|
173
201
|
`--exclude-tags`;在没这个能力的二进制上,抓四倍的量再丢弃——再按距离上限过滤,
|
|
@@ -243,14 +271,16 @@ agent/created ──▶ rules/taboos inject()
|
|
|
243
271
|
原始 `msg-*` 条目**故意不显示**:它是对话原文,读的人刚刚写过,而正确的读者是
|
|
244
272
|
Agent。
|
|
245
273
|
|
|
246
|
-
数据经由本插件注册在 DSH web
|
|
274
|
+
数据经由本插件注册在 DSH web 服务器上的、会话作用域的 HTTP 路由族
|
|
247
275
|
`/api/dsh-hypatia-auto-memory/session` 到达浏览器:状态每次轮询都从内存中的状态表
|
|
248
276
|
现算,shelf 内容只在可能变化时才读(打开时、切换会话时、手动刷新时,以及整合推进了
|
|
249
277
|
该会话水位时),其间缓存 15 秒。标签页在屏幕上时每 5 秒轮询一次,响应是有界的:
|
|
250
278
|
最新的 40 条摘要与 40 条工作单元,每条正文 4000 字符、整份响应 120000 字符。
|
|
251
279
|
JSE 没有 `ORDER BY`,所以「最新」只能在读完所有候选之后判定;扫描触到上限时计数
|
|
252
|
-
|
|
253
|
-
|
|
280
|
+
变成下界,标签页会把该次响应标为已截断,而不是让短列表无从解释。这个路由族里唯一的
|
|
281
|
+
**写**路由 `POST /consolidate` 就是「立即整理」按钮调用的那个;它自己不写任何东西
|
|
282
|
+
——它入队的是与自动触发完全相同的整合任务(见上文「也可以显式要求立刻整合」)。
|
|
283
|
+
路上否掉了两条通道。**session projection** 形态最
|
|
254
284
|
合适,但驱动它的事件在本仓库之外写不出来:`Session.append` 没有任何途径设置信封的
|
|
255
285
|
`ignorable` 标记,而持久化读取路径会拒绝一个带着未知事件类型、又没有该标记的会话
|
|
256
286
|
日志——所以自定义事件会破坏会话重载。**设置命名空间**即便在还存在时也是根作用域的:
|
|
@@ -273,7 +303,20 @@ agent/created ──▶ rules/taboos inject()
|
|
|
273
303
|
下面每个字段保存即生效,无需重载 profile。配置以插件条目 `config:`
|
|
274
304
|
的形式存进 profile patch;0.1.7 之前的 `settings.yaml` 段落会在首次启动时被一次性导入。
|
|
275
305
|
所有字段可选,展示的是默认值。空白的巩固路由在保存时就被拒绝;重复的路由在运行时被丢弃
|
|
276
|
-
|
|
306
|
+
并记一条警告。
|
|
307
|
+
|
|
308
|
+
**`consolidation.models` 是优先级顺序,不是轮换。** 一个任务的第一次尝试走第一条路由,
|
|
309
|
+
之后每次重试降级到下一条,超出列表长度时复用最后一条。两个需要知道的后果:一个任务能触达
|
|
310
|
+
的备选链长度是 `min(queue.maxAttempts, models.length)`——默认 `maxAttempts: 3` 时第四条
|
|
311
|
+
及以后的路由永远不会被用到;而每降一级要等一个 `queue.retryDelayMs`(5 秒),因为降级发生
|
|
312
|
+
在下一次重试,而不是同一次尝试内部。这是对原先「不管偏好、把所有已选路由轮流用完」的替换;
|
|
313
|
+
改变偏好就重排列表,或直接用卡片上的 ↑/↓ 按钮。
|
|
314
|
+
|
|
315
|
+
**每条路由还可以设置思考程度。** `reasoningEffort` 取一个适配器私有的档位——设置卡片只
|
|
316
|
+
列出该模型声明的那些,`llm-deepseek` 上是 `off`/`low`/`high`/`max`。留空即保持上文
|
|
317
|
+
*Consolidator* 里那套按用途策略;设了它就会覆盖该路由上所有调用的策略,输出上限也随之
|
|
318
|
+
变大来支付这份思考。模型没有声明的档位会在调用时被丢弃并记一条警告,而不是发出去——核心
|
|
319
|
+
会在任何 provider I/O 之前拒绝未声明的档位。
|
|
277
320
|
|
|
278
321
|
```yaml
|
|
279
322
|
hypatia-auto-memory:
|
|
@@ -289,8 +332,10 @@ hypatia-auto-memory:
|
|
|
289
332
|
toolLedger: false # 默认不写工具调用台账;true 才为有内容的助手消息记录
|
|
290
333
|
consolidation:
|
|
291
334
|
enabled: true
|
|
292
|
-
#
|
|
335
|
+
# 优先级顺序:每次整合先用第一条路由,重试才降级到下一条(见上文说明)。
|
|
293
336
|
models: [] # 选择一条或多条 { provider, model } 路由
|
|
337
|
+
# 每条路由还可以带 `reasoningEffort: <档位>`——卡片只列出该模型声明的档位
|
|
338
|
+
# (deepseek 上是 off/low/high/max)。留空即保持按用途策略。
|
|
294
339
|
maxInputTokens: 16000 # transcript 上限(chars/4 估算)
|
|
295
340
|
maxOutputTokens: 2000
|
|
296
341
|
timeoutMs: 120000
|
|
@@ -305,8 +350,8 @@ hypatia-auto-memory:
|
|
|
305
350
|
batchSize: 16 # 归档上一层之前,每层的条目数
|
|
306
351
|
queue:
|
|
307
352
|
concurrency: 1 # 并行会话数
|
|
308
|
-
maxAttempts: 3
|
|
309
|
-
retryDelayMs: 5000
|
|
353
|
+
maxAttempts: 3 # 每个任务的重试次数;同时决定模型备选链能走多深
|
|
354
|
+
retryDelayMs: 5000 # 也是换到下一条路由前的等待时间
|
|
310
355
|
# 不是分批旋钮。span 在 turn/end 切分;对一个跑这么久的 turn,
|
|
311
356
|
# 下一个 step/end 会把已完成的部分刷出。
|
|
312
357
|
flushWindowMs: 120000
|
|
@@ -374,12 +419,16 @@ shelf 清单本来就不是配置,而设置域现在只投影插件的 Config
|
|
|
374
419
|
4. 检查路由告警:没选 `models` 的巩固只记录一次告警,其余时间静默。
|
|
375
420
|
5. 后台调用实际用了哪个模型,逐次记在
|
|
376
421
|
`~/.dsh/storages/hypatia_auto_memory_diag.json`(`tables.model_calls`,按 `seq`
|
|
377
|
-
倒序,上限 100
|
|
378
|
-
|
|
379
|
-
`
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
422
|
+
倒序,上限 100 条)。设置卡片里的列表不是答案:`consolidation.models` 是优先级顺序,
|
|
423
|
+
同一个任务的几行会沿它往下走——第一次尝试是队首那条,重试是降级后的下一条,这也正是
|
|
424
|
+
确认「备选确实被用上了」的方式。`effort` 是这次调用**实际发出**的档位——路由拒绝了
|
|
425
|
+
配置值时它就不是配置值;`outputTokens` / `reasoningTokens` 来自路由自己上报的用量,
|
|
426
|
+
它们才说明输出上限是被正文吃掉的还是被推理吃掉的(路由不上报用量时两者都是 0)。
|
|
427
|
+
`outcome` 为 `pending`(在飞行中,或进程中途死了)、
|
|
428
|
+
`ok`(产出了可用结果)、`incomplete`(调用结束了但没产出——撞输出上限、被中止、回复
|
|
429
|
+
解析不出来)或 `error`(调用本身抛错);后两种都花掉了一次尝试且没有写入任何条目。
|
|
430
|
+
`detail` 是 finish kind、固定标签或 provider 的错误消息,截断到 200 字符——绝不放消息
|
|
431
|
+
正文,也绝不放模型输出。
|
|
383
432
|
6. `hypatia backfill --status` 报告 shelf 的嵌入债务。用当前的 hypatia,记录一个
|
|
384
433
|
turn 不会增加债务;只有摘要和工作单元在下一次 flush 前处于待处理。`hypatia scope
|
|
385
434
|
list --count` 显示在用的 scope——本项目 `msg-*` 条目所带的那个,就是会话种子读取
|
|
@@ -397,7 +446,7 @@ shelf 清单本来就不是配置,而设置域现在只投影插件的 Config
|
|
|
397
446
|
| 条目只在 turn 结束后才出现 | 设计如此:span 在 `turn/end` 切分,或当 turn 跑得比 `flushWindowMs` 久时在第一个 `step/end` 切分,所以条目不会缺它自己的工具结果 |
|
|
398
447
|
| 有记录,没摘要 | `consolidation.models` 为空或无效——首次触发时告警一次 |
|
|
399
448
|
| 有摘要但没有 `sum2-*` | 该项目里未归档的 tier-1 摘要还不足 `cascade.batchSize` 条 |
|
|
400
|
-
| 工作单元没有关系 | shelf 没有嵌入模型(`similar` 失败),所有候选都超过了 `dedupMaxDistance`,或裁决调用本身没有产出裁决——`model_calls` 里表现为 `incomplete` / `max-tokens`,也就是开启了思考的路由把整个输出预算花在推理上、还没开始作答就撞了上限。现在裁决会先确认适配器声明了 `off`,再对该路由请求 `reasoningEffort: 'off'`;对无法接受它的路由则把上限提到 1024
|
|
449
|
+
| 工作单元没有关系 | shelf 没有嵌入模型(`similar` 失败),所有候选都超过了 `dedupMaxDistance`,或裁决调用本身没有产出裁决——`model_calls` 里表现为 `incomplete` / `max-tokens`,也就是开启了思考的路由把整个输出预算花在推理上、还没开始作答就撞了上限。现在裁决会先确认适配器声明了 `off`,再对该路由请求 `reasoningEffort: 'off'`;对无法接受它的路由则把上限提到 1024——而路由的 `reasoningEffort` 若指定了真实档位,拿到的就是这 1024 再加上思考余量。自 hypatia #19 起,本地模型在 `~/.hypatia/models/<org>/<name>`(或 Hugging Face 缓存)里找,不再挨着 shelf:一个以前能回答 `similar`、现在说 `is not installed` 的 shelf,需要 `hypatia model install <model>`,或 `hypatia model register <model> <dir>` 指向它已有的文件 |
|
|
401
450
|
| `similar` 仍返回 `msg-*` 行 | 它们是在本插件让日志层退出嵌入之前写的,或由一个没有 `--no-embed` 的 hypatia 写的。在 shelf 的 `shelf.toml` 里用 `embedding.skip_tags = ["message"]` 加一次 `hypatia backfill` 一次性收回知识向量(比该 key 旧的二进制会拒绝打开 shelf,所以先升级所有共享它的二进制)。不要把 `session` 加进那个列表:`skip_tags` 匹配任何带该 tag 的条目,而人们写的关于会话的知识也带它——在试过的 shelf 上就有一条这样的条目丢了向量。插件自己的 `session-*` 节点很少,现在已逐次写入退出。三元组没有 tags 也没有 update 命令,所以之前写的 `belongTo` / `summary` 边保留向量 |
|
|
402
451
|
| 任务处于 `deferred` 状态 | live store 和 persistence 都没能提供它的会话。不是错误:它不消耗尝试次数,一旦二者之一能提供就运行。通常 storage 立即回答——只有组合里没有 `sessionPersistence`,或它的读取失败(日志里找 `could not be read`)时才会持久化这个状态。被 deferred 的巩固任务会在下次启动时被 backfill 重新入队,所以它不必等会话被重新打开 |
|
|
403
452
|
| 任务处于 `failed` 状态 | 一次 hypatia 或模型错误在 `maxAttempts` 之后持久化了。失败的 `log-message` 记录在下次启动时被修剪,因为水位会重新推导它们的范围;其它类型保留供检查——删掉一条好让下一个触发器重新创建它 |
|
|
@@ -409,7 +458,8 @@ shelf 清单本来就不是配置,而设置域现在只投影插件的 Config
|
|
|
409
458
|
| 手动乱改后出现重复 `msg-*` | 在 hypatia 里删掉该条目,并在 state domain 里把该会话的 `lastLoggedSeq` 调低——回填会重新创建它一次 |
|
|
410
459
|
| Agent 自己的 `hypatia` 写入仍要审批 | `autoApprove: false`、命令在引号外有管道/重定向/串联、或首词不是 `binaries` 之一——按设计只回答纯调用。读取根本不会走到审批,所以那里没有要修的 |
|
|
411
460
|
| Agent 拿到了一份教它手动记录消息的记忆协议 | 另一个插件先注册了 `hypatia-memory`(profile 里还有 `dsh-hypatia`),或磁盘上有 `hypatia-memory`——通常是 `hypatia skill install --agent codex` 写的 `~/.agents/skills/hypatia-memory`。Agent 预设会在比插件更近的一层加载磁盘技能,所以它在会话里胜出,尽管技能中心可能列出本插件。删掉启动告警指出的那份拷贝 |
|
|
412
|
-
| 不知道实际用了哪个模型 |
|
|
461
|
+
| 不知道实际用了哪个模型 | 列表是优先级顺序:一个任务的第一次尝试就是 `consolidation.models` 的队首,重试则是它降级到的那条。每次尝试都落在 `~/.dsh/storages/hypatia_auto_memory_diag.json`(`model_calls`),带 purpose、provider/model、**实际发出的 `effort`**、outcome、耗时以及路由自己上报的 token 拆分——把同一个任务的几行按序读下来,就能确认备选确实被用上了,而 `effort` 是确认配置的思考档位真的生效了的依据。usage ledger 答不了这个问题——它只折算 Agent 的 turn(`assistant/message`),看不见插件直连的 `llm.stream`。`pending` 行表示调用仍在进行中,或进程在调用中途死了:记录在调用前先落盘、调用结束后回填 |
|
|
462
|
+
| 配了 `reasoningEffort` 却像没生效 | 该路由没有声明这个档位,于是调用把它丢掉了,以免撞上核心的 `UNSUPPORTED_REASONING_EFFORT`。会有一条警告点名路由、值和该路由实际声明的档位,此后这些尝试的 `model_calls.effort` 是空的。卡片只提供模型声明的档位,所以这通常只发生在手改 profile、或把这条路由换成了别的模型之后 |
|
|
413
463
|
| 启动告警从不出现在终端 | `dsh web` 不挂日志导出器,所以插件任何级别的日志行都没地方去;控制台导出器的默认阈值也会丢掉警告(warn 是 2 级,高于 info 的 1 级)。临时挂一个 logger,如 `dsh-logbook` 或 `dsh-boot-doctor` 来读它们 |
|
|
414
464
|
| 项目 scope 看起来不对 | Scope = 会话 cwd 的 git 顶层目录的 basename(`git rev-parse --show-toplevel`);git 找不到工作树或答不上(未安装、或某 repo 被判定不安全)时,则是 cwd 本身的 basename。一次慢于 3 秒的 `rev-parse` 会让那个会话留在 cwd 自己的名字上。两个同名 checkout 按设计共享 scope;linked worktree 按它自己的目录而非主 checkout 的目录定 scope;git 会解析符号链接,所以通过一个与目标不同名的链接打开的 checkout 会得到目标的名字。一个会被 hypatia 改写的名字会先归一化:位于 `/` 的会话定 scope 为 `/`,逗号变 `_`,去掉首尾空白。这些修复之前写的条目留在原处:子目录会话写在那个目录名下(`repo/src` 写在 `src` 下——git 根从未被读取)、位于 `/` 的会话无 scope、`a,b` 同时写在 `a` 和 `b` 下、`foo,` 写在 `foo` 和全局下。带空白的名字被去空白存储,这也正是查询现在所请求的。没有任何东西迁移它们;`knowledge-update --scopes` 可移动单条并保留其 `created_at`。`hypatia scope list --count`(hypatia #30)显示每种在用的拼写及其条目数 |
|
|
415
465
|
|
|
@@ -486,7 +536,8 @@ dsh-hypatia-auto-memory/
|
|
|
486
536
|
│ ├── cascade.js # log₁₆(n) 分层摘要归档
|
|
487
537
|
│ ├── model-log.js # 每次尝试实际用了哪个模型的有界记录
|
|
488
538
|
│ ├── memory-status.js # 两张状态表按会话折叠(纯函数)
|
|
489
|
-
│ ├── memory-api.js # 记忆标签页与 shelf
|
|
539
|
+
│ ├── memory-api.js # 记忆标签页与 shelf 清单;含唯一的写路由
|
|
540
|
+
│ ├── on-demand.js # /hypatia-summarize 与 hypatia_summarize 工具
|
|
490
541
|
│ ├── recall.js # 会话启动时的 rules/taboos 预载
|
|
491
542
|
│ ├── auto-approve.js # 批准 Agent 自己的纯 bash hypatia 调用
|
|
492
543
|
│ ├── skills.js # 随包技能注册(从不遮蔽其它提供者)
|