@unblocklabs/unblock-memory 0.3.21 → 0.3.23

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.
@@ -0,0 +1,576 @@
1
+ # Retrieval, whisperers and maintenance
2
+
3
+ [Overview](../README.md) · [Configuration and credentials](configuration.md)
4
+
5
+ ## Search and read
6
+
7
+ `memory_search` searches every configured **non-skill** corpus for this agent by
8
+ default. Select named corpora, or use `["all"]` alone. An empty list or unknown
9
+ name is an error. Default: **5 results**, vector `minScore: 0.3`; configurable
10
+ `maxResults` is 1–20 and `minScore` is 0–1. No TypeSafe, BM25, query expansion
11
+ or reranker runs in this tool.
12
+
13
+ Example tool input (the `memory` corpus exists by default):
14
+
15
+ ```json
16
+ { "query": "Who approved the staging rollout?", "corpora": ["memory"], "maxResults": 5 }
17
+ ```
18
+
19
+ Results carry `path`, `startLine`, `endLine`, `snippet`, `score`, `corpus`
20
+ and citation data; session hits also carry session metadata. Vector similarity
21
+ is a retrieval signal, not confidence in the truth of a claim.
22
+
23
+ Read the **returned** path, substituting its actual source/line values:
24
+
25
+ ```json
26
+ { "path": "qmd://source-RETURNED_ID/memory/example.md", "from": 12, "lines": 40 }
27
+ ```
28
+
29
+ `memory_get` reads the indexed snapshot, not arbitrary filesystem paths or QMD
30
+ docids. It accepts exact configured `qmd://` paths, excludes skills, defaults to
31
+ 120 lines and bounds content to 12,000 characters. `from` is 1-based; requested
32
+ `lines` is 1–1,000. `status: "ok"` can still have `truncated: true`: use
33
+ `nextFrom` to continue when present. A single oversized line can be clipped
34
+ without a continuation line; inspect the authorized original source if that
35
+ complete line matters. `not_found` and `unavailable` are not successful empty reads.
36
+
37
+ Search snippets are leads. Inspect attribution, qualifications, dates and adjacent
38
+ context before relying on a factual claim. An old plan is not proof it happened,
39
+ and memory does not grant permission to act. No results may mean the wrong corpus,
40
+ a high threshold, an unsynced session or an unavailable index—not absence of the fact.
41
+ Search initialization errors include an `error` alongside empty `results`;
42
+ other failures may surface as tool errors. Investigate them instead of reporting
43
+ “nothing is remembered.”
44
+
45
+ Corpora and session filters select evidence; they are not audience ACLs. Normal
46
+ tools can access this agent's configured non-skill corpora, not just its current
47
+ chat. Only index material suitable for the agent's tool callers. Per-feature
48
+ TypeSafe allowlists do not restrict ordinary retrieval.
49
+
50
+ ## QMD search modes
51
+
52
+ The following describes QMD's TypeSafe-ranked `query` API (2.10+). Use the exact
53
+ dependency in the installed plugin's package metadata when diagnosing an older
54
+ installation.
55
+
56
+ | Surface | Retrieval/ranking | Score / useful distinction |
57
+ | --- | --- | --- |
58
+ | Plugin `memory_search` | Direct vector search, expansion off | Vector similarity; ordinary agent recall |
59
+ | QMD CLI `search` / SDK `searchLex` | Local BM25 | Normalized lexical relevance; useful for names, identifiers and exact phrases |
60
+ | QMD CLI/SDK `vsearch` | Local vectors; standalone default includes local query expansion | Vector similarity; `--no-expand` / `expand:false` selects literal retrieval |
61
+ | QMD CLI/MCP `query` / SDK `search` | Literal vectors + BM25, deduplicated source excerpts, independent TypeSafe usefulness ranking | Usefulness divided by 3, not cosine similarity; default limit 10/minScore 0 |
62
+ | QMD `query --no-rerank` / SDK/MCP `rerank:false` | Local retrieval, best reciprocal retrieval-rank ordering | 1 / rank; explicitly skips remote scoring |
63
+
64
+ Plain `query` takes `ceil(1.5 × limit)` candidates per backend, without local
65
+ expansion or local reranking. Explicit `lex`/`vec`/`hyde` variants share the
66
+ TypeSafe ranking policy by default. A supplied hypothetical `hyde` passage is
67
+ a retrieval input, not evidence. Scores across modes are not interchangeable.
68
+
69
+ **Migration:** Memory 0.3.22 removed the temporary `memory_xsearch` tool. QMD's
70
+ `query` provides that hybrid retrieval/ranking functionality; there is no plugin
71
+ `memory_query`, and `memory_search` did not become hybrid.
72
+
73
+ Standalone QMD is a separate entry point. Its project/named/global index is not
74
+ automatically this agent's `unblock-memory/index.sqlite`. Plugin corpus names
75
+ also are not QMD collection IDs: each configured path maps to a `source-<hash>`
76
+ collection, and a corpus can contain several. Establish the intended index,
77
+ collection scope and installed QMD version before using CLI/MCP as an alternative.
78
+ Do not run standalone collection/update/embed maintenance against a live
79
+ plugin-managed index as a casual search fallback.
80
+
81
+ QMD CLI/MCP read `TYPESAFE_API_KEY` or `TYPESAFE_API_KEY_FILE` from their own
82
+ process; SDK callers can supply credentials. Plugin `typesafe.apiKeyFile` does
83
+ not export a key to those processes. Ranked query sends the query, intent,
84
+ selected excerpts, source paths and evaluation time to TypeSafe. Missing keys or
85
+ scoring failures return errors, not vector fallback or a successful empty answer.
86
+ Use an explicitly local mode when appropriate.
87
+
88
+ See the [QMD guide](https://github.com/unblocklabs-ai/qmd#readme) for CLI/MCP syntax.
89
+ Private skill frontmatter retrieval and response-audit lexical investigations are
90
+ internal workflows, not extra public plugin search modes.
91
+
92
+ ## Sessions
93
+
94
+ Add a `sessions` entry alongside `memory` in your configured corpora (see the
95
+ [session profile](configuration.md#example-profiles)). Start a manual refresh with
96
+ `memory_sync_sessions({})`, then poll `memory_sync_status({})` until completed
97
+ or failed. `started` / `already_running` only acknowledge background work.
98
+ Check completion counts, including per-session failures, before assuming freshness.
99
+
100
+ Use `sessionFilter` to restrict session results by metadata while leaving file
101
+ corpora searchable. Supported fields are `startedFrom` and `startedTo`
102
+ (inclusive ISO 8601 timestamps), `provider`, `chatType`, `accountId`, and
103
+ `conversationId`:
104
+
105
+ ```json
106
+ {
107
+ "query": "deployment decision",
108
+ "sessionFilter": {
109
+ "startedFrom": "2026-08-01T00:00:00Z",
110
+ "provider": "slack",
111
+ "chatType": "channel"
112
+ }
113
+ }
114
+ ```
115
+
116
+ Provider matching is case-normalized; `chatType` uses the lowercase values
117
+ shown in the configuration example. Account and conversation IDs are trimmed
118
+ and matched exactly. When only `sessions` is selected and no sessions match,
119
+ search returns no results. With other corpora selected, their results remain
120
+ eligible.
121
+
122
+ The date bounds are inclusive **session start times**, not dates of messages or
123
+ claims. A matching session may contain much older facts. These metadata filters
124
+ do not change the selected file corpora or authorize disclosure to another audience.
125
+
126
+ The optional `sessions` corpus reads the current agent's normal OpenClaw SQLite
127
+ store and indexes its active user/assistant transcript branch. It defaults to
128
+ channel and group conversations; add `direct` explicitly to include DMs. A
129
+ session vector hit expands to its complete user/assistant turn when the turn
130
+ fits `maxExpandedTokens`, or to its complete enclosing message when only that
131
+ fits. The default is `500`; the original semantic chunk is preserved when
132
+ neither complete context fits, so expansion never clips the matched evidence.
133
+ Run
134
+ `memory_sync_sessions` to start a refresh, then use `memory_sync_status` to
135
+ check its progress or result. The read-only adapter explicitly supports OpenClaw
136
+ agent database schemas 17, 18, and 19 and validates its required columns before
137
+ reading. Projections are private derived Markdown under the
138
+ agent's `unblock-memory/sessions` state directory and can be rebuilt from
139
+ OpenClaw at any time. Their embedded text contains only `# Transcript` and
140
+ role-labeled, timestamped speaker messages; filtering metadata remains in the
141
+ session manifest. The projected file modification time matches the session
142
+ start time for meaningful chronological cluster reads. Session results include
143
+ provider, chat type, conversation identity, and start time as an ISO 8601 timestamp. They
144
+ participate in the same search and clustering index as file memory. The plugin
145
+ automatically checks each configured agent's sessions every 60 minutes while
146
+ the Gateway runs. Set `syncIntervalMinutes` on the `sessions` corpus to an integer
147
+ from `1` to `1440`, or `0` for manual-only syncing. For example:
148
+
149
+ ```json
150
+ { "name": "sessions", "kind": "sessions", "syncIntervalMinutes": 60 }
151
+ ```
152
+
153
+ The first refresh runs after one interval, not during startup. Restart the
154
+ Gateway after changing the interval. Refreshes are incremental; an already-running
155
+ sync is skipped, and failures are visible through `memory_sync_status` and retried
156
+ at the next interval. `memory_sync_sessions` still provides an immediate manual
157
+ refresh. Syncing and embedding run inside the Gateway process, without an LLM turn.
158
+
159
+ Quiet checks compare source metadata and the last successful index checkpoint
160
+ before initializing the memory manager. Unchanged sessions skip QMD updates and
161
+ embedding. New assistant answers count too, not just human messages. Changed
162
+ transcripts are projected and content-hashed; tool-only or filtered additions
163
+ that leave the indexed text unchanged also skip indexing. Empty/filtered sessions
164
+ are remembered. Index changes, missing projections, changed projection settings,
165
+ QMD upgrades and incomplete runs invalidate the skip checkpoint; `force: true`
166
+ bypasses both gates. `memory_sync_status` reports `lastCheckedAt`, `lastIndexedAt`
167
+ and `skipReason` (`no_changes` or `no_indexable_changes`) separately. Existing
168
+ explicit intervals remain unchanged on upgrade; set them to `60` for hourly checks.
169
+
170
+ Indexes live at `~/.openclaw/agents/<agentId>/unblock-memory/index.sqlite` (or the
171
+ equivalent configured OpenClaw state directory). Durable agent-supplied event
172
+ dates, maintenance proposals, people/dossiers and response audits live separately
173
+ in `unblock-memory.sqlite`, so a QMD
174
+ index rebuild does not discard them. The first lookup builds the index;
175
+ Markdown filesystem changes queue a debounced, serialized background refresh.
176
+
177
+ ### Loggie meeting interoperability
178
+
179
+ Loggie v0.1.12+ persists versioned, speaker-attributed meeting Markdown separately
180
+ from its workflow prompt. Session projection recognizes that format and also
181
+ normalizes complete legacy Loggie JSON envelopes. Unrecognized, malformed or
182
+ truncated legacy payloads keep their original text; source sessions are never
183
+ rewritten. Summaries remain labeled as generated material, distinct from speech.
184
+
185
+ QMD groups adjacent speaker blocks rather than forcing one chunk per speaker.
186
+ Search expands around the matching exchange within its existing budget. Long
187
+ monologue excerpts regain the source speaker label while citations still point
188
+ to the exact original source lines. No identity or timestamp is invented.
189
+
190
+ Within a session, identical replayed transcripts are suppressed; distinct
191
+ complete revisions with ordered source sequence numbers retain their history
192
+ and assistant follow-ups, with older versions marked superseded. Account,
193
+ workspace, meeting and external transcript identifiers scope the comparison.
194
+ Ambiguous/partial revisions are preserved. Separate session windows are not
195
+ globally deduplicated.
196
+
197
+ Use the session projection as the searchable meeting copy. Loggie raw archives
198
+ remain opt-in and should stay outside file-corpus globs (new default:
199
+ `transcripts/loggie-archive`). Memory never follows archive paths embedded in
200
+ messages. Truncated sessions stay explicitly incomplete; enabling archive
201
+ enrichment is not part of this version.
202
+
203
+ ### Conservative ingestion cleanup
204
+
205
+ Session projections unwrap complete, recognized task/attachment envelopes while
206
+ keeping the actual result, task/status, filename, MIME type, and untrusted-content
207
+ label. Internal task cleanup requires structured inter-session provenance, not
208
+ just matching text. Unknown formats, malformed envelopes, and code examples stay
209
+ intact. Assistant messages and Loggie's separate projection path are unaffected.
210
+ Raw session events and workspace memory files are never rewritten.
211
+ Attachment matching has a fixed work budget; oversized or repeatedly nested/
212
+ incomplete envelopes leave the entire message unchanged rather than blocking sync.
213
+
214
+ The companion QMD semantic-chunking update skips only source-confirmed standalone
215
+ REM heading/marker spans and orphan closing fences. Reflections and useful text
216
+ remain searchable, with original source offsets. These are deterministic rules,
217
+ not TypeSafe judgments; audit flags never authorize automatic memory deletion.
218
+
219
+ The plugin installs its exact release-pinned QMD dependency (see
220
+ [package metadata](../package.json)). Projector/chunker version changes refresh derived
221
+ projections and embeddings on their next normal sync; the first sync may take
222
+ longer while re-embedding. No manual deletion of source memories or review tasks
223
+ is needed.
224
+
225
+ ## Memory Whisperer
226
+
227
+ Memory Whisperer is optional and **off by default**. It proactively retrieves
228
+ historical context before user-triggered turns, without changing `memory_search`
229
+ or `memory_get`. Enable it in the plugin config with an explicit corpus allowlist:
230
+
231
+ ```json
232
+ {
233
+ "memoryWhisperer": {
234
+ "enabled": true,
235
+ "corpora": ["knowledge"],
236
+ "historyMessages": 5,
237
+ "minUsefulness": 0.9,
238
+ "maxHints": 2,
239
+ "cooldownTurns": 10,
240
+ "timeoutMs": 3000
241
+ }
242
+ }
243
+ ```
244
+
245
+ Requires `hooks.allowConversationAccess: true` on the plugin entry, prompt
246
+ injection permission, and [shared TypeSafe credentials](configuration.md#shared-typesafe-credentials).
247
+ An empty allowlist is invalid when enabled; `all`, unknown names, and `skills`
248
+ are not accepted. File corpora are approved for **every audience using the agent**:
249
+ do not allowlist private dossiers for an agent that also serves shared channels.
250
+ If `sessions` is allowlisted, only the exact current session is searched, including
251
+ its older indexed messages. Missing session identity excludes that corpus. Other
252
+ sessions, even in the same channel, are excluded before sending excerpts to TypeSafe.
253
+ Session availability still depends on the normal indexing/sync schedule.
254
+
255
+ The example is a plugin config fragment; `knowledge` must already be configured.
256
+ For a complete corpus example, use the [configuration profiles](configuration.md#example-profiles).
257
+
258
+ QMD searches the current request plus the last N user/assistant messages (at most
259
+ 12,000 characters), retrieving up to eight vector candidates without query expansion,
260
+ the local reranker, or a similarity-score cutoff. TypeSafe evaluates one independent
261
+ Noul question per candidate in a single request: does the excerpt add material value
262
+ beyond what the conversation already contains? Merely related, redundant,
263
+ wrong-person/project, and clearly superseded information should be rejected;
264
+ useful contradictory evidence can qualify. `minUsefulness` thresholds the probability
265
+ of yes, not a calibrated guarantee of accuracy. Evaluate it on your own conversations.
266
+
267
+ **Privacy and budgets:** this feature sends up to 16,000 characters of the available
268
+ user/assistant conversation, prioritizing the current request and recent messages,
269
+ plus up to eight 1,200-character excerpts, corpus names, and session dates to
270
+ `api.typesafe.ai`. Session excerpts retain a complete turn or message when it fits,
271
+ otherwise the complete matched chunk. Chunks exceeding the excerpt budget are
272
+ skipped, never sliced; ordinary `memory_search` is unchanged.
273
+ It does not fetch a complete historical transcript; the host may
274
+ already have compacted the available context. Truncation is marked in the judge's
275
+ input. System messages, thinking blocks, images, and tool-result messages are omitted;
276
+ anything quoted in ordinary user/assistant text can still be transmitted.
277
+
278
+ At most two qualifying excerpts are injected verbatim with source references and
279
+ historical/untrusted-data framing. Excerpts are deduplicated by normalized content
280
+ and overlapping source lines; recently injected content has a ten-user-turn cooldown
281
+ by default. Cooldown state is in memory and resets on session end or Gateway restart.
282
+ The complete hint payload is capped at 5,000 characters plus a short framing paragraph.
283
+
284
+ Unlike Skill Whisperer, **disabled TypeSafe, a missing key, no qualifying hits, or any
285
+ failure means no memory hint**—there is no vector-only fallback. The overall process
286
+ has a 3-second deadline, with the shared 1.5-second TypeSafe request deadline inside it;
287
+ neither performs retries. Timed-out or superseded runs cannot inject late hints.
288
+ Already-running local QMD work may finish in the background, but does not keep the
289
+ agent waiting beyond the deadline. No new indexing, clustering, or summarization runs
290
+ are triggered by this feature beyond the memory manager's normal initialization.
291
+
292
+ ## Skill Whisperer
293
+
294
+ Skill Whisperer is an optional semantic reminder for user turns. Configure one
295
+ isolated `skills` corpus, set `skillWhisperer.enabled` to `true`, and authorize
296
+ `plugins.entries.unblock-memory.hooks.allowConversationAccess`. The feature
297
+ embeds the current prompt plus the configured number of prior user/assistant
298
+ messages, compares it with each configured skill's frontmatter `name` and
299
+ `description`. With TypeSafe enabled and a key available, the top three valid
300
+ candidates are sent to TypeSafe, without a vector-score cutoff. TypeSafe chooses
301
+ one skill or none. A "none" decision never falls back to a vector hint. Full skill
302
+ procedures do not influence routing; no skill is invoked automatically.
303
+
304
+ See [shared TypeSafe credentials](configuration.md#shared-typesafe-credentials) for key setup,
305
+ rotation and the `enabled: true` / `timeoutMs: 1500` defaults.
306
+
307
+ If TypeSafe is disabled or no key is found, selection uses the original local
308
+ vector process and `skillWhisperer.minScore`. With a key present, an API error,
309
+ invalid response, or timeout emits no hint and logs a sanitized warning; it does
310
+ not switch to vector-only selection. There are no automatic HTTP retries. Other
311
+ credential-file read errors likewise produce a warning and no hint.
312
+
313
+ **Privacy:** enabled TypeSafe selection sends up to 12,000 characters of current
314
+ prompt/recent user-assistant text, plus the shortlisted names/descriptions, to
315
+ `api.typesafe.ai`. Source-path fields, full skill procedures, tool-result messages,
316
+ and system messages are excluded; dossiers and ordinary memory files are not read
317
+ for this call. Material already quoted in user/assistant text can still be included.
318
+ Disable `typesafe.enabled` to keep Skill Whisperer entirely local. The pinned model
319
+ is `jev-1.13.0`.
320
+
321
+ The defaults use five prior messages, a vector-only score threshold of `0.5`,
322
+ and a ten-turn cooldown. A skill is cooling down after either a suggestion or a
323
+ successful direct `read` of its indexed `SKILL.md`. When the selected
324
+ skill is cooling down, no hint is emitted; Skill Whisperer does not fall through
325
+ to a weaker match. Cooldown state is per session and intentionally resets with
326
+ the Gateway. Shell-command reads are not tracked.
327
+
328
+ The `skills` corpus shares the existing QMD store and warm embedding model but
329
+ is private to Skill Whisperer: it is excluded from ordinary `memory_search`
330
+ (including `corpora: ["all"]`), `memory_get`, clustering, and memory-maintenance
331
+ tasks. Paths are explicit by design; the plugin does not reconstruct
332
+ OpenClaw's effective skill inventory from `openclaw.json`. Configured skill
333
+ globs follow symlinked directories, including OpenClaw's `plugin-skills`
334
+ directory.
335
+
336
+ ## Review and diagnostics
337
+
338
+ - `memory_diagnostics` reports credential **availability only**, per-agent process-local
339
+ whisperer counters, projection version, old indexed-session projection count, and
340
+ embedding readiness. Counters are bounded to 100 agents and reset on restart.
341
+ This is not a complete people/response feature-status report; it makes no TypeSafe
342
+ request, but can initialize the memory manager/index on first use.
343
+ No prompts, excerpts, paths, keys, or provider error bodies enter these counters.
344
+ Parser cleanup/budget-skip counts are persisted with the latest completed
345
+ `memory_sync_status`; unchanged sessions are not counted again. QMD structural
346
+ omission counts cover this manager's embedding passes, not the whole corpus.
347
+ - Quality-audit groups distinguish `preserve_evidence_repair`, `inspect_scaffolding`,
348
+ and `context_review`, reusing cached noise/evidence judgments without another call.
349
+ Evidence-preserving repair tasks sort first. Maintenance tasks expose indexed
350
+ fingerprint presence; `not_present_in_index` is **not** a verified repair and
351
+ never resolves or deletes the task. Chunk boundaries may simply have changed.
352
+ - `memory_review_cluster` uses the existing `qualityAudit` opt-in/corpus allowlist.
353
+ It judges up to three representative and three low-membership members, deduplicates
354
+ the sample, and skips unapproved or >2,000-character chunks whole. Repeated defect
355
+ labels are investigation leads only. Stale/changed samples are rejected; useful
356
+ or uncertain members are retained. No tasks or sources are modified.
357
+ - `memory_review_claim` accepts one atomic claim (up to 2,000 characters) and 1–3
358
+ citations `{path, from, lines}`. It reads approved indexed evidence itself (at
359
+ most 6,000 characters), returns supports/contradicts/insufficient_evidence with
360
+ confidence and source hashes, and never writes or authorizes a write. Support
361
+ below 0.9 confidence is marked for review. This threshold is provisional, not a
362
+ guarantee of truth; read original evidence and verify current-state claims.
363
+
364
+ Optional plugin config fragment (corpora must already be configured):
365
+
366
+ ```json
367
+ {
368
+ "evidenceReview": { "enabled": true, "corpora": ["memory", "knowledge", "sessions"] },
369
+ "memoryWhisperer": {
370
+ "enabled": true, "corpora": ["memory", "knowledge"], "complementaryHints": true
371
+ }
372
+ }
373
+ ```
374
+
375
+ Both additions default off. Claim review sends the proposed claim and approved
376
+ source excerpts to TypeSafe; cluster review sends approved sampled excerpts.
377
+ Complementary hints use one extra bounded call over at most four already-useful
378
+ candidates (six directional comparisons). Only redundancy probability >=0.9
379
+ removes a hint; distinct evidence and contradictions should remain. Provider errors
380
+ retain baseline hints, while the existing total turn deadline/cancellation still
381
+ suppresses late results. Missing keys or disabled TypeSafe never enable these calls.
382
+ The retrieval corpus/session boundaries are unchanged.
383
+
384
+ ## Memory quality audit
385
+
386
+ `memory_audit_quality` is an on-demand, source-read-only audit. TypeSafe flags likely
387
+ ingestion noise for agent investigation; it never deletes, rewrites, or suppresses
388
+ memory. Enable it with explicit approval for the corpora sent to TypeSafe:
389
+
390
+ ```json
391
+ {
392
+ "qualityAudit": {
393
+ "enabled": true,
394
+ "corpora": ["memory", "knowledge"],
395
+ "minNoise": 0.8
396
+ }
397
+ }
398
+ ```
399
+
400
+ This is a plugin config fragment; both corpora must already be configured.
401
+ Off by default. Uses the shared TypeSafe credentials and request timeout. Missing
402
+ credentials or disabled TypeSafe produces no audit. Approval includes transmission
403
+ of full eligible chunks and visibility of findings to all audiences using the agent.
404
+ Unlike Memory Whisperer, approving `sessions` includes **all indexed sessions** in
405
+ that corpus, including configured direct conversations. Only approve that when intended.
406
+
407
+ Call with `{ "limit": 10 }` (maximum 20 indexed chunk occurrences per page), then
408
+ pass the returned `next` as `after` until `done` is true. A `partial` result preserves
409
+ the completed cursor; retry there, or from the beginning if no cursor exists. This
410
+ is not a full-document audit: unindexed content is not scanned. Chunks over 6,000
411
+ characters are counted as skipped, not silently truncated. No clustering is required.
412
+
413
+ Two independent Noul questions distinguish ingestion noise from identifiable useful
414
+ evidence. High values for both can indicate valuable content trapped in a wrapper.
415
+ Low evidence alone does not create a junk finding. JSON, logs, code, terse facts,
416
+ historical records, and missing context are not automatically defects. Empty chunks
417
+ are detected locally. A JSON string that decodes to a message envelope is also
418
+ flagged as a possible double-encoding defect, even when its content is useful.
419
+ An ordinary JSON message object is not flagged from its shape alone. These are
420
+ review clues, never verdicts about whether the information should be kept.
421
+
422
+ At most four unique chunks (24,000 characters) and their source kinds are sent in
423
+ one request, without conversation context or source paths. Requests do not retry
424
+ automatically and stop starting new work after a 30-second audit deadline; existing
425
+ manager initialization/indexing may finish later. Judgments are cached in the
426
+ curation database by content, source kind, model and question version. A rescan from
427
+ the beginning reuses cached results, including after corpus/index changes. Changes
428
+ behind a page cursor are picked up on the next rescan.
429
+
430
+ Suspect chunks become `quality_review` tasks in `memory_list_maintenance_tasks`.
431
+ The audit returns page-local groups by configured source and suspected issue,
432
+ with up to three examples each, not a claim that a whole cluster is defective.
433
+ Findings include source references, bounded previews, probabilities and content
434
+ fingerprints. Reviewed tasks are not reopened for unchanged content. The curator
435
+ inspects the original source and ingestion path, proposes or performs authorized
436
+ repairs, and verifies the resulting source/index before resolving with a required
437
+ note. Prefer repairing a common extractor or inclusion rule over many symptoms;
438
+ never manually edit generated session projections. Thresholds need evaluation on
439
+ your data; model probability is not proof of a defect.
440
+
441
+ ## Memory analysis
442
+
443
+ Analysis is opt-in. Core indexing, `memory_search`, and `memory_get` need only
444
+ Unblock Memory and its automatically installed QMD dependency. To enable
445
+ clustering, install the public
446
+ [`unblock-cluster`](https://github.com/unblocklabs-ai/unblock-cluster) worker once
447
+ on the same host:
448
+
449
+ ```bash
450
+ git clone https://github.com/unblocklabs-ai/unblock-cluster.git
451
+ cd unblock-cluster
452
+ python3 -m venv .venv
453
+ .venv/bin/python -m pip install -r requirements-analysis.txt
454
+ ```
455
+
456
+ Set `analysis.executable` to the absolute path of
457
+ `bin/unblock-memory-analysis` in that checkout. One worker installation can
458
+ serve every agent on the host. The plugin invokes it directly with
459
+ `--db <the agent's known index path>`, the plugin's non-skill collection IDs,
460
+ and, when requested, a validated `--config-json <clustering options>` payload.
461
+ Agents cannot choose a database, executable, collection, shell command, or
462
+ arbitrary arguments.
463
+
464
+ Without the worker, `memory_list_clusters` reports that memory has not been
465
+ analyzed and `memory_recluster` reports that analysis is unavailable. Ordinary
466
+ memory search and reads continue to work.
467
+
468
+ The analysis worker reads QMD's existing semantic vectors and writes only
469
+ derived results into four namespaced tables in that same `index.sqlite`:
470
+
471
+ - `memory_analysis_runs`
472
+ - `memory_analysis_clusters`
473
+ - `memory_analysis_memberships`
474
+ - `memory_analysis_duplicate_occurrences`
475
+
476
+ Unblock Memory exposes:
477
+
478
+ - `memory_list_clusters` to cheaply list current clusters and report whether the
479
+ retained analysis is stale
480
+ - `memory_recluster` to explicitly rebuild clusters when the list is missing or stale
481
+ - `memory_fetch_cluster` to return a sorted, paginated selection of QMD chunks
482
+ for a short `clusterId` returned by `memory_list_clusters`
483
+
484
+ `memory_recluster` optionally accepts UMAP controls (`method`, components,
485
+ neighbors, and minimum distance), HDBSCAN controls (minimum cluster size,
486
+ minimum samples, selection method and epsilon, and single-cluster behavior),
487
+ and a deterministic seed. Omitting them uses the worker's defaults.
488
+
489
+ `memory_fetch_cluster` accepts `topK` (1–50), a zero-based `offset`, and
490
+ `sort`: `representative` (the default), `score_desc`, `score_asc`, `date_desc`,
491
+ or `date_asc`. Score is cluster membership probability for normal clusters and
492
+ outlier score for noise. Each member reports raw `sourceModifiedAt` separately
493
+ from `eventTime` and `eventTimeBasis`. Session start times and dated memory paths
494
+ resolve programmatically; reviewed annotations resolve otherwise ambiguous
495
+ chunks or whole documents. Date sorting uses resolved event time when available
496
+ and the clearly labeled source modification time only as a fallback. Responses
497
+ include page totals and the next offset when more members remain.
498
+
499
+ A chronological cluster read creates a coalesced maintenance proposal only for
500
+ returned documents whose event time remains ambiguous; it does not scan the
501
+ whole corpus for chores. Persisted exact-duplicate analysis can likewise create
502
+ review proposals for non-session Markdown. `memory_list_maintenance_tasks`
503
+ returns at most ten tasks, while `memory_update_maintenance_task` can resolve,
504
+ defer, or mark one irrelevant and optionally attach a supported event date.
505
+ For duplicate proposals, defer confirmed cleanup until the source change is
506
+ complete, mark intentional repetition irrelevant, and resolve only completed
507
+ work. These tools never edit or delete source Markdown. Duplicate cleanup
508
+ remains a reviewed source change outside the maintenance tool, and generated
509
+ session projections must never be edited directly.
510
+
511
+ Member excerpts are capped at 2 KB each and 12 KB across a response; source
512
+ aliases are capped at five per member and 50 across a response. These budgets
513
+ are shared across the page so every returned member receives a useful excerpt
514
+ and at least one source path, including a full 50-member page.
515
+
516
+ If indexing changes content or vectors, the previous derived analysis is kept
517
+ and marked stale. Cluster reads include the analysis timestamp, stale timestamp,
518
+ and a hint to call `memory_recluster`; unavailable chunks reduce `availableSize`
519
+ without copying canonical text into analysis tables. A no-op sync stays fresh.
520
+ A failed rebuild leaves the stale result intact, while a successful rebuild
521
+ atomically replaces it. Analysis is never scheduled automatically. If the worker
522
+ is absent or fails, `memory_search` and `memory_get` continue to work.
523
+
524
+ ## Curating knowledge
525
+
526
+ The plugin bundles the `memory-curator` skill for turning useful clusters into
527
+ durable knowledge. It becomes available when the plugin is enabled. If the
528
+ agent has an explicit skill allowlist, include `memory-curator`.
529
+
530
+ Keep maintained knowledge outside `memory/**` so each file belongs to only one
531
+ corpus. Use stable topic files updated in place:
532
+
533
+ ```text
534
+ knowledge/
535
+ ├── fleet.md
536
+ ├── people/
537
+ │ └── rico.md
538
+ └── projects/
539
+ └── unblock-memory.md
540
+ ```
541
+
542
+ Knowledge is the agent's maintained, current understanding of its unique world:
543
+ facts such as fleet membership, local decisions and preferences, assessments,
544
+ and explicit uncertainty that would be expensive to reconstruct from scattered
545
+ history. Each claim should carry its own epistemic qualification so it remains
546
+ honest when semantic chunking retrieves it alone. Remove stale conclusions
547
+ instead of preserving history, changelogs, or `Supersedes` passages in the same
548
+ file; raw memory and sessions retain the evidence history.
549
+
550
+ Public or vendor-owned facts, generic command syntax, and behavior likely to
551
+ change with third-party releases should normally be looked up from the current
552
+ authoritative source. A local policy or deliberate divergence may belong in
553
+ knowledge, but the local decision—not copied generic documentation—is the
554
+ durable content.
555
+
556
+ For a manual run, ask the agent:
557
+
558
+ ```text
559
+ Use $memory-curator to review my memory clusters and curate any durable updates.
560
+ ```
561
+
562
+ For recurring curation, use an OpenClaw automation with the same thin message:
563
+
564
+ ```text
565
+ Use $memory-curator to run the scheduled memory curation cycle.
566
+ ```
567
+
568
+ The skill treats a cluster as an incomplete attention signal. It frames the
569
+ question raised, uses representative, score, and chronological views as useful,
570
+ searches existing knowledge and adjacent corpora, and investigates live systems,
571
+ files, documentation, or the web when those are better evidence. It then updates
572
+ a stable knowledge topic or correctly writes nothing. Its own writes are indexed
573
+ for the next cycle; it does not recluster recursively in the same run.
574
+
575
+ Existing `unblock-qmd` indexes are derived caches and may be left in place;
576
+ Unblock Memory rebuilds its own index from configured corpora.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "unblock-memory",
3
3
  "name": "Unblock Memory",
4
- "version": "0.3.21",
4
+ "version": "0.3.23",
5
5
  "description": "Indexes, retrieves, and analyzes configured workspace memory with existing QMD vectors.",
6
6
  "kind": "memory",
7
7
  "activation": { "onStartup": true },
@@ -9,7 +9,6 @@
9
9
  "contracts": {
10
10
  "tools": [
11
11
  "memory_search",
12
- "memory_xsearch",
13
12
  "memory_get",
14
13
  "memory_sync_sessions",
15
14
  "memory_sync_status",
@@ -46,14 +45,6 @@
46
45
  "memory_people_sync": { "sideEffecting": true, "optional": true }
47
46
  },
48
47
  "uiHints": {
49
- "xsearch.enabled": {
50
- "label": "Hybrid search with TypeSafe",
51
- "help": "Opt in to vector + BM25 retrieval and independent usefulness reranking. Sends queries and approved corpus excerpts to TypeSafe."
52
- },
53
- "xsearch.corpora": {
54
- "label": "Hybrid search approved corpora",
55
- "help": "Explicit non-skill corpora allowed for TypeSafe reranking. Required when enabled."
56
- },
57
48
  "peoplePrimer.enabled": { "label": "People Background Primer", "help": "Opt in to sending identity, approved excerpts and proposed snippets to TypeSafe. Prepares evidence and checks <=70-word blurbs before replace_dossier saves; disabled/unavailable reviews require explicit manual verification. Existing dossiers are not evidence. Results are accessible to the agent's tool callers." },
58
49
  "peoplePrimer.corpora": { "label": "Primer Approved Corpora", "help": "Explicit non-skill corpus allowlist. Sessions includes all indexed conversations; approve only content suitable for this agent's audiences." },
59
50
  "responseAudit.enabled": { "label": "Response Quality Audit", "help": "Opt in to background TypeSafe evaluation of approved Slack humans. Operator-only reports; no prompt or memory writes." },
@@ -72,10 +63,11 @@
72
63
  "help": "Explicit non-skill corpora approved for external TypeSafe processing and maintenance results visible to every audience using this agent. Sessions means ALL indexed sessions, not only the current conversation."
73
64
  },
74
65
  "typesafe.enabled": {
75
- "label": "TypeSafe Ranking",
76
- "help": "Enabled by default when credentials exist. Sends bounded conversation context and skill descriptions or approved memory excerpts to TypeSafe for enabled whisperers."
66
+ "label": "TypeSafe Judgments",
67
+ "help": "Shared provider gate for opted-in whisperers, people primer/save review, evidence/quality reviews and response audits. Does not enable features or approve corpora by itself. Ordinary search and People Whisperer injection stay local."
77
68
  },
78
69
  "typesafe.apiKey": { "label": "TypeSafe API Key", "sensitive": true },
70
+ "typesafe.timeoutMs": { "label": "TypeSafe Request Timeout", "help": "Per-request timeout, not the whole operation. People primer/dossier review use peoplePrimer.timeoutMs instead; Memory Whisperer also has its own total deadline." },
79
71
  "typesafe.apiKeyFile": {
80
72
  "label": "TypeSafe Key File",
81
73
  "help": "Absolute path to a plaintext API key or a dotenv file containing TYPESAFE_API_KEY. Prefer this over storing a key in config."
@@ -102,12 +94,13 @@
102
94
  },
103
95
  "people.enabled": {
104
96
  "label": "PeopleSQL",
105
- "help": "Maintain an agent-local people store. Disabled by default."
97
+ "help": "Enable the agent-local people store, Slack identity observation and people tools. Disabled by default; whispering and TypeSafe primer/review are separate opt-ins."
106
98
  },
107
99
  "people.whisperer.enabled": {
108
100
  "label": "People Whisperer",
109
- "help": "Inject an enabled person's bounded dossier blurb after exact identity matching. Requires hook conversation access."
101
+ "help": "Inject an active, injection-enabled person's saved blurb once per Slack thread/person, with durable receipts. Requires people.enabled and host hook permissions; no model call."
110
102
  },
103
+ "people.whisperer.maxChars": { "label": "People Blurb Character Limit", "help": "Also limits newly saved dossier blurbs when injection is off. The separate 70-word ceiling still applies." },
111
104
  "analysis.executable": {
112
105
  "label": "Memory Analysis Worker",
113
106
  "help": "Optional absolute path to the locally installed unblock-memory-analysis executable."
@@ -131,15 +124,6 @@
131
124
  "memoryCorpora": { "type": "array", "maxItems": 50, "items": { "type": "string", "pattern": "\\S" }, "default": [] }
132
125
  }
133
126
  },
134
- "xsearch": {
135
- "type": "object",
136
- "additionalProperties": false,
137
- "properties": {
138
- "enabled": { "type": "boolean", "default": false },
139
- "corpora": { "type": "array", "items": { "type": "string", "minLength": 1 }, "default": [] },
140
- "timeoutMs": { "type": "integer", "minimum": 1, "maximum": 30000, "default": 10000 }
141
- }
142
- },
143
127
  "qualityAudit": {
144
128
  "type": "object",
145
129
  "additionalProperties": false,