@psiclawops/hypermem 0.5.5 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -62
- package/dist/background-indexer.d.ts +18 -0
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +131 -20
- package/dist/cache.d.ts +24 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +77 -3
- package/dist/compositor.d.ts +6 -0
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +471 -129
- package/dist/context-backfill.d.ts +46 -0
- package/dist/context-backfill.d.ts.map +1 -0
- package/dist/context-backfill.js +113 -0
- package/dist/context-store.d.ts +77 -0
- package/dist/context-store.d.ts.map +1 -0
- package/dist/context-store.js +177 -0
- package/dist/cross-agent.d.ts +12 -0
- package/dist/cross-agent.d.ts.map +1 -1
- package/dist/cross-agent.js +31 -19
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +8 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -7
- package/dist/knowledge-lint.js +4 -4
- package/dist/message-store.d.ts +31 -2
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +131 -17
- package/dist/preference-store.d.ts +1 -1
- package/dist/preference-store.js +1 -1
- package/dist/profiles.d.ts +4 -2
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +72 -37
- package/dist/repair-tool-pairs.d.ts.map +1 -1
- package/dist/repair-tool-pairs.js +73 -2
- package/dist/schema.d.ts +1 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +27 -1
- package/dist/seed.d.ts +1 -1
- package/dist/seed.js +1 -1
- package/dist/session-flusher.d.ts +2 -2
- package/dist/session-flusher.js +2 -2
- package/dist/spawn-context.d.ts +1 -1
- package/dist/spawn-context.js +1 -1
- package/dist/topic-synthesizer.d.ts.map +1 -1
- package/dist/topic-synthesizer.js +4 -3
- package/dist/trigger-registry.d.ts +1 -1
- package/dist/trigger-registry.js +4 -4
- package/dist/types.d.ts +74 -32
- package/dist/types.d.ts.map +1 -1
- package/dist/vector-store.d.ts +10 -1
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +353 -0
- package/dist/version.d.ts +5 -5
- package/dist/version.js +5 -5
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ send transcript to model tool compression by turn age
|
|
|
116
116
|
model responds → append again keystone guard + hyperform profile
|
|
117
117
|
composed prompt → model
|
|
118
118
|
┌──────────────────┐ model responds → afterTurn ingest
|
|
119
|
-
│
|
|
119
|
+
│ loop until full │ → write back to all 4 layers
|
|
120
120
|
└──────────────────┘
|
|
121
121
|
|
|
122
122
|
When it fills: When budget is exceeded:
|
|
@@ -127,7 +127,7 @@ When it fills: When budget is exceeded:
|
|
|
127
127
|
|
|
128
128
|
| | Standard | hypercompositor |
|
|
129
129
|
|---|---|---|
|
|
130
|
-
| Context source | Growing transcript |
|
|
130
|
+
| Context source | Growing transcript only | Transcript + 3 additional storage layers |
|
|
131
131
|
| When context fills | Trim + summarize (lossy) | Budget allocation (lossless storage) |
|
|
132
132
|
| Old decisions | Lost after compaction | Retrievable via keystones + semantic recall |
|
|
133
133
|
| Topic changes | All history competes equally | Scoped retrieval by active topic |
|
|
@@ -136,21 +136,45 @@ When it fills: When budget is exceeded:
|
|
|
136
136
|
|
|
137
137
|
High-signal turns are marked as keystones and survive pressure trimming ahead of ordinary history.
|
|
138
138
|
|
|
139
|
+
The compositor fills 9 slots in priority order (system prompt → identity → hyperform → history → facts → wiki → semantic recall → cross-session → action summary). Each slot consumes tokens from the remaining budget before the next slot runs. Slots that don't fit this turn stay in storage, not destroyed.
|
|
140
|
+
|
|
141
|
+
For the full fill order, budget formula, and all configuration knobs, see **[Tuning](#tuning)** below and **[docs/TUNING.md](./docs/TUNING.md)**.
|
|
142
|
+
|
|
139
143
|
---
|
|
140
144
|
|
|
141
145
|
## hyperform
|
|
142
146
|
|
|
143
147
|
Raw model output has two problems. It drifts from your standards (sycophancy, hedging, pagination, formatting) and it drifts from your facts (confabulation, contradiction, stale claims). hyperform handles both: normalization enforces consistency, confabulation resistance checks output against what's actually stored.
|
|
144
148
|
|
|
145
|
-
|
|
149
|
+
Consistent output isn't just aesthetic. A model that paginates short answers, preambles with filler, or inflates lists uses more output tokens per turn. Over hundreds of turns, that compounds into real cost. hyperform directives compress output at the source: fewer tokens generated means lower API spend per session, and less context pressure for subsequent turns.
|
|
150
|
+
|
|
151
|
+
### Behavior standards
|
|
146
152
|
|
|
147
|
-
|
|
153
|
+
Behavior standards define how your agents write. Anti-sycophancy rules prevent filler openings. Density targets compress answers. Anti-pattern bans remove common AI markers (em dashes, AI vocabulary, inflated significance). These rules apply to all models equally.
|
|
154
|
+
|
|
155
|
+
| Tier | Tokens | What it injects |
|
|
148
156
|
|---|---|---|
|
|
149
|
-
| `light` | ~100 |
|
|
150
|
-
| `standard` | ~250 | Full directive set
|
|
151
|
-
| `full` | ~
|
|
157
|
+
| `light` | ~100 | 9 standalone directives: lead with answer, no sycophancy, no em dashes, AI vocab ban, length targets (simple/analysis/code), filler ban, no pagination of short answers, evidence calibration, numbers over adjectives. No database required. |
|
|
158
|
+
| `standard` | ~250 | Full directive set from the `fleet_output_standard` table: structural rules, density targets per task type, anti-patterns, format rules, compression ratios, voice directives, and task-context overrides. Falls back to `light` directives if no record exists. |
|
|
159
|
+
| `full` | ~250 + adaptation | Same directives as `standard`, plus model adaptation (see below). |
|
|
160
|
+
|
|
161
|
+
### Model adaptation
|
|
162
|
+
|
|
163
|
+
Different models have different default behaviors. GPT-5.4 tends toward 2x verbosity and long lists. Claude Opus defaults to hedging and preambles. Gemini produces bulleted summaries where prose would be more direct. Model adaptation corrects for these tendencies per model.
|
|
164
|
+
|
|
165
|
+
Adaptation entries are stored in the `model_output_directives` table and matched by model ID using exact match, then glob pattern (longest wins), then wildcard fallback. Each entry contains:
|
|
166
|
+
|
|
167
|
+
- **Calibration** — known model tendencies and specific adjustments (e.g., "2x verbosity: cut first drafts in half")
|
|
168
|
+
- **Corrections** — hard/medium/soft severity rules applied in order (e.g., "No preamble before the answer")
|
|
169
|
+
- **Task overrides** — per-task-type adjustments
|
|
170
|
+
|
|
171
|
+
Model adaptation is only active at the `full` tier. At `light` and `standard`, model-specific corrections are suppressed.
|
|
172
|
+
|
|
173
|
+
The `model_output_directives` table starts empty. You populate it with corrections for the models you run. See [docs/TUNING.md](./docs/TUNING.md#creating-custom-entries) for the schema and SQL examples.
|
|
152
174
|
|
|
153
|
-
|
|
175
|
+
### Before and after
|
|
176
|
+
|
|
177
|
+
The same prompt, GPT-5.4, with and without `hyperformProfile: "light"`:
|
|
154
178
|
|
|
155
179
|
```
|
|
156
180
|
Prompt: "How should I size my context window budget for a long-running agent session?"
|
|
@@ -172,11 +196,13 @@ Would you like me to go deeper on any of these?
|
|
|
172
196
|
WITH outputProfile: "light":
|
|
173
197
|
For a 128k window: reserve 14k for identity/system, target 46k for history, 10k for recent
|
|
174
198
|
tool context, and leave ~30k as allocator reserve. hypermem handles slot competition
|
|
175
|
-
automatically
|
|
199
|
+
automatically — set `reserveFraction` to your preferred floor and let the compositor fill.
|
|
176
200
|
```
|
|
177
201
|
|
|
178
202
|
**Confabulation resistance** checks output against stored facts before claims are recorded. No LLM call. Pattern matching against the fact corpus, with confidence scoring and contradiction detection. Unsupported claims are flagged, contradictions surface in diagnostics, and a confabulation risk score is attached to the stored episode.
|
|
179
203
|
|
|
204
|
+
Set `compositor.hyperformProfile` to `light`, `standard`, or `full`. For tier selection guidance, configuration details, and custom entry creation, see **[Tuning](#tuning)** below and **[docs/TUNING.md](./docs/TUNING.md)**.
|
|
205
|
+
|
|
180
206
|
---
|
|
181
207
|
|
|
182
208
|
## What it solves
|
|
@@ -197,17 +223,32 @@ OpenClaw 2026.4.7 ships memory wiki for structured storage. hypermem goes furthe
|
|
|
197
223
|
|
|
198
224
|
Spawned subagents inherit a bounded context block: recent parent turns, session-scoped documents, and relevant facts. Scope is isolated from the shared library. Documents are cleaned up on completion.
|
|
199
225
|
|
|
226
|
+
### Context that doesn't repeat itself
|
|
227
|
+
|
|
228
|
+
Retrieval paths pull from four layers, trigger shortcuts, temporal indexes, open-domain FTS5, semantic recall, and cross-session summaries. Without dedup, the same fact surfaces through multiple paths and wastes budget on repetition.
|
|
229
|
+
|
|
230
|
+
hypermem runs content fingerprint dedup across all compose-time retrieval. Every fact, temporal result, open-domain hit, and semantic recall entry is normalized and fingerprinted on a 120-char prefix. O(1) lookup in a shared set catches duplicates regardless of which retrieval path produced them, including rephrased near-duplicates that substring matching missed. Diagnostics track dedup counts and fingerprint collisions per compose call.
|
|
231
|
+
|
|
232
|
+
Identity content (SOUL.md, USER.md, IDENTITY.md) and doc chunks already injected by OpenClaw's bootstrap are fingerprinted before retrieval runs, so the compositor never double-injects content the runtime already placed in the prompt.
|
|
233
|
+
|
|
234
|
+
### Integrity under failure
|
|
235
|
+
|
|
236
|
+
The background indexer runs a startup integrity check against `library.db` on every boot. If the schema is corrupt, tables are missing, or critical indexes are damaged, the indexer enters circuit-breaker mode: it logs the failure, skips indexing for the session, and avoids cascading writes into a broken database. The agent still runs with cached and in-memory data while the operator is notified.
|
|
237
|
+
|
|
238
|
+
SQL queries that interpolate datetime values are fully parameterized. FTS5 trigger terms are quoted to prevent injection through crafted content. These aren't theoretical: agentic sessions ingest arbitrary user and tool output into the fact store, and unparameterized queries on that path were a real attack surface.
|
|
239
|
+
|
|
200
240
|
---
|
|
201
241
|
|
|
202
242
|
## Pressure management
|
|
203
243
|
|
|
204
|
-
hypermem composes context fresh on every turn, but a long-running session still accumulates history in its JSONL transcript. When that grows large enough, incoming tool results have nowhere to land and get silently stripped.
|
|
244
|
+
hypermem composes context fresh on every turn, but a long-running session still accumulates history in its JSONL transcript. When that grows large enough, incoming tool results have nowhere to land and get silently stripped. Four automatic paths handle this:
|
|
205
245
|
|
|
206
246
|
| Path | Trigger | Action |
|
|
207
247
|
|---|---|---|
|
|
208
248
|
| **Pressure-tiered tool-loop trim** | Any tool-loop turn | Measures projected occupancy before results land; trims large results at 80%+ and truncates the messages[] array for the current turn |
|
|
209
249
|
| **AfterTurn trim** | Every turn at >80% | Pre-emptive headroom cut after the assistant replies, before the next turn arrives |
|
|
210
250
|
| **Deep compaction** | compact() at >85% | Cuts in-memory cache to 25% budget and truncates JSONL to ~20% depth. Bypasses the normal reshape guard |
|
|
251
|
+
| **Reshape guard** | Structured tool history on downshift | `canPersistReshapedHistory()` blocks a lower-context snapshot from overwriting the full JSONL history |
|
|
211
252
|
|
|
212
253
|
**The one thing these paths cannot fix:** a session whose JSONL transcript on disk is already at 98% when the gateway restarts. The JSONL loads into runtime context before any compaction runs. Check `session_status` on startup. If you're above 85%, start a fresh session.
|
|
213
254
|
|
|
@@ -281,6 +322,8 @@ Retrieval follows a fixed pipeline on every compose call:
|
|
|
281
322
|
|
|
282
323
|
FTS5 queries use compound indexes on `agentId + sort key` and prefix optimization (3+ chars, capped at 8 terms, OR queries). These indexes yielded a 25% read improvement over baseline despite a 47% increase in stored data.
|
|
283
324
|
|
|
325
|
+
### Retrieval pipeline
|
|
326
|
+
|
|
284
327
|
**L4: Library DB.** Per-agent storage can't hold shared knowledge. Facts established by one agent, wiki pages synthesized from cross-agent topics, shared registry state: these belong to the system, not one agent. One shared SQLite database:
|
|
285
328
|
|
|
286
329
|
| Collection | What it holds |
|
|
@@ -311,17 +354,17 @@ Facts are ranked by `confidence × recencyDecay`, where decay is exponential wit
|
|
|
311
354
|
│
|
|
312
355
|
topic detection ──► scope retrieval to active thread
|
|
313
356
|
│
|
|
314
|
-
|
|
315
|
-
│ query 4 layers (parallel)
|
|
316
|
-
│
|
|
317
|
-
│ L1 in-memory L2 History
|
|
357
|
+
┌────┴───────────────────────────────────────────────┐
|
|
358
|
+
│ query 4 layers (parallel) │
|
|
359
|
+
│ │
|
|
360
|
+
│ L1 in-memory L2 History L3 Vectors L4 Library │
|
|
318
361
|
│ hot state durable semantic facts/wiki │
|
|
319
362
|
│ 0.1ms 0.16ms 0.29ms 0.08ms │
|
|
320
|
-
|
|
363
|
+
└────┬───────────────────────────────────────────────┘
|
|
321
364
|
│
|
|
322
365
|
budget allocator ──► 10 slots, fixed token cap
|
|
323
366
|
│
|
|
324
|
-
tool compression ──►
|
|
367
|
+
tool compression ──► clusterNeutralMessages() → T0 full → T1 6k → T2 800 → T3 150-char stub
|
|
325
368
|
│
|
|
326
369
|
keystone guard ──► high-signal turns survive pressure
|
|
327
370
|
│
|
|
@@ -340,14 +383,7 @@ Slot-level budget allocation is shown in the [hypercompositor diagram](#what-the
|
|
|
340
383
|
|
|
341
384
|
## Requirements
|
|
342
385
|
|
|
343
|
-
**Current release: hypermem 0.5.
|
|
344
|
-
|
|
345
|
-
What 0.5.4 includes:
|
|
346
|
-
- Topic-aware context tracking
|
|
347
|
-
- Compiled knowledge / wiki-like synthesis and recall
|
|
348
|
-
- Metrics dashboard primitives
|
|
349
|
-
- Obsidian import and export
|
|
350
|
-
- Aligned runtime profiles: `light`, `standard`, `full`
|
|
386
|
+
**Current release: hypermem 0.5.6.** Changelog: [CHANGELOG.md](./CHANGELOG.md)
|
|
351
387
|
|
|
352
388
|
| Requirement | Version | Notes |
|
|
353
389
|
|---|---|---|
|
|
@@ -360,9 +396,8 @@ SQLite is a library, not a service. All four layers run in-process with no exter
|
|
|
360
396
|
**Runtime version constants** (importable from the package):
|
|
361
397
|
```typescript
|
|
362
398
|
import {
|
|
363
|
-
ENGINE_VERSION, // '0.5.
|
|
399
|
+
ENGINE_VERSION, // '0.5.6'
|
|
364
400
|
MIN_NODE_VERSION, // '22.0.0'
|
|
365
|
-
MIN_SQLITE_VERSION, // '3.35.0'
|
|
366
401
|
SQLITE_VEC_VERSION, // '0.1.9'
|
|
367
402
|
MAIN_SCHEMA_VERSION, // 6 (hypermem.db)
|
|
368
403
|
LIBRARY_SCHEMA_VERSION_EXPORT, // 12 (library.db)
|
|
@@ -407,54 +442,59 @@ If you prefer, hand the install to your OpenClaw agent:
|
|
|
407
442
|
|
|
408
443
|
### Tuning
|
|
409
444
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
| Profile | Context window | Budget fraction | Best for |
|
|
413
|
-
|---|---|---|---|
|
|
414
|
-
| `light` | 64k | 0.50 | Single-agent installs, minimal parallel work |
|
|
415
|
-
| `standard` | 128k | 0.65 | Normal OpenClaw deployments |
|
|
416
|
-
| `full` | 200k+ | 0.55 | Large-context or multi-agent installs, maximum richness |
|
|
445
|
+
Two independent surfaces: **context assembly** (what fills the context window) and **output shaping** (how the model writes). Pick a profile first — most deployments adjust one or two settings on top.
|
|
417
446
|
|
|
418
|
-
|
|
447
|
+
| Profile | Target window | Best for |
|
|
448
|
+
|---|---|---|
|
|
449
|
+
| `light` | 64k | Single agent, small models, constrained resources |
|
|
450
|
+
| `standard` | 128k | Normal deployments, small fleets |
|
|
451
|
+
| `full` | 200k+ | Multi-agent fleets, large-context models |
|
|
419
452
|
|
|
420
|
-
|
|
453
|
+
Start with `light`. Use `mergeProfile()` to adjust individual settings:
|
|
421
454
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
455
|
+
```typescript
|
|
456
|
+
import { mergeProfile } from '@psiclawops/hypermem';
|
|
457
|
+
const config = mergeProfile('standard', { compositor: { maxFacts: 40 } });
|
|
458
|
+
```
|
|
425
459
|
|
|
426
|
-
Drop a `~/.openclaw/hypermem/config.json` to override
|
|
460
|
+
Drop a `~/.openclaw/hypermem/config.json` to override defaults (takes effect on gateway restart):
|
|
427
461
|
|
|
428
462
|
```json
|
|
429
463
|
{
|
|
430
|
-
"deferToolPruning": true,
|
|
431
464
|
"compositor": {
|
|
432
|
-
"
|
|
433
|
-
"
|
|
434
|
-
"contextWindowReserve": 0.25,
|
|
435
|
-
"outputProfile": "standard"
|
|
465
|
+
"budgetFraction": 0.70,
|
|
466
|
+
"hyperformProfile": "standard"
|
|
436
467
|
}
|
|
437
468
|
}
|
|
438
469
|
```
|
|
439
470
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
`deferToolPruning: true` tells hypermem to skip its own T0/T1/T2/T3 gradient when OpenClaw's native `contextPruning` extension is active (Anthropic and Google providers). On those providers, OpenClaw's pruner handles tool result trimming: ratio-driven at >30% context fill, soft-trim head+tail for results over 4,000 chars, hard-clear above 50k total, with the last 3 assistant turns always protected. hypermem's gradient remains active as fallback for other providers (GPT-5.4, etc.). Default: `true` for Anthropic installs.
|
|
443
|
-
|
|
444
|
-
`outputProfile` valid values: `"light"` (~100 tokens: anti-sycophancy, em dash ban, AI vocab ban, length targets, evidence calibration), `"standard"` (~250 tokens: full directive set plus pagination and hedging rules), `"full"` (~400 tokens: complete normalization with full directive set and model-specific calibration). Default: `"standard"`.
|
|
471
|
+
Or configure through `openclaw.json` (preferred for managed deployments):
|
|
445
472
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
473
|
+
```json
|
|
474
|
+
{
|
|
475
|
+
"plugins": {
|
|
476
|
+
"entries": {
|
|
477
|
+
"hypercompositor": {
|
|
478
|
+
"config": {
|
|
479
|
+
"compositor": { "budgetFraction": 0.70 },
|
|
480
|
+
"hyperformProfile": "standard"
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
450
486
|
```
|
|
451
487
|
|
|
452
|
-
|
|
488
|
+
Plugin config in `openclaw.json` takes precedence over `config.json`. Both sources are merged, with plugin config winning on overlap. The config schema is validated on gateway start and visible via `openclaw config get plugins.entries.hypercompositor.config`.
|
|
489
|
+
|
|
490
|
+
Full reference: **[docs/TUNING.md](./docs/TUNING.md)**
|
|
453
491
|
|
|
454
492
|
---
|
|
455
493
|
|
|
456
494
|
## API
|
|
457
495
|
|
|
496
|
+
> **Note:** The examples below use placeholder agent names (`my-agent`, `agent1`, etc.). Replace these with your actual agent IDs from your OpenClaw config. Single-agent installs typically use `main`. Multi-agent fleets use whatever IDs you've configured. See [INSTALL.md § "Configure your fleet"](./INSTALL.md#step-5--configure-your-fleet-multi-agent-only) for details.
|
|
497
|
+
|
|
458
498
|
```typescript
|
|
459
499
|
import { HyperMem } from '@psiclawops/hypermem';
|
|
460
500
|
|
|
@@ -468,18 +508,18 @@ const hm = await HyperMem.create({
|
|
|
468
508
|
});
|
|
469
509
|
|
|
470
510
|
// Record and compose
|
|
471
|
-
await hm.recordUserMessage('
|
|
511
|
+
await hm.recordUserMessage('my-agent', 'agent:my-agent:webchat:main', 'How does drift detection work?');
|
|
472
512
|
|
|
473
513
|
const composed = await hm.compose({
|
|
474
|
-
agentId: '
|
|
475
|
-
sessionKey: 'agent:
|
|
514
|
+
agentId: 'my-agent',
|
|
515
|
+
sessionKey: 'agent:my-agent:webchat:main',
|
|
476
516
|
prompt: 'How does drift detection work?',
|
|
477
517
|
tokenBudget: 4000,
|
|
478
518
|
provider: 'anthropic',
|
|
479
519
|
});
|
|
480
520
|
|
|
481
521
|
// Refresh tool compression after each turn
|
|
482
|
-
await hm.refreshCacheGradient('
|
|
522
|
+
await hm.refreshCacheGradient('my-agent', 'agent:my-agent:webchat:main');
|
|
483
523
|
```
|
|
484
524
|
|
|
485
525
|
Spawning a subagent with parent context:
|
|
@@ -488,10 +528,10 @@ Spawning a subagent with parent context:
|
|
|
488
528
|
import { buildSpawnContext, MessageStore, DocChunkStore } from '@psiclawops/hypermem';
|
|
489
529
|
|
|
490
530
|
const spawn = await buildSpawnContext(
|
|
491
|
-
new MessageStore(hm.dbManager.getMessageDb('
|
|
531
|
+
new MessageStore(hm.dbManager.getMessageDb('my-agent')),
|
|
492
532
|
new DocChunkStore(hm.dbManager.getLibraryDb()),
|
|
493
|
-
'
|
|
494
|
-
{ parentSessionKey: 'agent:
|
|
533
|
+
'my-agent',
|
|
534
|
+
{ parentSessionKey: 'agent:my-agent:webchat:main', workingSnapshot: 12 }
|
|
495
535
|
);
|
|
496
536
|
```
|
|
497
537
|
|
|
@@ -503,7 +543,7 @@ const spawn = await buildSpawnContext(
|
|
|
503
543
|
|
|
504
544
|
```bash
|
|
505
545
|
node bin/hypermem-status.mjs # full dashboard
|
|
506
|
-
node bin/hypermem-status.mjs --agent
|
|
546
|
+
node bin/hypermem-status.mjs --agent my-agent # scoped to one agent
|
|
507
547
|
node bin/hypermem-status.mjs --json # machine-readable output
|
|
508
548
|
node bin/hypermem-status.mjs --health # health checks only (exit 1 on failure)
|
|
509
549
|
```
|
|
@@ -545,6 +585,12 @@ Design guide: [PsiClawOps/AgenticCognitiveArchitecture](https://github.com/PsiCl
|
|
|
545
585
|
|
|
546
586
|
---
|
|
547
587
|
|
|
588
|
+
## Acknowledgments
|
|
589
|
+
|
|
590
|
+
The embedding-space fidelity threshold used in compaction validation was informed by the geometric preservation mathematics published by the [libravdb](https://github.com/xDarkicex/openclaw-memory-libravdb) project.
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
548
594
|
## License
|
|
549
595
|
|
|
550
596
|
Apache-2.0, [PsiClawOps](https://github.com/PsiClawOps)
|
|
@@ -56,6 +56,10 @@ export declare class BackgroundIndexer {
|
|
|
56
56
|
private vectorStore;
|
|
57
57
|
private synthesizer;
|
|
58
58
|
private tickCount;
|
|
59
|
+
/** Circuit breaker: consecutive tick failure count. Resets on success. */
|
|
60
|
+
private consecutiveFailures;
|
|
61
|
+
/** True when the indexer is running in backoff mode due to repeated failures. */
|
|
62
|
+
private inBackoff;
|
|
59
63
|
constructor(config?: Partial<IndexerConfig>, getMessageDb?: ((agentId: string) => DatabaseSync) | undefined, getLibraryDb?: (() => DatabaseSync) | undefined, listAgents?: (() => string[]) | undefined, getCursor?: CursorFetcher | undefined, dreamerConfig?: Partial<DreamerConfig>);
|
|
60
64
|
/**
|
|
61
65
|
* Set the vector store for embedding new facts/episodes at index time.
|
|
@@ -66,6 +70,20 @@ export declare class BackgroundIndexer {
|
|
|
66
70
|
* Start periodic indexing.
|
|
67
71
|
*/
|
|
68
72
|
start(): void;
|
|
73
|
+
/**
|
|
74
|
+
* Circuit breaker for tick failures.
|
|
75
|
+
*
|
|
76
|
+
* - Tracks consecutive failures.
|
|
77
|
+
* - After 3 failures, logs actionable recovery guidance once, then switches
|
|
78
|
+
* the indexer to 10× backoff interval so it stops spamming the log.
|
|
79
|
+
* - On the next successful tick, resets state and restores normal interval.
|
|
80
|
+
*/
|
|
81
|
+
private _handleTickError;
|
|
82
|
+
/**
|
|
83
|
+
* Reset the circuit breaker and restore normal interval after a successful tick.
|
|
84
|
+
* Called at the end of a successful tick().
|
|
85
|
+
*/
|
|
86
|
+
private _resetCircuitBreaker;
|
|
69
87
|
/**
|
|
70
88
|
* Stop periodic indexing.
|
|
71
89
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"background-indexer.d.ts","sourceRoot":"","sources":["../src/background-indexer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAiB,aAAa,EAAe,aAAa,EAAE,MAAM,YAAY,CAAC;AAK3F,OAAO,EAA2B,KAAK,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAOrF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"background-indexer.d.ts","sourceRoot":"","sources":["../src/background-indexer.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,23 +32,31 @@ import { isSafeForSharedVisibility } from './secret-scanner.js';
|
|
|
32
32
|
// Used to populate the `domain` column on extracted facts so that
|
|
33
33
|
// domain-scoped retrieval (e.g. getActiveFacts({ domain: 'infrastructure' }))
|
|
34
34
|
// returns results. New agents default to 'general'.
|
|
35
|
+
//
|
|
36
|
+
// ── EXAMPLE DATA ──────────────────────────────────────────────────
|
|
37
|
+
// The agent names below (agent1, director1, etc.) are PLACEHOLDERS.
|
|
38
|
+
// Replace them with your own agent IDs and domain labels to match
|
|
39
|
+
// your fleet. Single-agent installs don't need to edit this:
|
|
40
|
+
// unknown agents fall through to 'general' automatically.
|
|
41
|
+
// See INSTALL.md § "Configure your fleet" for details.
|
|
42
|
+
// ─────────────────────────────────────────────────────────────────
|
|
35
43
|
const AGENT_DOMAIN_MAP = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
agent1: 'infrastructure',
|
|
45
|
+
director2: 'infrastructure',
|
|
46
|
+
director1: 'infrastructure',
|
|
47
|
+
director3: 'infrastructure',
|
|
48
|
+
agent2: 'product',
|
|
49
|
+
director4: 'product',
|
|
50
|
+
director5: 'product',
|
|
51
|
+
director6: 'product',
|
|
52
|
+
agent3: 'security',
|
|
53
|
+
director7: 'security',
|
|
54
|
+
director8: 'security',
|
|
55
|
+
agent4: 'ux',
|
|
56
|
+
agent6: 'governance',
|
|
57
|
+
agent5: 'strategy',
|
|
58
|
+
specialist1: 'development',
|
|
59
|
+
specialist2: 'communications',
|
|
52
60
|
main: 'general',
|
|
53
61
|
'channel-mini': 'general',
|
|
54
62
|
};
|
|
@@ -83,7 +91,7 @@ function extractFactCandidates(content) {
|
|
|
83
91
|
// Preference patterns — medium confidence (0.60)
|
|
84
92
|
const preferencePatterns = [
|
|
85
93
|
/(?:prefer|always use|never use|don't use|avoid) (.{10,150})/gi,
|
|
86
|
-
/(?:
|
|
94
|
+
/(?:operator|operator) (?:wants|prefers|likes|hates|dislikes) (.{10,150})/gi,
|
|
87
95
|
];
|
|
88
96
|
// Operational patterns: deployments, incidents, fixes — high confidence (0.70)
|
|
89
97
|
const operationalPatterns = [
|
|
@@ -137,7 +145,7 @@ const OPERATIONAL_BOILERPLATE = [
|
|
|
137
145
|
/still\s*waiting/i,
|
|
138
146
|
/will\s*pick\s*(it\s*)?up\s*(on\s*(next|the))?/i,
|
|
139
147
|
/message\s*is\s*in\s*(his|her|their|the)\s*queue/i,
|
|
140
|
-
/sent\s+to\s+(
|
|
148
|
+
/sent\s+to\s+(agent6|agent2|agent4|agent3|agent5|agent1)/i,
|
|
141
149
|
/dispatched\s+(it\s+)?to/i,
|
|
142
150
|
/timed\s*out\s*after/i,
|
|
143
151
|
/\bNO_REPLY\b/,
|
|
@@ -408,6 +416,10 @@ export class BackgroundIndexer {
|
|
|
408
416
|
vectorStore = null;
|
|
409
417
|
synthesizer = null;
|
|
410
418
|
tickCount = 0;
|
|
419
|
+
/** Circuit breaker: consecutive tick failure count. Resets on success. */
|
|
420
|
+
consecutiveFailures = 0;
|
|
421
|
+
/** True when the indexer is running in backoff mode due to repeated failures. */
|
|
422
|
+
inBackoff = false;
|
|
411
423
|
constructor(config, getMessageDb, getLibraryDb, listAgents, getCursor, dreamerConfig) {
|
|
412
424
|
this.getMessageDb = getMessageDb;
|
|
413
425
|
this.getLibraryDb = getLibraryDb;
|
|
@@ -457,9 +469,31 @@ export class BackgroundIndexer {
|
|
|
457
469
|
return;
|
|
458
470
|
if (this.intervalHandle)
|
|
459
471
|
return;
|
|
472
|
+
// Startup integrity check — catch corruption before the first tick writes anything.
|
|
473
|
+
if (this.getLibraryDb) {
|
|
474
|
+
try {
|
|
475
|
+
const libDb = this.getLibraryDb();
|
|
476
|
+
if (libDb) {
|
|
477
|
+
const row = libDb.prepare('PRAGMA quick_check').get();
|
|
478
|
+
if (row?.integrity_check && row.integrity_check !== 'ok') {
|
|
479
|
+
console.error('[indexer] ⚠️ library.db integrity check failed: ' + row.integrity_check + '\n' +
|
|
480
|
+
'[indexer] Recovery: stop OpenClaw, run ' +
|
|
481
|
+
'`sqlite3 ~/.openclaw/hypermem/library.db ".recover" | sqlite3 ~/.openclaw/hypermem/library_recovered.db`' +
|
|
482
|
+
', swap the files, and restart. If recovery fails, delete library.db — the indexer rebuilds from message history.');
|
|
483
|
+
// Don't start the interval — nothing will succeed with a corrupt DB.
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch (err) {
|
|
489
|
+
// If we can't even open the DB, log and bail — don't start the interval.
|
|
490
|
+
console.error('[indexer] Could not open library.db for integrity check:', err.message);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
460
494
|
// Run once immediately
|
|
461
495
|
this.tick().catch(err => {
|
|
462
|
-
|
|
496
|
+
this._handleTickError(err, 'initial');
|
|
463
497
|
});
|
|
464
498
|
// Run episode vector backfill once at startup (no-op if already done)
|
|
465
499
|
if (this.vectorStore && this.getLibraryDb) {
|
|
@@ -470,11 +504,83 @@ export class BackgroundIndexer {
|
|
|
470
504
|
// Then periodically
|
|
471
505
|
this.intervalHandle = setInterval(() => {
|
|
472
506
|
this.tick().catch(err => {
|
|
473
|
-
|
|
507
|
+
this._handleTickError(err, 'periodic');
|
|
474
508
|
});
|
|
475
509
|
}, this.config.periodicInterval);
|
|
476
510
|
console.log(`[indexer] Started with interval ${this.config.periodicInterval}ms, batchSize ${this.config.batchSize}, maxPerTick ${this.config.maxMessagesPerTick}`);
|
|
477
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* Circuit breaker for tick failures.
|
|
514
|
+
*
|
|
515
|
+
* - Tracks consecutive failures.
|
|
516
|
+
* - After 3 failures, logs actionable recovery guidance once, then switches
|
|
517
|
+
* the indexer to 10× backoff interval so it stops spamming the log.
|
|
518
|
+
* - On the next successful tick, resets state and restores normal interval.
|
|
519
|
+
*/
|
|
520
|
+
_handleTickError(err, phase) {
|
|
521
|
+
this.consecutiveFailures++;
|
|
522
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
523
|
+
const isSqliteCorrupt = msg.includes('database disk image is malformed') ||
|
|
524
|
+
msg.includes('SQLITE_CORRUPT') ||
|
|
525
|
+
(err instanceof Error && 'code' in err && err.code === 'ERR_SQLITE_ERROR');
|
|
526
|
+
if (this.consecutiveFailures < 3) {
|
|
527
|
+
// First 1–2 failures: log normally.
|
|
528
|
+
console.error(`[indexer] ${phase === 'initial' ? 'Initial' : 'Periodic'} tick failed (attempt ${this.consecutiveFailures}/3):`, err);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (this.consecutiveFailures === 3) {
|
|
532
|
+
// Third failure: log once with recovery instructions, then enter backoff.
|
|
533
|
+
if (isSqliteCorrupt) {
|
|
534
|
+
console.error(`[indexer] ⛔ Tick failed 3 times consecutively — library.db appears corrupted. Entering backoff mode.\n` +
|
|
535
|
+
`[indexer] Recovery steps:\n` +
|
|
536
|
+
`[indexer] 1. Stop OpenClaw: openclaw gateway stop\n` +
|
|
537
|
+
`[indexer] 2. Check damage: sqlite3 ~/.openclaw/hypermem/library.db "PRAGMA integrity_check"\n` +
|
|
538
|
+
`[indexer] 3. Attempt recovery: sqlite3 ~/.openclaw/hypermem/library.db ".recover" | sqlite3 ~/.openclaw/hypermem/library_recovered.db\n` +
|
|
539
|
+
`[indexer] 4. Swap: mv library.db library_corrupt.bak && mv library_recovered.db library.db\n` +
|
|
540
|
+
`[indexer] 5. If recovery fails, delete library.db — the indexer rebuilds from message history on next start.\n` +
|
|
541
|
+
`[indexer] 6. Restart: openclaw gateway start\n` +
|
|
542
|
+
`[indexer] Indexer will retry every ${(this.config.periodicInterval * 10) / 60000} minutes until then.`);
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
console.error(`[indexer] ⛔ Tick failed 3 times consecutively (${msg}). Entering backoff mode. ` +
|
|
546
|
+
`Will retry every ${(this.config.periodicInterval * 10) / 60000} minutes.`);
|
|
547
|
+
}
|
|
548
|
+
// Switch to backoff interval.
|
|
549
|
+
this.inBackoff = true;
|
|
550
|
+
if (this.intervalHandle) {
|
|
551
|
+
clearInterval(this.intervalHandle);
|
|
552
|
+
}
|
|
553
|
+
this.intervalHandle = setInterval(() => {
|
|
554
|
+
this.tick().catch(backoffErr => {
|
|
555
|
+
this._handleTickError(backoffErr, 'periodic');
|
|
556
|
+
});
|
|
557
|
+
}, this.config.periodicInterval * 10);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
// Beyond 3: silent (already logged, in backoff — don't spam).
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Reset the circuit breaker and restore normal interval after a successful tick.
|
|
564
|
+
* Called at the end of a successful tick().
|
|
565
|
+
*/
|
|
566
|
+
_resetCircuitBreaker() {
|
|
567
|
+
if (this.consecutiveFailures === 0)
|
|
568
|
+
return;
|
|
569
|
+
const wasInBackoff = this.inBackoff;
|
|
570
|
+
this.consecutiveFailures = 0;
|
|
571
|
+
this.inBackoff = false;
|
|
572
|
+
if (wasInBackoff) {
|
|
573
|
+
// Restore normal interval.
|
|
574
|
+
if (this.intervalHandle)
|
|
575
|
+
clearInterval(this.intervalHandle);
|
|
576
|
+
this.intervalHandle = setInterval(() => {
|
|
577
|
+
this.tick().catch(err => {
|
|
578
|
+
this._handleTickError(err, 'periodic');
|
|
579
|
+
});
|
|
580
|
+
}, this.config.periodicInterval);
|
|
581
|
+
console.log('[indexer] Circuit breaker reset — tick succeeded, restored normal interval.');
|
|
582
|
+
}
|
|
583
|
+
}
|
|
478
584
|
/**
|
|
479
585
|
* Stop periodic indexing.
|
|
480
586
|
*/
|
|
@@ -494,6 +600,7 @@ export class BackgroundIndexer {
|
|
|
494
600
|
}
|
|
495
601
|
this.running = true;
|
|
496
602
|
const results = [];
|
|
603
|
+
let tickSucceeded = false;
|
|
497
604
|
try {
|
|
498
605
|
if (!this.listAgents || !this.getMessageDb || !this.getLibraryDb) {
|
|
499
606
|
console.warn('[indexer] Missing database accessors — skipping');
|
|
@@ -601,8 +708,12 @@ export class BackgroundIndexer {
|
|
|
601
708
|
}
|
|
602
709
|
}
|
|
603
710
|
}
|
|
711
|
+
// If we reach here, the tick completed without throwing.
|
|
712
|
+
tickSucceeded = true;
|
|
604
713
|
}
|
|
605
714
|
finally {
|
|
715
|
+
if (tickSucceeded)
|
|
716
|
+
this._resetCircuitBreaker();
|
|
606
717
|
this.running = false;
|
|
607
718
|
}
|
|
608
719
|
return results;
|
package/dist/cache.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Same public interface, zero external dependencies, zero TCP overhead.
|
|
6
6
|
*/
|
|
7
7
|
import { DatabaseSync } from 'node:sqlite';
|
|
8
|
-
import type { CacheConfig, SessionMeta, SessionCursor, StoredMessage, NeutralMessage } from './types.js';
|
|
8
|
+
import type { CacheConfig, ComposeDiagnostics, SessionMeta, SessionCursor, StoredMessage, NeutralMessage } from './types.js';
|
|
9
9
|
export interface ModelState {
|
|
10
10
|
model: string;
|
|
11
11
|
tokenBudget: number;
|
|
@@ -13,6 +13,13 @@ export interface ModelState {
|
|
|
13
13
|
historyDepth: number;
|
|
14
14
|
reshapedAt?: string;
|
|
15
15
|
}
|
|
16
|
+
export interface WindowCacheMeta {
|
|
17
|
+
slots: Record<string, number>;
|
|
18
|
+
totalTokens: number;
|
|
19
|
+
warnings: string[];
|
|
20
|
+
diagnostics: ComposeDiagnostics;
|
|
21
|
+
composedAt: string;
|
|
22
|
+
}
|
|
16
23
|
export declare class CacheLayer {
|
|
17
24
|
private db;
|
|
18
25
|
private readonly config;
|
|
@@ -41,6 +48,7 @@ export declare class CacheLayer {
|
|
|
41
48
|
private stmtEvictHistory;
|
|
42
49
|
private stmtSetWindow;
|
|
43
50
|
private stmtGetWindow;
|
|
51
|
+
private stmtGetFreshWindowBundle;
|
|
44
52
|
private stmtDeleteWindow;
|
|
45
53
|
private stmtEvictWindows;
|
|
46
54
|
private stmtSetKv;
|
|
@@ -67,6 +75,21 @@ export declare class CacheLayer {
|
|
|
67
75
|
setWindow(agentId: string, sessionKey: string, messages: NeutralMessage[], ttlSeconds?: number): Promise<void>;
|
|
68
76
|
getWindow(agentId: string, sessionKey: string): Promise<NeutralMessage[] | null>;
|
|
69
77
|
invalidateWindow(agentId: string, sessionKey: string): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Returns the cached window + metadata only if a single read shows the cache
|
|
80
|
+
* and cursor still refer to the same composed window.
|
|
81
|
+
* Used for C4 window cache fast-exit in compositor.ts.
|
|
82
|
+
*/
|
|
83
|
+
getFreshWindowBundle(agentId: string, sessionKey: string, lastMessageId: number): Promise<{
|
|
84
|
+
messages: NeutralMessage[];
|
|
85
|
+
meta: WindowCacheMeta;
|
|
86
|
+
} | null>;
|
|
87
|
+
/**
|
|
88
|
+
* Store compose result metadata alongside the window cache.
|
|
89
|
+
* Enables the C4 fast-exit to return a complete ComposeResult without re-running.
|
|
90
|
+
*/
|
|
91
|
+
setWindowMeta(agentId: string, sessionKey: string, meta: WindowCacheMeta, ttl: number): Promise<void>;
|
|
92
|
+
getWindowMeta(agentId: string, sessionKey: string): Promise<WindowCacheMeta | null>;
|
|
70
93
|
setCursor(agentId: string, sessionKey: string, cursor: SessionCursor): Promise<void>;
|
|
71
94
|
getCursor(agentId: string, sessionKey: string): Promise<SessionCursor | null>;
|
|
72
95
|
warmSession(agentId: string, sessionKey: string, slots: {
|