@shadowforge0/aquifer-memory 1.8.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/consumers/cli.js CHANGED
@@ -11,6 +11,8 @@
11
11
  * aquifer backfill [options] Enrich pending sessions
12
12
  * aquifer stats [options] Show database statistics
13
13
  * aquifer backend-info [--json] Show selected backend capabilities
14
+ * aquifer install-openclaw Install/update OpenClaw MCP + extension wiring
15
+ * aquifer finalization ... Inspect read-only finalization ledger
14
16
  * aquifer export [options] Export sessions
15
17
  * aquifer operator ... Run operator-safe consolidation jobs
16
18
  * aquifer mcp Start MCP server
@@ -21,6 +23,7 @@ const { createAquiferFromConfig } = require('./shared/factory');
21
23
  const { loadConfig } = require('./shared/config');
22
24
  const { formatRecallResults } = require('./shared/recall-format');
23
25
  const { backendCapabilities } = require('../core/backends/capabilities');
26
+ const { summarizeFinalizationListRow } = require('../core/finalization-inspector');
24
27
 
25
28
  function formatDate(value, fallback) {
26
29
  if (!value) return fallback;
@@ -183,6 +186,9 @@ function parseArgs(argv) {
183
186
  'policy-version', 'worker-id', 'apply-token', 'claim-lease-seconds', 'snapshot-as-of',
184
187
  'scope-id', 'scope-key', 'scope-keys', 'scope-kind', 'context-key', 'topic-key', 'authority', 'input',
185
188
  'synthesis-summary', 'synthesis-summary-file', 'min-finalizations', 'checkpoint-key',
189
+ 'openclaw-home', 'id', 'transcript-hash', 'phase', 'host', 'run-id', 'kind', 'query',
190
+ 'memory-type', 'visibility', 'as-of', 'resolution', 'reason', 'actor-kind', 'actor-id',
191
+ 'defer-until', 'expected-latest-issue-feedback-id', 'expected-latest-issue-feedback-at',
186
192
  ]);
187
193
  for (let i = 0; i < argv.length; i++) {
188
194
  if (argv[i] === '--') { args._.push(...argv.slice(i + 1)); break; }
@@ -412,6 +418,9 @@ async function cmdStats(aquifer, args) {
412
418
  console.log(`Serving mode: ${stats.serving?.mode || 'legacy'}`);
413
419
  console.log(`Active scope: ${stats.serving?.activeScopePath?.join(' > ') || stats.serving?.activeScopeKey || 'none'}`);
414
420
  console.log(`Sessions: ${stats.sessionTotal} (${Object.entries(stats.sessions).map(([k, v]) => `${k}: ${v}`).join(', ')})`);
421
+ if (stats.pendingSessions?.available) {
422
+ console.log(`Actionable pending/failed: ${stats.pendingSessions.total}`);
423
+ }
415
424
  console.log(`Summaries: ${stats.summaries}`);
416
425
  console.log(`Turn embeddings: ${stats.turnEmbeddings}`);
417
426
  console.log(`Entities: ${stats.entities}`);
@@ -473,6 +482,149 @@ async function cmdBackendInfo(args) {
473
482
  }
474
483
  }
475
484
 
485
+ function formatDoctor(result = {}) {
486
+ const checks = Array.isArray(result.checks) ? result.checks : [];
487
+ if (checks.length === 0) return `Doctor: ${result.status || 'unknown'}`;
488
+ return checks.map(check => {
489
+ const next = check.nextAction ? ` next=${check.nextAction}` : '';
490
+ return `${check.status || 'unknown'} ${check.id || 'check'}: ${check.summary || ''}${next}`;
491
+ }).join('\n');
492
+ }
493
+
494
+ async function cmdDoctor(aquifer, args) {
495
+ const result = await aquifer.doctor.run({
496
+ host: args.flags.host || undefined,
497
+ openclawHome: args.flags['openclaw-home'] || undefined,
498
+ limit: parsePositiveInt(args.flags.limit, 20),
499
+ });
500
+ if (args.flags.json) {
501
+ console.log(JSON.stringify(result, null, 2));
502
+ return;
503
+ }
504
+ console.log(formatDoctor(result));
505
+ }
506
+
507
+ function formatTimestamp(value) {
508
+ if (!value) return '?';
509
+ const parsed = new Date(value);
510
+ return isNaN(parsed.getTime()) ? String(value) : parsed.toISOString();
511
+ }
512
+
513
+ function shortHash(value) {
514
+ if (!value) return '?';
515
+ return String(value).slice(0, 12);
516
+ }
517
+
518
+ function formatFinalizationList(rows = []) {
519
+ if (!rows || rows.length === 0) return 'No finalization rows found.';
520
+ return rows.map(row => {
521
+ const item = summarizeFinalizationListRow(row);
522
+ const session = `${item.source}/${item.agentId}/${item.sessionId}`;
523
+ const phase = item.phase ? ` phase=${item.phase}` : '';
524
+ const mode = item.mode ? ` mode=${item.mode}` : '';
525
+ const hash = item.transcriptHashPrefix ? ` hash=${item.transcriptHashPrefix}` : '';
526
+ return `#${item.id} ${item.status} ${session}${phase}${mode}${hash} updated=${formatTimestamp(item.updatedAt)}`;
527
+ }).join('\n');
528
+ }
529
+
530
+ function formatFinalizationInspect(report = {}) {
531
+ const item = report.finalization || {};
532
+ const candidateSummary = report.candidateSummary || {};
533
+ const lineage = report.lineage || {};
534
+ const envelope = report.candidateEnvelope || {};
535
+ const memoryResult = report.memoryResult || {};
536
+ const lines = [
537
+ `Finalization #${item.id || '?'} ${item.status || 'unknown'}`,
538
+ `Session: ${item.source || '?'}/${item.agentId || '?'}/${item.sessionId || '?'} phase=${item.phase || '?'} mode=${item.mode || '?'} host=${item.host || '?'}`,
539
+ `Transcript hash: ${item.transcriptHashPrefix || shortHash(item.transcriptHash)}`,
540
+ `Updated: ${formatTimestamp(item.updatedAt)} finalized=${formatTimestamp(item.finalizedAt)}`,
541
+ ];
542
+
543
+ if (item.finalizerModel) lines.push(`Finalizer: ${item.finalizerModel}`);
544
+ if (item.scopeKind || item.scopeKey || item.contextKey || item.topicKey) {
545
+ lines.push(`Scope: kind=${item.scopeKind || '?'} key=${item.scopeKey || '?'} context=${item.contextKey || '?'} topic=${item.topicKey || '?'}`);
546
+ }
547
+ if (item.error) lines.push(`Error: ${item.error}`);
548
+
549
+ lines.push(`Memory result: candidates=${memoryResult.candidates || 0} promoted=${memoryResult.promoted || 0} quarantined=${memoryResult.quarantined || 0} skipped=${memoryResult.skipped || 0}`);
550
+ lines.push(`Candidate envelope: version=${envelope.version || '?'} hash=${shortHash(envelope.hash)} candidates=${envelope.candidateCount || 0}`);
551
+ lines.push(`Candidate actions: total=${candidateSummary.total || 0} promote=${candidateSummary.promote || 0} quarantine=${candidateSummary.quarantine || 0} skip=${candidateSummary.skip || 0} supersede=${candidateSummary.supersede || 0} error=${candidateSummary.error || 0}`);
552
+ lines.push(`Lineage: memories=${(lineage.memoryRecordIds || []).join(',') || 'none'} facts=${(lineage.factAssertionIds || []).join(',') || 'none'} evidenceRefs=${lineage.evidenceRefCount || 0} evidenceItems=${lineage.evidenceItemCount || 0}`);
553
+
554
+ if (report.review?.humanReviewText) {
555
+ lines.push('\nHuman review:\n' + report.review.humanReviewText);
556
+ }
557
+ if (report.review?.sessionStartText) {
558
+ lines.push('\nSessionStart:\n' + report.review.sessionStartText);
559
+ }
560
+ if (Array.isArray(report.candidates) && report.candidates.length > 0) {
561
+ lines.push('\nCandidates:');
562
+ for (const candidate of report.candidates) {
563
+ lines.push(` [${candidate.index}] ${candidate.action}${candidate.reason ? `/${candidate.reason}` : ''} ${candidate.memoryType || '?'} ${candidate.canonicalKey || '?'}${candidate.memoryRecordId ? ` memory=${candidate.memoryRecordId}` : ''}`);
564
+ }
565
+ }
566
+ return lines.join('\n');
567
+ }
568
+
569
+ async function cmdFinalization(aquifer, args) {
570
+ const verb = args._[1] || 'list';
571
+
572
+ if (verb === 'list') {
573
+ const rows = await aquifer.finalization.list({
574
+ status: args.flags.status || undefined,
575
+ agentId: args.flags['agent-id'] || undefined,
576
+ source: args.flags.source || undefined,
577
+ host: args.flags.host || undefined,
578
+ sessionId: args.flags['session-id'] || undefined,
579
+ transcriptHash: args.flags['transcript-hash'] || undefined,
580
+ phase: args.flags.phase || undefined,
581
+ mode: args.flags.mode || undefined,
582
+ limit: parsePositiveInt(args.flags.limit, 50),
583
+ });
584
+
585
+ if (args.flags.json) {
586
+ console.log(JSON.stringify({
587
+ readOnly: true,
588
+ finalizations: rows.map(summarizeFinalizationListRow),
589
+ }, null, 2));
590
+ return;
591
+ }
592
+ console.log(formatFinalizationList(rows));
593
+ return;
594
+ }
595
+
596
+ if (verb === 'inspect') {
597
+ const id = args.flags.id || args._[2] || undefined;
598
+ const sessionId = args.flags['session-id'];
599
+ if (id && (sessionId || args.flags['agent-id'] || args.flags.source || args.flags['transcript-hash'])) {
600
+ console.error('Usage: --id is mutually exclusive with --session-id/--agent-id/--source/--transcript-hash.');
601
+ process.exit(1);
602
+ }
603
+ if (!id && (!sessionId || !args.flags['agent-id'] || !args.flags.source)) {
604
+ console.error('Usage: aquifer finalization inspect (--id ID | --session-id ID --agent-id ID --source SOURCE [--transcript-hash HASH]) [--json]');
605
+ process.exit(1);
606
+ }
607
+ const report = await aquifer.finalization.inspect({
608
+ id,
609
+ sessionId: sessionId || undefined,
610
+ agentId: args.flags['agent-id'] || undefined,
611
+ source: args.flags.source || undefined,
612
+ transcriptHash: args.flags['transcript-hash'] || undefined,
613
+ phase: args.flags.phase || undefined,
614
+ });
615
+
616
+ if (args.flags.json) {
617
+ console.log(JSON.stringify(report, null, 2));
618
+ return;
619
+ }
620
+ console.log(formatFinalizationInspect(report));
621
+ return;
622
+ }
623
+
624
+ console.error('Usage: aquifer finalization <list|inspect> [options]');
625
+ process.exit(1);
626
+ }
627
+
476
628
  async function cmdLocalQuickstart(aquifer) {
477
629
  const cfg = aquifer.getConfig();
478
630
  console.log('Aquifer quickstart — verifying local starter backend.\n');
@@ -644,10 +796,319 @@ async function cmdBootstrap(aquifer, args) {
644
796
  }
645
797
  }
646
798
 
799
+ function formatExplain(result = {}) {
800
+ const selected = Array.isArray(result.selected) ? result.selected : [];
801
+ const excluded = Array.isArray(result.excluded) ? result.excluded : [];
802
+ const lines = [
803
+ `Explain ${result.lane || 'memory'}: selected=${selected.length} excluded=${excluded.length}`,
804
+ ];
805
+ if (selected.length > 0) {
806
+ lines.push('Selected:');
807
+ for (const row of selected) {
808
+ lines.push(` ${row.memoryId || row.id || '?'} ${row.canonicalKey || '?'} reason=${row.reason || 'selected'}`);
809
+ }
810
+ }
811
+ if (excluded.length > 0) {
812
+ lines.push('Excluded:');
813
+ for (const row of excluded) {
814
+ lines.push(` ${row.memoryId || row.id || '?'} ${row.canonicalKey || '?'} reason=${row.reason || 'unknown'}`);
815
+ }
816
+ }
817
+ if (result.budget) {
818
+ lines.push(`Budget: limit=${result.budget.limit || '?'} maxChars=${result.budget.maxChars || '?'} overflow=${result.budget.overflow === true}`);
819
+ }
820
+ return lines.join('\n');
821
+ }
822
+
823
+ function formatFeedbackCounts(counts = {}) {
824
+ const entries = Object.entries(counts).filter(([, count]) => count > 0);
825
+ if (entries.length === 0) return 'none';
826
+ return entries.map(([type, count]) => `${type}=${count}`).join(' ');
827
+ }
828
+
829
+ function formatReviewResolution(resolution = null) {
830
+ if (!resolution || !resolution.resolution) return 'none';
831
+ const at = resolution.resolvedAt ? ` at=${formatTimestamp(resolution.resolvedAt)}` : '';
832
+ const defer = resolution.deferUntil ? ` deferUntil=${formatTimestamp(resolution.deferUntil)}` : '';
833
+ return `${resolution.resolution}${at}${defer}`;
834
+ }
835
+
836
+ function formatReviewQueue(result = {}) {
837
+ if (result.available === false) {
838
+ return result.error || 'Memory review queue is unavailable.';
839
+ }
840
+ const rows = Array.isArray(result.items) ? result.items : (Array.isArray(result.reviewQueue) ? result.reviewQueue : []);
841
+ if (rows.length === 0) return 'No memory review items found.';
842
+ return rows.map(row => {
843
+ const scope = row.scope?.key ? ` scope=${row.scope.key}` : '';
844
+ const latest = row.latestFeedbackAt ? ` latest=${formatTimestamp(row.latestFeedbackAt)}` : '';
845
+ const resolution = row.resolution ? ` resolution=${formatReviewResolution(row.resolution)}` : '';
846
+ const counts = formatFeedbackCounts(row.feedbackCounts);
847
+ return `#${row.memoryId} severity=${row.severityScore} issues=${row.issueCount}/${row.feedbackCount} ${row.status || '?'}${scope} ${row.canonicalKey || '?'} feedback=${counts}${latest}${resolution}`;
848
+ }).join('\n');
849
+ }
850
+
851
+ function formatReviewInspect(result = {}) {
852
+ const memory = result.memory || {};
853
+ const review = result.review || {};
854
+ const visibility = memory.visibility || {};
855
+ const lines = [
856
+ `Memory #${memory.memoryId || '?'} ${memory.status || '?'} ${memory.canonicalKey || '?'}`,
857
+ `Scope: ${memory.scope?.kind || '?'}/${memory.scope?.key || '?'}`,
858
+ `Visibility: bootstrap=${visibility.bootstrap === true} recall=${visibility.recall === true}`,
859
+ `Feedback: issues=${review.issueCount || 0}/${review.feedbackCount || 0} ${formatFeedbackCounts(review.feedbackCounts)}`,
860
+ `Latest resolution: ${formatReviewResolution(review.latestResolution)}`,
861
+ ];
862
+ const events = Array.isArray(review.recentFeedback) ? review.recentFeedback : [];
863
+ if (events.length > 0) {
864
+ lines.push('Recent feedback:');
865
+ for (const event of events) {
866
+ const actor = event.actorId ? `${event.actorKind || 'actor'}:${event.actorId}` : (event.actorKind || 'actor');
867
+ lines.push(` #${event.id} ${event.feedbackType} ${actor} at=${formatTimestamp(event.createdAt)}`);
868
+ }
869
+ }
870
+ return lines.join('\n');
871
+ }
872
+
873
+ function formatReviewResolve(result = {}) {
874
+ const resolution = result.resolution || {};
875
+ const memory = result.memory || {};
876
+ const issueCount = result.review?.issueFeedbackCount || 0;
877
+ return [
878
+ `Review resolution recorded: ${resolution.resolution || '?'} for memory #${memory.memoryId || resolution.memoryId || '?'}`,
879
+ `Canonical key: ${memory.canonicalKey || resolution.canonicalKey || '?'}`,
880
+ `Issue feedback covered: ${issueCount} ${formatFeedbackCounts(result.review?.issueFeedbackCounts)}`,
881
+ `Memory mutated: ${result.memoryMutated === true}`,
882
+ ].join('\n');
883
+ }
884
+
885
+ async function cmdReview(aquifer, args) {
886
+ const verb = args._[1] || 'queue';
887
+ if (verb === 'queue') {
888
+ const activeScopePath = parseScopePath(args.flags['active-scope-path']);
889
+ const activeScopeKey = args.flags['active-scope-key']
890
+ || args.flags['scope-key']
891
+ || (activeScopePath ? activeScopePath[activeScopePath.length - 1] : undefined);
892
+ const result = await aquifer.review.queue({
893
+ feedbackType: args.flags['feedback-type'] || undefined,
894
+ memoryType: args.flags['memory-type'] || undefined,
895
+ visibility: args.flags.visibility || undefined,
896
+ activeScopeKey,
897
+ activeScopePath,
898
+ scopeKey: args.flags['scope-key'] || activeScopeKey,
899
+ canonicalKey: args.flags['canonical-key'] || undefined,
900
+ dateFrom: args.flags['date-from'] || undefined,
901
+ dateTo: args.flags['date-to'] || undefined,
902
+ asOf: args.flags['as-of'] || args.flags['snapshot-as-of'] || undefined,
903
+ includeResolved: args.flags['include-resolved'] === true,
904
+ limit: parsePositiveInt(args.flags.limit, 20),
905
+ });
906
+ if (args.flags.json) {
907
+ console.log(JSON.stringify(result, null, 2));
908
+ return;
909
+ }
910
+ console.log(formatReviewQueue(result));
911
+ return;
912
+ }
913
+
914
+ if (verb === 'inspect') {
915
+ const activeScopePath = parseScopePath(args.flags['active-scope-path']);
916
+ const activeScopeKey = args.flags['active-scope-key']
917
+ || args.flags['scope-key']
918
+ || (activeScopePath ? activeScopePath[activeScopePath.length - 1] : undefined);
919
+ const memoryId = args.flags['memory-id'] || args.flags.id || args._[2] || undefined;
920
+ const canonicalKey = args.flags['canonical-key'] || undefined;
921
+ if (memoryId && canonicalKey) {
922
+ console.error('Usage: --memory-id/--id is mutually exclusive with --canonical-key.');
923
+ process.exit(1);
924
+ }
925
+ if (!memoryId && !canonicalKey) {
926
+ console.error('Usage: aquifer review inspect (--memory-id ID | --canonical-key KEY) [--json]');
927
+ process.exit(1);
928
+ }
929
+ const result = await aquifer.review.inspect({
930
+ memoryId,
931
+ canonicalKey,
932
+ activeScopeKey,
933
+ activeScopePath,
934
+ scopeKey: args.flags['scope-key'] || activeScopeKey,
935
+ visibility: args.flags.visibility || undefined,
936
+ feedbackType: args.flags['feedback-type'] || undefined,
937
+ dateFrom: args.flags['date-from'] || undefined,
938
+ dateTo: args.flags['date-to'] || undefined,
939
+ asOf: args.flags['as-of'] || args.flags['snapshot-as-of'] || undefined,
940
+ limit: parsePositiveInt(args.flags.limit, 20),
941
+ });
942
+ if (args.flags.json) {
943
+ console.log(JSON.stringify(result, null, 2));
944
+ return;
945
+ }
946
+ console.log(formatReviewInspect(result));
947
+ return;
948
+ }
949
+
950
+ if (verb === 'resolve') {
951
+ const activeScopePath = parseScopePath(args.flags['active-scope-path']);
952
+ const activeScopeKey = args.flags['active-scope-key']
953
+ || args.flags['scope-key']
954
+ || (activeScopePath ? activeScopePath[activeScopePath.length - 1] : undefined);
955
+ const memoryId = args.flags['memory-id'] || args.flags.id || undefined;
956
+ const canonicalKey = args.flags['canonical-key'] || undefined;
957
+ if (memoryId && canonicalKey) {
958
+ console.error('Usage: --memory-id/--id is mutually exclusive with --canonical-key.');
959
+ process.exit(1);
960
+ }
961
+ if (!memoryId && !canonicalKey) {
962
+ console.error('Usage: aquifer review resolve (--memory-id ID | --canonical-key KEY) --resolution resolved|ignored|deferred [--json]');
963
+ process.exit(1);
964
+ }
965
+ if (!args.flags['expected-latest-issue-feedback-id'] && !args.flags['expected-latest-issue-feedback-at']) {
966
+ console.error('Usage: aquifer review resolve requires --expected-latest-issue-feedback-id or --expected-latest-issue-feedback-at.');
967
+ process.exit(1);
968
+ }
969
+ const result = await aquifer.review.resolve({
970
+ memoryId,
971
+ canonicalKey,
972
+ resolution: args.flags.resolution || undefined,
973
+ reason: args.flags.reason || args.flags.note || undefined,
974
+ actorKind: args.flags['actor-kind'] || undefined,
975
+ actorId: args.flags['actor-id'] || args.flags['agent-id'] || undefined,
976
+ deferUntil: args.flags['defer-until'] || undefined,
977
+ expectedLatestIssueFeedbackId: args.flags['expected-latest-issue-feedback-id'] || undefined,
978
+ expectedLatestIssueFeedbackAt: args.flags['expected-latest-issue-feedback-at'] || undefined,
979
+ activeScopeKey,
980
+ activeScopePath,
981
+ scopeKey: args.flags['scope-key'] || activeScopeKey,
982
+ visibility: args.flags.visibility || undefined,
983
+ asOf: args.flags['as-of'] || args.flags['snapshot-as-of'] || undefined,
984
+ });
985
+ if (args.flags.json) {
986
+ console.log(JSON.stringify(result, null, 2));
987
+ return;
988
+ }
989
+ console.log(formatReviewResolve(result));
990
+ return;
991
+ }
992
+
993
+ console.error('Usage: aquifer review <queue|inspect|resolve> [options]');
994
+ process.exit(1);
995
+ }
996
+
997
+ async function cmdExplain(aquifer, args) {
998
+ const lane = args._[1] || 'bootstrap';
999
+ if (lane === 'bootstrap') {
1000
+ const result = await aquifer.memory.explainBootstrap({
1001
+ activeScopeKey: args.flags['active-scope-key'] || undefined,
1002
+ activeScopePath: parseScopePath(args.flags['active-scope-path']),
1003
+ limit: parsePositiveInt(args.flags.limit, 5),
1004
+ maxChars: parsePositiveInt(args.flags['max-chars'], 4000),
1005
+ });
1006
+ if (args.flags.json) {
1007
+ console.log(JSON.stringify(result, null, 2));
1008
+ return;
1009
+ }
1010
+ console.log(formatExplain(result));
1011
+ return;
1012
+ }
1013
+ if (lane === 'memory') {
1014
+ const query = args.flags.query || args._.slice(2).join(' ');
1015
+ if (!query) {
1016
+ console.error('Usage: aquifer explain memory --query TEXT --active-scope-key KEY [--limit N] [--json]');
1017
+ process.exit(1);
1018
+ }
1019
+ const result = await aquifer.memory.explainCurrent(query, {
1020
+ activeScopeKey: args.flags['active-scope-key'] || undefined,
1021
+ activeScopePath: parseScopePath(args.flags['active-scope-path']),
1022
+ limit: parsePositiveInt(args.flags.limit, 5),
1023
+ });
1024
+ if (args.flags.json) {
1025
+ console.log(JSON.stringify(result, null, 2));
1026
+ return;
1027
+ }
1028
+ console.log(formatExplain(result));
1029
+ return;
1030
+ }
1031
+ console.error('Usage: aquifer explain <bootstrap|memory> [options]');
1032
+ process.exit(1);
1033
+ }
1034
+
1035
+ function formatOperatorStatus(result = {}) {
1036
+ const compaction = result.compaction || {};
1037
+ const checkpoint = result.checkpoint || {};
1038
+ const lines = [
1039
+ `Compaction: ${compaction.available === false ? 'unavailable' : 'available'} statuses=${JSON.stringify(compaction.statusCounts || {})}`,
1040
+ `Checkpoint: ${checkpoint.available === false ? 'unavailable' : 'available'} statuses=${JSON.stringify(checkpoint.statusCounts || {})}`,
1041
+ ];
1042
+ if (Array.isArray(compaction.staleClaims) && compaction.staleClaims.length > 0) {
1043
+ lines.push(`Stale compaction claims: ${compaction.staleClaims.map(run => `#${run.id}/${run.cadence}`).join(', ')}`);
1044
+ }
1045
+ if (Array.isArray(compaction.latest) && compaction.latest.length > 0) {
1046
+ lines.push('Latest compaction: ' + compaction.latest.map(run => `#${run.id} ${run.status} ${run.cadence}`).join(', '));
1047
+ }
1048
+ if (Array.isArray(checkpoint.latest) && checkpoint.latest.length > 0) {
1049
+ lines.push('Latest checkpoint: ' + checkpoint.latest.map(run => `#${run.id} ${run.status} sources=${run.sourceCount}`).join(', '));
1050
+ }
1051
+ return lines.join('\n');
1052
+ }
1053
+
1054
+ function formatOperatorInspect(result = {}) {
1055
+ const run = result.run || {};
1056
+ const lines = [
1057
+ `${run.kind || 'operator'} run #${run.id || '?'} status=${run.status || '?'}`,
1058
+ ];
1059
+ if (run.kind === 'compaction') {
1060
+ lines.push(`Window: ${run.cadence || '?'} ${run.periodStart || '?'} -> ${run.periodEnd || '?'}`);
1061
+ lines.push(`Plan: candidates=${run.candidateCount || 0} statusUpdates=${run.statusUpdateCount || 0}`);
1062
+ if (run.leaseExpiresAt) lines.push(`Claim: worker=${run.workerId || '?'} leaseExpiresAt=${run.leaseExpiresAt}`);
1063
+ } else if (run.kind === 'checkpoint') {
1064
+ lines.push(`Checkpoint: scope=${run.scopeId || '?'} key=${run.checkpointKey || '?'}`);
1065
+ lines.push(`Range: ${run.fromFinalizationIdExclusive || 0} -> ${run.toFinalizationIdInclusive || '?'}`);
1066
+ lines.push(`Sources: ${run.sourceCount || 0}`);
1067
+ }
1068
+ if (run.error) lines.push(`Error: ${run.error}`);
1069
+ if (Array.isArray(result.sources) && result.sources.length > 0) {
1070
+ lines.push('Sources:');
1071
+ for (const source of result.sources) {
1072
+ lines.push(` [${source.sourceIndex}] finalization=${source.finalizationId} ${source.source || '?'}/${source.agentId || '?'}/${source.sessionId || '?'}`);
1073
+ }
1074
+ }
1075
+ return lines.join('\n');
1076
+ }
1077
+
647
1078
  async function cmdOperator(aquifer, args) {
648
1079
  const operatorVerb = args._[1] || 'compaction';
649
1080
  const cadenceVerbs = new Set(['manual', 'daily', 'weekly', 'monthly']);
650
1081
 
1082
+ if (operatorVerb === 'status') {
1083
+ const result = await aquifer.operator.status({
1084
+ limit: parsePositiveInt(args.flags.limit, 10),
1085
+ });
1086
+ if (args.flags.json) {
1087
+ console.log(JSON.stringify(result, null, 2));
1088
+ return;
1089
+ }
1090
+ console.log(formatOperatorStatus(result));
1091
+ return;
1092
+ }
1093
+
1094
+ if (operatorVerb === 'inspect') {
1095
+ const runId = args.flags['run-id'] || args.flags.id || args._[2];
1096
+ if (!runId) {
1097
+ console.error('Usage: aquifer operator inspect --run-id ID [--kind compaction|checkpoint] [--json]');
1098
+ process.exit(1);
1099
+ }
1100
+ const result = await aquifer.operator.inspect({
1101
+ runId,
1102
+ kind: args.flags.kind || 'compaction',
1103
+ });
1104
+ if (args.flags.json) {
1105
+ console.log(JSON.stringify(result, null, 2));
1106
+ return;
1107
+ }
1108
+ console.log(formatOperatorInspect(result));
1109
+ return;
1110
+ }
1111
+
651
1112
  if (operatorVerb === 'checkpoint') {
652
1113
  const synthesisSummary = readSynthesisSummaryFromFlags(args.flags);
653
1114
  const result = await aquifer.checkpoints.runProducer({
@@ -830,13 +1291,18 @@ Commands:
830
1291
  quickstart Verify end-to-end setup (migrate → commit → enrich → recall)
831
1292
  migrate Run database migrations
832
1293
  backend-info Show selected backend capabilities without connecting to a database
1294
+ install-openclaw Install/update OpenClaw MCP + extension wiring
1295
+ doctor Run read-only health diagnostics
833
1296
  recall <query> Search sessions (requires embed config)
834
1297
  evidence-recall <query> Search legacy session/evidence plane explicitly
835
1298
  feedback Record trust feedback on a session
836
1299
  memory-feedback Record curated memory feedback
837
1300
  feedback-stats Show trust feedback statistics and coverage
1301
+ review ... Inspect or resolve memory feedback review queue
838
1302
  backfill Enrich pending sessions
839
1303
  operator ... Run operator-safe consolidation jobs
1304
+ finalization ... Inspect read-only finalization ledger
1305
+ explain ... Explain current-memory serving decisions
840
1306
  compact Plan or apply curated memory compaction
841
1307
  stats Show database statistics
842
1308
  export Export sessions as JSONL
@@ -853,11 +1319,32 @@ Options:
853
1319
  --date-to YYYY-MM-DD End date
854
1320
  --entities A,B,C Entity names (comma-separated, recall)
855
1321
  --entity-mode any|all Entity match mode (recall, default: any)
1322
+ --id ID Finalization row id
1323
+ --host HOST Finalization host filter
1324
+ --status STATUS Finalization status filter
1325
+ --phase PHASE Finalization phase filter
1326
+ --transcript-hash HASH Finalization transcript hash filter
1327
+ --run-id ID Operator run id
1328
+ --kind KIND Operator inspect kind: compaction|checkpoint
1329
+ --query TEXT Explain memory query
856
1330
  --session-id ID Session ID (feedback)
857
- --memory-id ID Curated memory record ID (memory-feedback)
858
- --canonical-key KEY Active curated memory canonical key (memory-feedback)
1331
+ --memory-id ID Curated memory record ID (memory-feedback, review inspect/resolve)
1332
+ --canonical-key KEY Active curated memory canonical key (memory-feedback, review queue/inspect/resolve)
859
1333
  --verdict helpful|unhelpful Feedback verdict (feedback)
860
1334
  --feedback-type TYPE Curated memory feedback type
1335
+ --memory-type TYPE Curated memory type filter (review queue)
1336
+ --visibility MODE Review visibility filter: either|both|bootstrap|recall
1337
+ --as-of ISO Review queue memory validity instant
1338
+ --include-resolved Include currently resolved review items in review queue
1339
+ --resolution ACTION Review resolution: resolved|ignored|deferred
1340
+ --reason TEXT Operator reason for review resolution
1341
+ --actor-kind KIND Review resolution actor kind: user|agent|system|curator
1342
+ --actor-id ID Review resolution actor id
1343
+ --defer-until ISO Re-open deferred review item after this instant
1344
+ --expected-latest-issue-feedback-id ID
1345
+ Reject review resolution if newer issue feedback exists
1346
+ --expected-latest-issue-feedback-at ISO
1347
+ Timestamp guard for review resolution snapshots
861
1348
  --note TEXT Feedback note (feedback)
862
1349
  --explain Show score breakdown per result (recall)
863
1350
  --allow-unsafe-debug Allow broad evidence-recall without audit boundary
@@ -865,6 +1352,11 @@ Options:
865
1352
  --dry-run Preview only (backfill)
866
1353
  --output PATH Output file (export)
867
1354
  --config PATH Config file path
1355
+ --openclaw-home PATH OpenClaw home path (install-openclaw)
1356
+ --skip-extension Do not wire the OpenClaw extension/plugin (install-openclaw)
1357
+ --skip-mcp Do not update mcp.servers.aquifer (install-openclaw)
1358
+ --force Move aside existing extension path if needed (install-openclaw)
1359
+ --link-current-package Development only: wire the current checkout instead of OpenClaw node_modules
868
1360
  --lookback-days N How far back in days (bootstrap, default: 14)
869
1361
  --max-chars N Max output characters (bootstrap, default: 4000)
870
1362
  --active-scope-key KEY Active curated memory scope key
@@ -890,6 +1382,17 @@ Options:
890
1382
  --min-messages N Min user messages to ingest (ingest-opencode, default: 3)
891
1383
 
892
1384
  Operator examples:
1385
+ aquifer doctor --json
1386
+ aquifer finalization list --status finalized --json
1387
+ aquifer finalization inspect --id 42 --json
1388
+ aquifer finalization inspect --session-id SESSION --agent-id main --source codex --transcript-hash HASH
1389
+ aquifer explain bootstrap --active-scope-key project:aquifer --json
1390
+ aquifer explain memory --query "serving contract" --active-scope-key project:aquifer --json
1391
+ aquifer review queue --scope-key project:aquifer --feedback-type incorrect --json
1392
+ aquifer review inspect --memory-id 42 --json
1393
+ aquifer review resolve --memory-id 42 --resolution resolved --reason "verified current" --json
1394
+ aquifer operator status --json
1395
+ aquifer operator inspect --run-id 42 --kind compaction --json
893
1396
  aquifer operator compaction daily --json
894
1397
  aquifer operator compaction daily --include-synthesis-prompt --json
895
1398
  aquifer operator compaction manual --period-start 2026-04-27T00:00:00Z --period-end 2026-04-28T00:00:00Z --apply
@@ -898,7 +1401,9 @@ Operator examples:
898
1401
  aquifer operator checkpoint --scope-id 7 --synthesis-summary-file /tmp/checkpoint-summary.json --apply --finalize --json
899
1402
  AQUIFER_BACKEND=local aquifer backend-info --json
900
1403
  aquifer codex-recovery checkpoint-heartbeat --hook-stdin --scope-key project:aquifer
1404
+ aquifer codex-recovery checkpoint-spool-status --json --limit 10
901
1405
  aquifer codex-recovery checkpoint-heartbeat-hook --scope-key project:aquifer --hooks-path "$CODEX_HOME/hooks.json" --json
1406
+ aquifer install-openclaw --openclaw-home "$OPENCLAW_HOME"
902
1407
  aquifer operator archive-distill --input /tmp/archive-snapshot.json --json`);
903
1408
  process.exit(0);
904
1409
  }
@@ -939,6 +1444,11 @@ Operator examples:
939
1444
  return;
940
1445
  }
941
1446
 
1447
+ if (command === 'install-openclaw') {
1448
+ await require('./openclaw-install').cmdInstallOpenClaw(args);
1449
+ return;
1450
+ }
1451
+
942
1452
  // All other commands need an Aquifer instance
943
1453
  const configOverrides = {};
944
1454
  if (args.flags.config) {
@@ -986,6 +1496,9 @@ Operator examples:
986
1496
  case 'migrate':
987
1497
  await cmdMigrate(aquifer, args);
988
1498
  break;
1499
+ case 'doctor':
1500
+ await cmdDoctor(aquifer, args);
1501
+ break;
989
1502
  case 'recall':
990
1503
  await cmdRecall(aquifer, args);
991
1504
  break;
@@ -1001,12 +1514,21 @@ Operator examples:
1001
1514
  case 'feedback-stats':
1002
1515
  await cmdFeedbackStats(aquifer, args);
1003
1516
  break;
1517
+ case 'review':
1518
+ await cmdReview(aquifer, args);
1519
+ break;
1004
1520
  case 'backfill':
1005
1521
  await cmdBackfill(aquifer, args);
1006
1522
  break;
1007
1523
  case 'operator':
1008
1524
  await cmdOperator(aquifer, args);
1009
1525
  break;
1526
+ case 'finalization':
1527
+ await cmdFinalization(aquifer, args);
1528
+ break;
1529
+ case 'explain':
1530
+ await cmdExplain(aquifer, args);
1531
+ break;
1010
1532
  case 'compact':
1011
1533
  await cmdCompact(aquifer, args);
1012
1534
  break;
@@ -1039,8 +1561,19 @@ module.exports = {
1039
1561
  selectedBackendInfo,
1040
1562
  cmdBackendInfo,
1041
1563
  cmdMigrate,
1564
+ cmdDoctor,
1042
1565
  cmdLocalQuickstart,
1043
1566
  cmdOperator,
1567
+ cmdFinalization,
1568
+ cmdExplain,
1569
+ cmdReview,
1570
+ formatDoctor,
1571
+ formatFinalizationList,
1572
+ formatFinalizationInspect,
1573
+ formatExplain,
1574
+ formatReviewQueue,
1575
+ formatReviewInspect,
1576
+ cmdInstallOpenClaw: require('./openclaw-install').cmdInstallOpenClaw,
1044
1577
  readSynthesisSummaryFromFlags,
1045
1578
  };
1046
1579
 
@@ -40,7 +40,7 @@ const DEFAULT_MAX_AFTERBURNS = 1;
40
40
  const DEFAULT_MIN_IMPORT_USER_MESSAGES = 3;
41
41
  const MAX_RETRY_COUNT = 3;
42
42
  const SAFE_SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,199}$/;
43
- const DEFAULT_RECOVERY_MAX_BYTES = 1024 * 1024;
43
+ const DEFAULT_RECOVERY_MAX_BYTES = 20 * 1024 * 1024;
44
44
  const DEFAULT_RECOVERY_MAX_MESSAGES = 80;
45
45
  const DEFAULT_RECOVERY_MAX_CHARS = 24000;
46
46
  const DEFAULT_RECOVERY_MAX_PROMPT_TOKENS = 9000;
package/consumers/mcp.js CHANGED
@@ -389,6 +389,9 @@ async function main() {
389
389
  for (const [status, count] of Object.entries(stats.sessions)) {
390
390
  lines.push(` ${status}: ${count}`);
391
391
  }
392
+ if (stats.pendingSessions?.available) {
393
+ lines.push(`Actionable pending/failed: ${stats.pendingSessions.total}`);
394
+ }
392
395
  lines.push(`Summaries: ${stats.summaries}`);
393
396
  lines.push(`Turn embeddings: ${stats.turnEmbeddings}`);
394
397
  lines.push(`Entities: ${stats.entities}`);