@unblocklabs/unblock-memory 0.3.15 → 0.3.17

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.
Files changed (50) hide show
  1. package/README.md +303 -15
  2. package/dist/src/config.d.ts +4 -0
  3. package/dist/src/config.js +8 -2
  4. package/dist/src/evidence-review.d.ts +9 -1
  5. package/dist/src/evidence-review.js +2 -1
  6. package/dist/src/manager.d.ts +5 -1
  7. package/dist/src/people-background.d.ts +3 -0
  8. package/dist/src/people-background.js +5 -0
  9. package/dist/src/people-dossier-review.d.ts +41 -0
  10. package/dist/src/people-dossier-review.js +47 -0
  11. package/dist/src/people-primer-config.d.ts +13 -0
  12. package/dist/src/people-primer-config.js +33 -0
  13. package/dist/src/people-primer-tool.d.ts +5 -0
  14. package/dist/src/people-primer-tool.js +78 -0
  15. package/dist/src/people-primer.d.ts +94 -0
  16. package/dist/src/people-primer.js +170 -0
  17. package/dist/src/people-store.d.ts +8 -1
  18. package/dist/src/people-store.js +60 -2
  19. package/dist/src/people-tools.d.ts +2 -1
  20. package/dist/src/people-tools.js +35 -11
  21. package/dist/src/plugin.js +5 -1
  22. package/dist/src/response-audit.d.ts +87 -0
  23. package/dist/src/response-audit.js +193 -0
  24. package/dist/src/response-config.d.ts +13 -0
  25. package/dist/src/response-config.js +43 -0
  26. package/dist/src/response-episodes.d.ts +68 -0
  27. package/dist/src/response-episodes.js +242 -0
  28. package/dist/src/response-identity.d.ts +15 -0
  29. package/dist/src/response-identity.js +34 -0
  30. package/dist/src/response-judge.d.ts +224 -0
  31. package/dist/src/response-judge.js +248 -0
  32. package/dist/src/response-memory.d.ts +8 -0
  33. package/dist/src/response-memory.js +25 -0
  34. package/dist/src/response-outcome.d.ts +30 -0
  35. package/dist/src/response-outcome.js +51 -0
  36. package/dist/src/response-reviews.d.ts +27 -0
  37. package/dist/src/response-reviews.js +116 -0
  38. package/dist/src/response-runtime.d.ts +3 -0
  39. package/dist/src/response-runtime.js +150 -0
  40. package/dist/src/response-stages.d.ts +184 -0
  41. package/dist/src/response-stages.js +38 -0
  42. package/dist/src/response-store.d.ts +180 -0
  43. package/dist/src/response-store.js +411 -0
  44. package/dist/src/response-text.d.ts +6 -0
  45. package/dist/src/response-text.js +37 -0
  46. package/dist/src/typesafe-review.d.ts +14 -1
  47. package/dist/src/typesafe-review.js +46 -8
  48. package/openclaw.plugin.json +37 -1
  49. package/package.json +1 -1
  50. package/skills/people-whisperer/SKILL.md +107 -100
package/README.md CHANGED
@@ -1,5 +1,203 @@
1
1
  # Unblock Memory
2
2
 
3
+ ## Response quality tracking (opt-in)
4
+
5
+ `responseAudit` evaluates bounded human-agent exchanges in the background. It is
6
+ separate from chunk-quality auditing and never changes memories or prompts. It
7
+ creates private response-review tasks, not memory-curation tasks.
8
+ Its primary purpose is tracking delivery quality over time: visible fulfillment,
9
+ deliverable fit, clear underdelivery and its observable reason. Memory gaps are only
10
+ an optional diagnostic lead, not a proxy for performance.
11
+ Only approved Slack sender IDs with trusted `senderKind: human` or owner metadata
12
+ qualify (older Slack records use unknown senderKind even for known owners).
13
+ Explicit bots, unverified identities, internal messages, other senders and thread changes form
14
+ hard boundaries. Synthetic delivery mirrors and gateway-injected answers are excluded.
15
+ Assistant progress messages are grouped with the terminal answer.
16
+ Recognized Slack envelopes are stripped even inside `upstreamUserText`; embedded
17
+ history is not treated as current human text. Ambiguous envelopes are excluded.
18
+ Removed history marks the context as limited; ordinary Markdown/JSON is preserved.
19
+ Human feedback closes when the next assistant turn starts. Still-open feedback,
20
+ no-response exchanges, incomplete/failed turns and oversized inputs are not graded.
21
+
22
+ ```json
23
+ {
24
+ "responseAudit": {
25
+ "enabled": true,
26
+ "sentimentEnabled": true,
27
+ "senderIds": ["YOUR_SLACK_USER_ID"],
28
+ "chatTypes": ["direct"],
29
+ "historyMessages": 6,
30
+ "lookbackDays": 30,
31
+ "maxEpisodes": 20,
32
+ "intervalMinutes": 60,
33
+ "memoryCorpora": ["memory", "knowledge"]
34
+ }
35
+ }
36
+ ```
37
+
38
+ This is explicit approval to send those exchanges to TypeSafe. Sender IDs apply
39
+ across the agent's Slack accounts; use only identities approved in all such accounts.
40
+ `memoryCorpora` is optional and separately approves configured **file** corpora for
41
+ memory-gap investigation. Leave it empty to send no indexed memory evidence.
42
+ `typesafe.enabled: false` or missing credentials prevents evaluation. An interval
43
+ of zero means manual-only. Defaults are disabled, no approved senders, direct chats,
44
+ 6 preceding visible messages, 30 days, 20 episodes per run and a 60-minute interval.
45
+ `sentimentEnabled` defaults to **true within that opt-in audit**; it does not bypass
46
+ approved senders or TypeSafe credentials. Set it false to omit polarity, annoyance,
47
+ frustration and intensity questions while retaining quality/repair judgments.
48
+ `intervalMinutes` controls their shared cadence; no second sentiment timer is needed.
49
+ The Gateway checks a durable per-agent due time on startup and every minute (no
50
+ agent-turn cron or separate launchd job). First enablement waits one interval;
51
+ restarts preserve the due time and an overdue schedule gets one bounded catch-up,
52
+ not one run per missed interval. Each attempt advances the due time before work,
53
+ including missing-key skips, failures or interrupted runs, to prevent retry storms.
54
+ Changing the interval recalculates the due time from the last scheduled attempt
55
+ (or initial enablement). The Gateway must be running; manual audits do not change
56
+ the automatic schedule. Missing/unreadable credentials skip all quality and sentiment
57
+ inference without failing Gateway startup or normal memory functionality.
58
+ Changing the interval does not invalidate cached results. Changing the sentiment
59
+ toggle selects a separate reporting cohort, so older missing sentiment is not
60
+ treated as neutral; unchanged quality/feedback stages are reused across the toggle.
61
+
62
+ Operator commands (not agent tools):
63
+
64
+ ```sh
65
+ openclaw memory-responses audit --agent main --dry-run
66
+ openclaw memory-responses audit --agent main
67
+ openclaw memory-responses report --agent main
68
+ openclaw memory-responses report --agent main --episode EPISODE_ID
69
+ openclaw memory-responses report --agent main --sender SLACK_USER_ID --account ACCOUNT_SCOPE --bucket day --since 2026-09-01 --until 2026-10-01
70
+ openclaw memory-responses report --agent main --person PERSON_ID
71
+ openclaw memory-responses tasks --agent main
72
+ openclaw memory-responses review --agent main --id TASK_ID --status deferred --reviewer human --note "Review the linked exchanges before changing preferences"
73
+ openclaw memory-responses annotate --agent main --date 2026-09-18 --kind prompt --note "Known prompt revision deployed"
74
+ openclaw memory-responses retry-failed --agent main
75
+ ```
76
+
77
+ Reports group by scoped human identity as well as task/model/time. Names are not
78
+ identity keys. Existing active people-store links are resolved read-only at assessment
79
+ time; missing links do not prevent analysis. Unknown account scopes stay isolated
80
+ per session. No new identity fields are sent to TypeSafe. Date ranges are UTC with
81
+ an inclusive start and exclusive end. `periodStart` identifies a day/week bucket;
82
+ legacy `week`/`fromWeek`/`toWeek` fields remain aliases. `--task-type` and `--model`
83
+ further narrow comparisons. Human-specific scores are not rankings of the humans:
84
+ task difficulty, feedback habits and selection bias remain important.
85
+
86
+ Session checkpoints hash bounded active source bytes; unchanged sessions skip
87
+ extraction and all inference. Changed sessions are re-extracted within the existing
88
+ budget, then stage hashes reuse unchanged quality, feedback, sentiment and later
89
+ evidence judgments. Only hashes/counts are checkpointed, never a transcript copy.
90
+ A persisted cursor rotates through discovery and tracked-session reconciliation;
91
+ `deferredByLimit` includes known backlog and a lower-bound marker for unvisited
92
+ sessions. `stages` exposes pending/failed/succeeded counts and exhausted retries
93
+ for the cohort/date range, before person filters. `retry-failed` only resets failed
94
+ work; successful stages remain cached. Source freshness is checked before activation.
95
+
96
+ Review tasks distinguish concrete delivery shortfalls from high-intensity human
97
+ experience complaints. Stable task keys include exchange, scoped human and issue
98
+ family. Decisions survive rescoring; stale source evidence and superseded findings
99
+ are labeled separately. Review status/provenance never changes the raw judgments.
100
+ Tasks and change annotations are operator-only and stay out of memory/whisperer
101
+ prompts. `--reviewer` records human/agent provenance, not authentication or a new
102
+ permission grant. Task lists disclose their 1,000-item cap. There are no automatic
103
+ dossier updates: review the evidence and approve any concrete preference separately.
104
+ Old cohorts remain stored; the first staged-cohort run does not silently import
105
+ unverified older rubric judgments. Audit-history retention is not automatic.
106
+
107
+ Two separate TypeSafe requests prevent human feedback from influencing the original
108
+ fulfillment/deliverable-fit grade. The feedback pass distinguishes acceptance,
109
+ correction, continuation, unrelated replies, expressed sentiment, repeated constraints
110
+ and avoidable rework. Current-index memory investigation runs only for a strong
111
+ memory-gap signal: lexical retrieval selects up to three whole short documents from
112
+ approved collections. This is an investigation lead, **not proof of historical
113
+ availability, factual truth, or agent fault**. Tool-call counts do not establish what
114
+ the model saw or whether it should have searched. Unseen artifacts are unassessable.
115
+
116
+ Deliverable kind/format/scope has its own assessability gate, independent of whether
117
+ execution or external facts can be verified. Feedback attribution distinguishes the
118
+ current answer, earlier behavior, delivery, missing proactive action, external events,
119
+ new work and mixed/unclear targets. A reported forgotten instruction does not prove
120
+ searchable memory existed. A third, separate request examines the original exchange,
121
+ human feedback and available next assistant block for specific reported shortfalls,
122
+ acknowledgment, explicit factual corrections, delivery failures and regressions. These are
123
+ retrospective signals, not independently verified facts and never inputs to the
124
+ original grade. Clean text preceding a synthetic error/delivery notice can be assessed
125
+ as **partial** evidence; the notice itself is excluded and no successful completion
126
+ is inferred. Later evidence is capped at six messages/12K characters; incomplete,
127
+ unsafe or oversized blocks stay explicitly pending/unavailable/oversized. New later
128
+ evidence changes the input hash; only changed assessment stages are re-evaluated,
129
+ within normal audit budgets. Successful stages survive failures in later stages.
130
+ When the next block is unavailable, the third pass uses only the original exchange
131
+ and feedback; it cannot infer a missing delivery from missing later evidence.
132
+
133
+ Code combines narrow, confident evidence into an **observed outcome**, preserving
134
+ its basis and reason. A concrete original-answer shortfall or later admission takes
135
+ precedence over praise. Broad reported failures are used only when they do not
136
+ depend on a newly introduced requirement. Accurate explanations of earlier mistakes,
137
+ ordinary follow-ups, necessary clarification and unseen work are not automatically
138
+ failures. Sentiment and earlier-workflow complaints remain separate review signals.
139
+ Sentiment includes independent annoyance and frustration yes-probabilities (both
140
+ can apply), plus an expressed-dissatisfaction intensity score from 0 to 3. Intensity
141
+ means no expressed displeasure / restrained displeasure / pointed complaint /
142
+ explicit rejection or loss of trust. It is **not confidence or failure severity**.
143
+ External frustration, brevity and factual corrections alone do not establish
144
+ annoyance or frustration; mixed praise and complaints can still carry both signals.
145
+ Weekly reports show dissatisfaction, annoyance and frustration rates, intensity
146
+ means, unknown counts and their own assessment denominators. Unassessed results
147
+ are never counted as neutral. Sentiment deltas require 20 samples in both periods
148
+ and matching assessment coverage; they remain descriptive, not causal evidence.
149
+ Outcome, evidence basis and failure reasons remain distinct: a correction does not
150
+ automatically mean `incorrect_claim`. Confident reason judgments and direct
151
+ delivery/regression admissions supply reasons; otherwise `reasonStatus` is
152
+ `uncertain`. `reasonDetails` retain each label's source and strength, distinguishing
153
+ Choice confidence from Noul yes-probability. Multiple supported reasons can coexist.
154
+ `reportVersion` identifies composition/reporting semantics independently of the
155
+ judge rubric, allowing cached judgments to be re-reported without re-inference.
156
+
157
+ Results live in the agent's private `unblock-memory/response-audit.sqlite`, outside
158
+ the memory index. It stores judgments and source event references/hashes, not copies
159
+ of conversations. Identical successful inputs are cached; source rewrites invalidate
160
+ in-scope results on the next scan. Reports partition by fixed judge/rubric/context
161
+ configuration, UTC week, task type and agent model. They expose eligible/assessed
162
+ counts, excluded cases, confidence-qualified score means with per-dimension denominators, rework rates with Wilson
163
+ intervals, and evidence IDs. Small groups (<20) are marked explicitly. Confidence
164
+ thresholds are provisional, not calibrated guarantees. Human-reviewed evaluation
165
+ data is still needed before drawing performance conclusions.
166
+ Reports include dated clear-underdelivery examples and reason counts. Descriptive
167
+ score deltas compare successive available UTC weeks within the same task type,
168
+ agent model and rubric/configuration, with at least 20 confident scores per dimension
169
+ in each period and unchanged scored coverage; changed coverage withholds the score
170
+ delta. Outcome trends show acknowledgment, reported-shortfall and unknown rates
171
+ against **all evaluated exchanges**, with at least 20 evaluated exchanges per period.
172
+ Read the three rates together: fewer acknowledgments can mean more unknowns, not
173
+ more failures. Every delta includes before/after values, sample counts, denominator
174
+ and coverage-change flags. Unknown task types/models cannot produce deltas. These
175
+ are not statistical change-point detections or proof of causality; model/version
176
+ changes remain visible as separate groups rather than silently mixing cohorts.
177
+ The legacy `observedSuccessRate` group field remains acknowledgment / known outcomes
178
+ for compatibility, but is not used for trends. Unknowns are never successes.
179
+ Coverage changes and threshold variability can move rates; acknowledgment is not
180
+ factual verification. Week buckets
181
+ may be partial, and several exchanges in one session are not independent. Wilson
182
+ intervals are descriptive, not calibrated confidence about overall agent ability.
183
+
184
+ Each run selects at most 100 recent sessions for inference, each at most 2,000 active events/2M
185
+ characters; episodes must fit 24K characters and six feedback messages without
186
+ truncating the answer. Coverage counts describe the scanned sessions; only episodes
187
+ within `lookbackDays` are judged. Caps, failures and no-feedback cases remain visible.
188
+ Saved sessions in the report window are also reconciled independently of that
189
+ selection, so removing an entire active branch retires its scores. Oversized saved
190
+ sessions defer reconciliation rather than being treated as deleted; the report
191
+ exposes `reconciledSessions` and `reconciliationDeferred`. All reconciliation shares
192
+ the run deadline. Freshness checks compare the assessed episode, not unrelated
193
+ later session activity. Actual snapshot races do not exhaust provider retries.
194
+ The whole run has a two-minute deadline, at most three provider attempts per input (ten-minute
195
+ backoff), and a cross-process lease. Scheduling never starts inference on the agent
196
+ turn path or boots a QMD manager. No model downloads or source re-indexing occur.
197
+ The report is observational: different task mixes, selective human replies and judge
198
+ changes can produce apparent trends. It does not automatically declare regressions,
199
+ rewrite prompts, or treat silence as success.
200
+
3
201
  ## Review and diagnostics
4
202
 
5
203
  - `memory_diagnostics` reports credential **availability only**, per-agent process-local
@@ -326,18 +524,105 @@ directory.
326
524
 
327
525
  ### People Whisperer
328
526
 
527
+ #### Optional People Dossier Primer
528
+
529
+ `memory_people_prime({ personId, agentName? })` prepares evidence for an existing person;
530
+ it does **not** generate claims, update dossiers, or inject context. With
531
+ `people.enabled: true`, opt in separately:
532
+
533
+ ```json
534
+ {
535
+ "peoplePrimer": {
536
+ "enabled": true,
537
+ "corpora": ["memory", "knowledge", "sessions"],
538
+ "hitsPerQuestion": 30,
539
+ "minScore": 0.35,
540
+ "minUsefulness": 0.8,
541
+ "maxEvidencePerQuestion": 3,
542
+ "timeoutMs": 30000
543
+ }
544
+ }
545
+ ```
546
+
547
+ List only configured, approved non-skill corpora. The feature is **off by
548
+ default** and requires shared TypeSafe credentials. Disabled TypeSafe or missing/
549
+ unreadable credentials safely skip the primer; agents can still research normally.
550
+ Enabling it approves sending the person's identity, retrieved excerpts and optional
551
+ draft snippet to TypeSafe. Existing dossiers are not sent as grading evidence.
552
+ Sessions includes all indexed conversations;
553
+ results are available to the agent's tool callers, so scope approval accordingly.
554
+
555
+ Three default questions cover explicit role/organization, enduring organizational
556
+ background, and the person's relationship to the agent (not its business mission).
557
+ Preferences, working styles, priorities, feedback and task history are excluded.
558
+ Each uses QMD vector search (no query expansion) for up to 30 hits, configurable
559
+ up to 40. All unique eligible hits above the vector threshold are graded, not just
560
+ the final top three. Complete excerpts over 6,000 characters are counted and skipped,
561
+ not silently truncated. Duplicate source spans across questions share a request;
562
+ Independent attribution, explicit-background, durability, recognition-value and
563
+ question-usefulness judgments run together; every dimension must pass the threshold.
564
+ Every candidate is graded against all three questions, regardless of which search
565
+ found it. Mixed excerpts may supply a useful background fact without making their
566
+ surrounding behavioral content eligible for the snippet.
567
+ Provider concurrency is four, with a two-minute overall tool deadline.
568
+
569
+ Supply the agent's human-facing name when no identity name is configured; otherwise
570
+ questions use "the assistant", never an internal routing ID such as `main`.
571
+ The output includes a deduplicated source-linked excerpt list referenced by each
572
+ question's evidence IDs, a bounded uncertain-review shortlist,
573
+ and retrieval/cache/failure counts. Coverage is `evidence_found`, `uncertain` or
574
+ `unknown`, not a claim that a question has been definitively answered. Partial
575
+ provider failures are explicit; absence of selected hits does not prove absence of
576
+ evidence. The agent must verify dates, speakers and contradictions before writing.
577
+ Memory evidence never grants permissions or establishes that an old request is
578
+ still open.
579
+
580
+ `memory_people_update({ action: "replace_dossier", personId, dossier, reason,
581
+ agentName? })` automatically checks the proposed blurb before saving. Exact
582
+ `qmd://path#Lstart-Lend` claim evidence locators supply up to three indexed ranges
583
+ from the primer's approved corpora (120 lines each, 6,000 characters total).
584
+ Support confidence and background-only/explicit-support probabilities must all
585
+ be >=0.9. `needs_review` or `review_unavailable` leaves the dossier and history
586
+ unchanged; missing keys and failures never count as approval. A concurrent dossier
587
+ edit/deletion returns `conflict` instead of overwriting the newer change.
588
+
589
+ After independently verifying every assertion and background eligibility, an agent
590
+ can supply a source-specific `manualVerification` explanation (up to 400 characters)
591
+ for direct human corrections, non-indexed evidence or disabled/unavailable/incorrect
592
+ reviews. This explicit path skips TypeSafe, records manual provenance in change
593
+ history and keeps all structural limits. It is not a provider pass. Normal success
594
+ returns `status: "ok"`, `saved: true` and `verification: "typesafe" | "manual"`.
595
+ The skill documents when to use each path. Sources outside approved corpora are
596
+ rejected before egress; no separate `evidenceReview` toggle is needed.
597
+
598
+ For optional read-only diagnostics, `memory_people_prime({ personId, agentName?,
599
+ draft: { blurb, citations: [{ path, from, lines }] } })` still reviews a snippet
600
+ without writing. Agents do not need this extra call in the normal update workflow.
601
+
602
+ Judgments are cached privately in `people.sqlite` (maximum 2,000 entries), keyed
603
+ by person, agent, exact evidence/context, questions,
604
+ and judge version. No source text or credentials are stored in the cache.
605
+ Retrieval reruns against the current index; unchanged judgments are reused.
606
+ This is on-demand preparation, not a new scheduler or incremental session scanner.
607
+ Use it from an existing People Whisperer maintenance cron. Refresh stale session
608
+ indexes with `memory_sync_sessions` before priming when needed.
609
+
610
+ #### People store and maintenance
611
+
329
612
  PeopleSQL is an optional agent-local people store. When `people.enabled` is
330
613
  true, incoming Slack messages with a canonical agent session key and exact
331
614
  account and sender IDs create or refresh an injection-enabled person record.
332
615
  Incomplete Slack identities create a bounded, deduplicated todo without storing
333
616
  message content. Other channels are ignored.
334
617
 
335
- PeopleSQL registers three tools when enabled:
618
+ PeopleSQL registers these tools when enabled:
336
619
 
337
620
  - `memory_people_inspect` lists active people, reads one exact person, reads one
338
621
  person's dossier change history, or lists bounded actionable todos;
339
622
  - `memory_people_update` replaces or deletes dossiers, toggles one person's
340
- injection, and manages company, todo, deletion, or restoration state; and
623
+ injection, and manages company, todo, deletion, or restoration state;
624
+ - `memory_people_prime` prepares evidence when the separately opted-in primer is
625
+ enabled, otherwise returns disabled; and
341
626
  - the optional `memory_people_sync` enriches one active OpenClaw Slack account;
342
627
  its tool input accepts an account ID, not a token.
343
628
 
@@ -353,10 +638,18 @@ person's current dossier, search ordinary memory and sessions with
353
638
  `memory_search`/`memory_get`, and replace the dossier when that would improve a
354
639
  future conversation. The plugin owns no dossier-maintenance workflow or refresh
355
640
  schedule. A dossier's `reviewedAt` value records its last successful write; it
356
- is not scheduling state. The plugin performs no model call for dossier
357
- maintenance or prompt injection.
358
-
359
- Every `replace_dossier` and `delete_dossier` action requires a concise `reason`.
641
+ is not scheduling state. Dossier generation belongs to the agent; prompt injection
642
+ performs no model call. The optional primer grades evidence and reviews draft snippets.
643
+ The goal is recognition, not a behavioral profile: one short paragraph of at most
644
+ 70 words identifying the person and their enduring organization/agent relationship.
645
+ New writes allow only `role`/`relationship` sections and explicit `observed`/`reported`
646
+ claims; priorities, preferences and inferred profiles belong outside dossiers.
647
+ Legacy dossiers remain readable, but must be deliberately rewritten by the agent
648
+ before replacement. No automatic destructive migration or blanket deletion occurs.
649
+
650
+ Every `replace_dossier` and `delete_dossier` action requires a concise `reason`
651
+ (up to 500 characters for replacements, 1,000 for deletions). Replacement history
652
+ also records whether TypeSafe checks passed or a manual attestation was used.
360
653
  The plugin transactionally records that reason with its authoritative before and
361
654
  after dossier snapshots. List small newest-first summaries with
362
655
  `memory_people_inspect({ view: "dossier_changes", personId, limit?, offset? })`,
@@ -379,20 +672,15 @@ The package includes a `$people-whisperer` skill with the canonical agent
379
672
  procedure and dossier shape. For a manual refresh, ask:
380
673
 
381
674
  ```text
382
- Use $people-whisperer to improve your understanding of this person. Search memory
383
- and recent sessions, inspect their current PeopleSQL dossier, and update it only
384
- if the result would make future conversations meaningfully better.
675
+ Use $people-whisperer to maintain this person's brief background snippet.
385
676
  ```
386
677
 
387
678
  For an optional cron or isolated agent session, use this goal:
388
679
 
389
680
  ```text
390
- Use $people-whisperer to improve your understanding of people you interact with.
391
-
392
- Search recent sessions and memory for meaningful information about people. Inspect
393
- their existing PeopleSQL dossiers when useful. Update a dossier only when doing so
394
- would make future conversations meaningfully better. Ignore routine conversation,
395
- repetition, and weak inference. You may update several people or nobody.
681
+ Use $people-whisperer to maintain brief background snippets for people you interact
682
+ with. Follow the packaged skill, including source verification and write results.
683
+ Update only when useful; several people or nobody is fine. Report changes and gaps.
396
684
  ```
397
685
 
398
686
  Choose any cadence appropriate for the agent; the plugin does not require or
@@ -1,3 +1,5 @@
1
+ import { type ResponseAuditConfig } from "./response-config.js";
2
+ import { type PeoplePrimerConfig } from "./people-primer-config.js";
1
3
  export type FileCorpusConfig = {
2
4
  name: string;
3
5
  kind: "files";
@@ -40,6 +42,8 @@ export type UnblockMemoryConfig = {
40
42
  enabled: boolean;
41
43
  corpora: readonly string[];
42
44
  };
45
+ responseAudit: ResponseAuditConfig;
46
+ peoplePrimer: PeoplePrimerConfig;
43
47
  people: {
44
48
  enabled: boolean;
45
49
  whisperer: {
@@ -1,4 +1,6 @@
1
1
  import { isAbsolute } from "node:path";
2
+ import { resolveResponseAudit } from "./response-config.js";
3
+ import { resolvePeoplePrimer } from "./people-primer-config.js";
2
4
  const DEFAULT_PATHS = ["MEMORY.md", "USER.md", "memory/**/*.md"];
3
5
  const DEFAULT_SESSION_MAX_EXPANDED_TOKENS = 500;
4
6
  const MAX_SESSION_MAX_EXPANDED_TOKENS = 10_000;
@@ -262,6 +264,8 @@ export function resolveConfig(value) {
262
264
  typesafe: { ...DEFAULT_TYPESAFE_CONFIG },
263
265
  qualityAudit: { ...DEFAULT_QUALITY_AUDIT },
264
266
  evidenceReview: { enabled: false, corpora: [] },
267
+ responseAudit: resolveResponseAudit(undefined, DEFAULT_CORPORA),
268
+ peoplePrimer: resolvePeoplePrimer(undefined, DEFAULT_CORPORA, false),
265
269
  people: DEFAULT_PEOPLE_CONFIG,
266
270
  skillWhisperer: DEFAULT_SKILL_WHISPERER,
267
271
  memoryWhisperer: { ...DEFAULT_MEMORY_WHISPERER },
@@ -271,9 +275,10 @@ export function resolveConfig(value) {
271
275
  throw new Error("unblock-memory config must be an object");
272
276
  }
273
277
  const config = value;
274
- assertOnlyKeys(config, ["corpora", "keepEmbeddingModelWarm", "analysis", "people", "skillWhisperer", "memoryWhisperer", "typesafe", "qualityAudit", "evidenceReview"], "config");
278
+ assertOnlyKeys(config, ["corpora", "keepEmbeddingModelWarm", "analysis", "people", "peoplePrimer", "skillWhisperer", "memoryWhisperer", "typesafe", "qualityAudit", "evidenceReview", "responseAudit"], "config");
275
279
  const corpora = resolveCorpora(config.corpora);
276
280
  const people = resolvePeople(config.people);
281
+ const peoplePrimer = resolvePeoplePrimer(config.peoplePrimer, corpora, people.enabled);
277
282
  let evidenceReview = { enabled: false, corpora: [] };
278
283
  if (config.evidenceReview !== undefined) {
279
284
  const value = config.evidenceReview;
@@ -344,8 +349,9 @@ export function resolveConfig(value) {
344
349
  if (skillWhisperer.enabled && !corpora.some((corpus) => corpus.kind === "skills")) {
345
350
  throw new Error('unblock-memory enabled skillWhisperer requires a corpus named "skills" with kind "skills"');
346
351
  }
347
- return { corpora, keepEmbeddingModelWarm, analysis: analysisConfig, people, skillWhisperer,
352
+ return { corpora, keepEmbeddingModelWarm, analysis: analysisConfig, people, peoplePrimer, skillWhisperer,
348
353
  qualityAudit: resolveQualityAudit(config.qualityAudit, corpora),
349
354
  evidenceReview,
355
+ responseAudit: resolveResponseAudit(config.responseAudit, corpora),
350
356
  memoryWhisperer: resolveMemoryWhisperer(config.memoryWhisperer, corpora), typesafe: resolveTypeSafe(config.typesafe) };
351
357
  }
@@ -13,6 +13,10 @@ export declare function reviewIndexedClaim(params: {
13
13
  apiKey: string;
14
14
  timeoutMs: number;
15
15
  signal: AbortSignal;
16
+ personBackground?: {
17
+ name: string;
18
+ agentName: string;
19
+ };
16
20
  read?: <T>(run: () => T) => Promise<T>;
17
21
  }): Promise<{
18
22
  status: "unavailable";
@@ -29,6 +33,11 @@ export declare function reviewIndexedClaim(params: {
29
33
  }[];
30
34
  policy: string;
31
35
  scope: string;
36
+ needsReview: boolean;
37
+ background?: {
38
+ backgroundOnly: number;
39
+ explicitSupport: number;
40
+ } | undefined;
32
41
  verdict: "supports" | "contradicts" | "insufficient_evidence";
33
42
  confidence: number;
34
43
  probabilities: {
@@ -36,6 +45,5 @@ export declare function reviewIndexedClaim(params: {
36
45
  contradicts: number;
37
46
  insufficient_evidence: number;
38
47
  };
39
- needsReview: boolean;
40
48
  status: "ok";
41
49
  }>;
@@ -45,6 +45,7 @@ export async function reviewIndexedClaim(params) {
45
45
  }
46
46
  return { status: "ok", ...judgment,
47
47
  evidence: evidence.map(({ text: _text, ...citation }) => citation),
48
- policy: "jev-1.13.0:claim-v1", scope: "Advisory support check against cited indexed excerpts only, not current truth or authorization to write. Verify original sources and identity before promotion." };
48
+ policy: params.personBackground ? "jev-1.13.0:person-background-v2" : "jev-1.13.0:claim-v1",
49
+ scope: "Advisory support check against cited indexed excerpts only, not current truth or authorization to write. Verify original sources and identity before promotion." };
49
50
  });
50
51
  }
@@ -85,6 +85,11 @@ export declare class QmdMemoryManager implements MemorySearchManagerContract {
85
85
  }[];
86
86
  policy: string;
87
87
  scope: string;
88
+ needsReview: boolean;
89
+ background?: {
90
+ backgroundOnly: number;
91
+ explicitSupport: number;
92
+ } | undefined;
88
93
  verdict: "supports" | "contradicts" | "insufficient_evidence";
89
94
  confidence: number;
90
95
  probabilities: {
@@ -92,7 +97,6 @@ export declare class QmdMemoryManager implements MemorySearchManagerContract {
92
97
  contradicts: number;
93
98
  insufficient_evidence: number;
94
99
  };
95
- needsReview: boolean;
96
100
  status: "ok";
97
101
  }>;
98
102
  reviewCluster(params: Omit<Parameters<typeof reviewClusterIngestion>[0], "db" | "sources" | "read"> & {
@@ -0,0 +1,3 @@
1
+ /** A recognition aid, never a behavioral profile. Existing records remain readable. */
2
+ export declare const PEOPLE_BACKGROUND_MAX_WORDS = 70;
3
+ export declare function backgroundWordCount(text: string): number;
@@ -0,0 +1,5 @@
1
+ /** A recognition aid, never a behavioral profile. Existing records remain readable. */
2
+ export const PEOPLE_BACKGROUND_MAX_WORDS = 70;
3
+ export function backgroundWordCount(text) {
4
+ return text.trim().split(/\s+/u).filter(Boolean).length;
5
+ }
@@ -0,0 +1,41 @@
1
+ import type { UnblockMemoryConfig } from "./config.js";
2
+ import type { Person, PersonDossier } from "./people-store.js";
3
+ import type { QmdMemoryRuntime } from "./runtime.js";
4
+ import type { getContext } from "./tool-context.js";
5
+ /** Review the injected blurb using only exact indexed references already on its claims. */
6
+ export declare function reviewPersonDossier(params: {
7
+ config: UnblockMemoryConfig;
8
+ runtime: QmdMemoryRuntime;
9
+ active: NonNullable<ReturnType<typeof getContext>>;
10
+ person: Person;
11
+ dossier: PersonDossier;
12
+ agentName?: string;
13
+ signal?: AbortSignal;
14
+ }): Promise<{
15
+ evidence: {
16
+ path: string;
17
+ from: number;
18
+ lines: number;
19
+ documentHash: string;
20
+ excerptHash: string;
21
+ }[];
22
+ policy: string;
23
+ scope: string;
24
+ needsReview: boolean;
25
+ background?: {
26
+ backgroundOnly: number;
27
+ explicitSupport: number;
28
+ } | undefined;
29
+ verdict: "supports" | "contradicts" | "insufficient_evidence";
30
+ confidence: number;
31
+ probabilities: {
32
+ supports: number;
33
+ contradicts: number;
34
+ insufficient_evidence: number;
35
+ };
36
+ status: "ok";
37
+ } | {
38
+ status: "unavailable";
39
+ needsReview: boolean;
40
+ reason: string;
41
+ }>;
@@ -0,0 +1,47 @@
1
+ import { resolveAgentIdentity } from "openclaw/plugin-sdk/agent-runtime";
2
+ import { abortable } from "./abortable.js";
3
+ import { resolveTypeSafeApiKey } from "./typesafe.js";
4
+ /** Review the injected blurb using only exact indexed references already on its claims. */
5
+ export async function reviewPersonDossier(params) {
6
+ const unavailable = (reason) => ({ status: "unavailable", needsReview: true, reason });
7
+ const { config, runtime, active, person, dossier } = params;
8
+ if (!config.peoplePrimer.enabled || !config.typesafe.enabled) {
9
+ return unavailable("TypeSafe background review is disabled; verify manually before saving");
10
+ }
11
+ const citations = new Map();
12
+ for (const section of dossier.sections)
13
+ for (const claim of section.claims)
14
+ for (const ref of claim.evidence) {
15
+ const match = /^(qmd:\/\/[^#]+)#L([1-9]\d*)(?:-L([1-9]\d*))?$/.exec(ref.locator);
16
+ if (!match)
17
+ return unavailable("Evidence needs exact qmd://path#Lstart-Lend references or explicit manual verification");
18
+ const from = Number(match[2]), end = Number(match[3] ?? match[2]);
19
+ if (!Number.isSafeInteger(from) || !Number.isSafeInteger(end) || end < from || end - from >= 120) {
20
+ return unavailable("Evidence ranges must contain 1–120 lines");
21
+ }
22
+ const citation = { path: match[1], from, lines: end - from + 1 };
23
+ citations.set(JSON.stringify(citation), citation);
24
+ }
25
+ if (!citations.size || citations.size > 3)
26
+ return unavailable("Choose 1–3 distinct indexed evidence ranges or verify manually");
27
+ const deadline = AbortSignal.timeout(120_000);
28
+ const signal = params.signal ? AbortSignal.any([params.signal, deadline]) : deadline;
29
+ try {
30
+ signal.throwIfAborted();
31
+ const apiKey = await abortable(resolveTypeSafeApiKey(config.typesafe), signal);
32
+ if (!apiKey)
33
+ return unavailable("TypeSafe API key not configured; verify manually before saving");
34
+ const { manager } = await abortable(runtime.getMemorySearchManager(active), signal);
35
+ if (!manager)
36
+ return unavailable("Memory index unavailable; verify manually before saving");
37
+ return await abortable(manager.reviewClaim({
38
+ claim: dossier.blurb, citations: [...citations.values()],
39
+ personBackground: { name: person.preferredName ?? person.displayName,
40
+ agentName: resolveAgentIdentity(active.cfg, active.agentId)?.name?.trim() || params.agentName?.trim() || "the assistant" },
41
+ corpora: config.peoplePrimer.corpora, apiKey, timeoutMs: config.peoplePrimer.timeoutMs, signal,
42
+ }), signal);
43
+ }
44
+ catch {
45
+ return unavailable("Background review failed or was cancelled; no dossier written");
46
+ }
47
+ }
@@ -0,0 +1,13 @@
1
+ import { Type, type Static } from "typebox";
2
+ import type { CorpusConfig } from "./config.js";
3
+ export declare const peoplePrimerSchema: Type.TObject<{
4
+ enabled: Type.TBoolean;
5
+ corpora: Type.TArray<Type.TString>;
6
+ hitsPerQuestion: Type.TInteger;
7
+ minScore: Type.TNumber;
8
+ minUsefulness: Type.TNumber;
9
+ maxEvidencePerQuestion: Type.TInteger;
10
+ timeoutMs: Type.TInteger;
11
+ }>;
12
+ export type PeoplePrimerConfig = Static<typeof peoplePrimerSchema>;
13
+ export declare function resolvePeoplePrimer(value: unknown, corpora: readonly CorpusConfig[], peopleEnabled: boolean): PeoplePrimerConfig;
@@ -0,0 +1,33 @@
1
+ import { Type } from "typebox";
2
+ import { Value } from "typebox/value";
3
+ export const peoplePrimerSchema = Type.Object({
4
+ enabled: Type.Boolean({ default: false }),
5
+ corpora: Type.Array(Type.String({ minLength: 1 }), { default: [] }),
6
+ hitsPerQuestion: Type.Integer({ minimum: 1, maximum: 40, default: 30 }),
7
+ minScore: Type.Number({ minimum: 0, maximum: 1, default: 0.35 }),
8
+ minUsefulness: Type.Number({ minimum: 0.5, maximum: 1, default: 0.8 }),
9
+ maxEvidencePerQuestion: Type.Integer({ minimum: 1, maximum: 10, default: 3 }),
10
+ timeoutMs: Type.Integer({ minimum: 1, maximum: 60000, default: 30000 }),
11
+ }, { additionalProperties: false });
12
+ export function resolvePeoplePrimer(value, corpora, peopleEnabled) {
13
+ if (value === undefined)
14
+ return { enabled: false, corpora: [], hitsPerQuestion: 30,
15
+ minScore: 0.35, minUsefulness: 0.8, maxEvidencePerQuestion: 3, timeoutMs: 30000 };
16
+ let config;
17
+ try {
18
+ const withDefaults = Value.Default(peoplePrimerSchema, value);
19
+ if (!Value.Check(peoplePrimerSchema, withDefaults))
20
+ throw new Error("Invalid config");
21
+ config = withDefaults;
22
+ }
23
+ catch {
24
+ throw new Error("Invalid peoplePrimer configuration");
25
+ }
26
+ if (config.corpora.some(name => !corpora.some(c => c.name === name && c.kind !== "skills"))) {
27
+ throw new Error("peoplePrimer.corpora must list configured non-skill corpora");
28
+ }
29
+ if (config.enabled && (!peopleEnabled || !config.corpora.length)) {
30
+ throw new Error("peoplePrimer requires people.enabled and explicit approved corpora");
31
+ }
32
+ return { ...config, corpora: [...new Set(config.corpora)] };
33
+ }
@@ -0,0 +1,5 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
2
+ import type { UnblockMemoryConfig } from "./config.js";
3
+ import type { PeopleStores } from "./people-store.js";
4
+ import type { QmdMemoryRuntime } from "./runtime.js";
5
+ export declare function registerPeoplePrimerTool(api: OpenClawPluginApi, runtime: QmdMemoryRuntime, stores: PeopleStores, config: UnblockMemoryConfig): void;