@shadowforge0/aquifer-memory 1.8.1 → 1.9.1

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 (57) hide show
  1. package/.env.example +1 -0
  2. package/README.md +82 -26
  3. package/README_CN.md +33 -23
  4. package/README_TW.md +25 -24
  5. package/aquifer.config.example.json +2 -1
  6. package/consumers/cli.js +587 -33
  7. package/consumers/codex-active-checkpoint.js +3 -1
  8. package/consumers/codex-current-memory.js +10 -6
  9. package/consumers/codex.js +6 -3
  10. package/consumers/default/daily-entries.js +2 -2
  11. package/consumers/default/index.js +40 -30
  12. package/consumers/default/prompts/summary.js +2 -2
  13. package/consumers/mcp.js +56 -46
  14. package/consumers/openclaw-ext/index.js +65 -7
  15. package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
  16. package/consumers/openclaw-ext/package.json +1 -1
  17. package/consumers/openclaw-install.js +326 -0
  18. package/consumers/openclaw-plugin.js +105 -24
  19. package/consumers/shared/compat-recall.js +101 -0
  20. package/consumers/shared/config.js +2 -0
  21. package/consumers/shared/openclaw-product-tools.js +130 -0
  22. package/consumers/shared/recall-format.js +2 -2
  23. package/core/aquifer.js +553 -41
  24. package/core/backends/local.js +169 -1
  25. package/core/doctor.js +924 -0
  26. package/core/finalization-inspector.js +164 -0
  27. package/core/finalization-review.js +88 -42
  28. package/core/interface.js +629 -0
  29. package/core/mcp-manifest.js +11 -3
  30. package/core/memory-bootstrap.js +25 -27
  31. package/core/memory-consolidation.js +564 -42
  32. package/core/memory-explain.js +593 -0
  33. package/core/memory-promotion.js +392 -55
  34. package/core/memory-recall.js +75 -71
  35. package/core/memory-records.js +107 -108
  36. package/core/memory-review.js +891 -0
  37. package/core/memory-serving.js +61 -4
  38. package/core/memory-type-policy.js +298 -0
  39. package/core/operator-observability.js +249 -0
  40. package/core/postgres-migrations.js +22 -0
  41. package/core/session-checkpoint-producer.js +3 -1
  42. package/core/session-checkpoints.js +1 -1
  43. package/core/session-finalization.js +78 -3
  44. package/core/storage.js +124 -8
  45. package/docs/getting-started.md +50 -4
  46. package/docs/setup.md +163 -24
  47. package/package.json +5 -4
  48. package/schema/004-completion.sql +4 -4
  49. package/schema/010-v1-finalization-review.sql +72 -0
  50. package/schema/019-v1-memory-review-resolutions.sql +53 -0
  51. package/schema/020-v1-assistant-shaping-memory.sql +30 -0
  52. package/scripts/backfill-canonical-key.js +1 -1
  53. package/scripts/codex-checkpoint-commands.js +28 -0
  54. package/scripts/codex-checkpoint-runtime.js +109 -0
  55. package/scripts/codex-recovery.js +16 -4
  56. package/scripts/diagnose-fts-zh.js +1 -1
  57. package/scripts/extract-insights-from-recent-sessions.js +4 -4
package/consumers/cli.js CHANGED
@@ -9,8 +9,11 @@
9
9
  * aquifer migrate Run database migrations
10
10
  * aquifer recall <query> [options] Search sessions
11
11
  * aquifer backfill [options] Enrich pending sessions
12
- * aquifer stats [options] Show database statistics
12
+ * aquifer backlog [options] Explain saved-content preparation work
13
+ * aquifer stats [options] Check memory readiness and diagnostics
13
14
  * aquifer backend-info [--json] Show selected backend capabilities
15
+ * aquifer install-openclaw Install/update OpenClaw MCP + extension wiring
16
+ * aquifer finalization ... Inspect read-only finalization ledger
14
17
  * aquifer export [options] Export sessions
15
18
  * aquifer operator ... Run operator-safe consolidation jobs
16
19
  * aquifer mcp Start MCP server
@@ -21,12 +24,13 @@ const { createAquiferFromConfig } = require('./shared/factory');
21
24
  const { loadConfig } = require('./shared/config');
22
25
  const { formatRecallResults } = require('./shared/recall-format');
23
26
  const { backendCapabilities } = require('../core/backends/capabilities');
24
-
25
- function formatDate(value, fallback) {
26
- if (!value) return fallback;
27
- const parsed = new Date(value);
28
- return isNaN(parsed.getTime()) ? fallback : parsed.toISOString().slice(0, 10);
29
- }
27
+ const { summarizeFinalizationListRow } = require('../core/finalization-inspector');
28
+ const {
29
+ buildBacklogEnvelope,
30
+ buildStatsEnvelope,
31
+ formatMemoryStatsInterface,
32
+ formatPendingWorkInterface,
33
+ } = require('../core/interface');
30
34
 
31
35
  function quoteIdentifier(identifier) {
32
36
  if (!/^[a-zA-Z_]\w{0,62}$/.test(identifier)) {
@@ -42,6 +46,12 @@ function parsePositiveInt(value, fallback) {
42
46
  return Math.max(1, parsed);
43
47
  }
44
48
 
49
+ function printTextBlock(text) {
50
+ for (const line of String(text).split(/\r?\n/)) {
51
+ console.log(line);
52
+ }
53
+ }
54
+
45
55
  function parseScopePath(value) {
46
56
  if (!value) return undefined;
47
57
  const parts = String(value).split(',').map(s => s.trim()).filter(Boolean);
@@ -177,12 +187,16 @@ function parseArgs(argv) {
177
187
  // Flags that take a value (not boolean)
178
188
  const VALUE_FLAGS = new Set([
179
189
  'limit', 'agent-id', 'source', 'date-from', 'date-to', 'output', 'format', 'config', 'status',
190
+ 'plan', 'action',
180
191
  'concurrency', 'entities', 'entity-mode', 'mode', 'session-id', 'memory-id', 'canonical-key',
181
192
  'verdict', 'feedback-type', 'note', 'db', 'since', 'min-messages', 'lookback-days', 'max-chars',
182
193
  'out', 'active-scope-key', 'active-scope-path', 'cadence', 'period-start', 'period-end',
183
194
  'policy-version', 'worker-id', 'apply-token', 'claim-lease-seconds', 'snapshot-as-of',
184
195
  'scope-id', 'scope-key', 'scope-keys', 'scope-kind', 'context-key', 'topic-key', 'authority', 'input',
185
196
  'synthesis-summary', 'synthesis-summary-file', 'min-finalizations', 'checkpoint-key',
197
+ 'openclaw-home', 'id', 'transcript-hash', 'phase', 'host', 'run-id', 'kind', 'query',
198
+ 'memory-type', 'visibility', 'as-of', 'resolution', 'reason', 'actor-kind', 'actor-id',
199
+ 'defer-until', 'expected-latest-issue-feedback-id', 'expected-latest-issue-feedback-at',
186
200
  ]);
187
201
  for (let i = 0; i < argv.length; i++) {
188
202
  if (argv[i] === '--') { args._.push(...argv.slice(i + 1)); break; }
@@ -402,34 +416,42 @@ async function cmdBackfill(aquifer, args) {
402
416
  if (failed > 0) process.exitCode = 2;
403
417
  }
404
418
 
419
+ async function cmdBacklog(aquifer, args) {
420
+ const action = args.flags.plan || args.flags.action || 'inspect';
421
+ const opts = {
422
+ limit: parsePositiveInt(args.flags.limit, 50),
423
+ source: args.flags.source || undefined,
424
+ agentId: args.flags['agent-id'] || undefined,
425
+ status: args.flags.status || undefined,
426
+ action,
427
+ };
428
+ const report = await aquifer.getPendingWork(opts);
429
+
430
+ if (args.flags.json) {
431
+ console.log(JSON.stringify(buildBacklogEnvelope(report, {
432
+ diagnostics: args.flags.diagnostics === true,
433
+ includePlan: true,
434
+ }), null, 2));
435
+ return;
436
+ }
437
+
438
+ printTextBlock(formatPendingWorkInterface(report, {
439
+ diagnostics: args.flags.diagnostics === true,
440
+ includePlan: true,
441
+ }));
442
+ }
443
+
405
444
  async function cmdStats(aquifer, args) {
406
445
  const stats = await aquifer.getStats();
407
446
 
408
447
  if (args.flags.json) {
409
- console.log(JSON.stringify(stats, null, 2));
448
+ console.log(JSON.stringify(buildStatsEnvelope(stats, {
449
+ diagnostics: args.flags.diagnostics === true,
450
+ }), null, 2));
410
451
  } else {
411
- console.log(`Backend: ${stats.backendKind || 'unknown'} (${stats.backendProfile || 'unknown'})`);
412
- console.log(`Serving mode: ${stats.serving?.mode || 'legacy'}`);
413
- console.log(`Active scope: ${stats.serving?.activeScopePath?.join(' > ') || stats.serving?.activeScopeKey || 'none'}`);
414
- console.log(`Sessions: ${stats.sessionTotal} (${Object.entries(stats.sessions).map(([k, v]) => `${k}: ${v}`).join(', ')})`);
415
- console.log(`Summaries: ${stats.summaries}`);
416
- console.log(`Turn embeddings: ${stats.turnEmbeddings}`);
417
- console.log(`Entities: ${stats.entities}`);
418
- if (stats.memoryRecords) {
419
- console.log(`Memory records: ${stats.memoryRecords.total} (${stats.memoryRecords.active} active, ${stats.memoryRecords.visibleInRecall} recall-visible, ${stats.memoryRecords.visibleInBootstrap} bootstrap-visible)`);
420
- if (stats.memoryRecords.latest) console.log(`Memory record range: ${formatDate(stats.memoryRecords.earliest, '?')} — ${formatDate(stats.memoryRecords.latest, '?')}`);
421
- }
422
- if (stats.sessionFinalizations?.available) {
423
- const statusText = Object.entries(stats.sessionFinalizations.statuses || {})
424
- .map(([status, count]) => `${status}: ${count}`)
425
- .join(', ') || 'none';
426
- console.log(`Session finalizations: ${stats.sessionFinalizations.total} (${statusText})`);
427
- if (stats.sessionFinalizations.latestFinalizedAt) console.log(`Latest finalization: ${formatDate(stats.sessionFinalizations.latestFinalizedAt, '?')}`);
428
- }
429
- if (stats.earliest) console.log(`Range: ${formatDate(stats.earliest, '?')} — ${formatDate(stats.latest, '?')}`);
430
- if ((stats.serving?.mode || 'legacy') !== 'curated') {
431
- console.log('Warning: legacy serving returns session/evidence material; configure curated serving with an active scope for current-memory answers.');
432
- }
452
+ printTextBlock(formatMemoryStatsInterface(stats, {
453
+ diagnostics: args.flags.diagnostics === true,
454
+ }));
433
455
  }
434
456
  }
435
457
 
@@ -473,6 +495,149 @@ async function cmdBackendInfo(args) {
473
495
  }
474
496
  }
475
497
 
498
+ function formatDoctor(result = {}) {
499
+ const checks = Array.isArray(result.checks) ? result.checks : [];
500
+ if (checks.length === 0) return `Doctor: ${result.status || 'unknown'}`;
501
+ return checks.map(check => {
502
+ const next = check.nextAction ? ` next=${check.nextAction}` : '';
503
+ return `${check.status || 'unknown'} ${check.id || 'check'}: ${check.summary || ''}${next}`;
504
+ }).join('\n');
505
+ }
506
+
507
+ async function cmdDoctor(aquifer, args) {
508
+ const result = await aquifer.doctor.run({
509
+ host: args.flags.host || undefined,
510
+ openclawHome: args.flags['openclaw-home'] || undefined,
511
+ limit: parsePositiveInt(args.flags.limit, 20),
512
+ });
513
+ if (args.flags.json) {
514
+ console.log(JSON.stringify(result, null, 2));
515
+ return;
516
+ }
517
+ console.log(formatDoctor(result));
518
+ }
519
+
520
+ function formatTimestamp(value) {
521
+ if (!value) return '?';
522
+ const parsed = new Date(value);
523
+ return isNaN(parsed.getTime()) ? String(value) : parsed.toISOString();
524
+ }
525
+
526
+ function shortHash(value) {
527
+ if (!value) return '?';
528
+ return String(value).slice(0, 12);
529
+ }
530
+
531
+ function formatFinalizationList(rows = []) {
532
+ if (!rows || rows.length === 0) return 'No finalization rows found.';
533
+ return rows.map(row => {
534
+ const item = summarizeFinalizationListRow(row);
535
+ const session = `${item.source}/${item.agentId}/${item.sessionId}`;
536
+ const phase = item.phase ? ` phase=${item.phase}` : '';
537
+ const mode = item.mode ? ` mode=${item.mode}` : '';
538
+ const hash = item.transcriptHashPrefix ? ` hash=${item.transcriptHashPrefix}` : '';
539
+ return `#${item.id} ${item.status} ${session}${phase}${mode}${hash} updated=${formatTimestamp(item.updatedAt)}`;
540
+ }).join('\n');
541
+ }
542
+
543
+ function formatFinalizationInspect(report = {}) {
544
+ const item = report.finalization || {};
545
+ const candidateSummary = report.candidateSummary || {};
546
+ const lineage = report.lineage || {};
547
+ const envelope = report.candidateEnvelope || {};
548
+ const memoryResult = report.memoryResult || {};
549
+ const lines = [
550
+ `Finalization #${item.id || '?'} ${item.status || 'unknown'}`,
551
+ `Session: ${item.source || '?'}/${item.agentId || '?'}/${item.sessionId || '?'} phase=${item.phase || '?'} mode=${item.mode || '?'} host=${item.host || '?'}`,
552
+ `Transcript hash: ${item.transcriptHashPrefix || shortHash(item.transcriptHash)}`,
553
+ `Updated: ${formatTimestamp(item.updatedAt)} finalized=${formatTimestamp(item.finalizedAt)}`,
554
+ ];
555
+
556
+ if (item.finalizerModel) lines.push(`Finalizer: ${item.finalizerModel}`);
557
+ if (item.scopeKind || item.scopeKey || item.contextKey || item.topicKey) {
558
+ lines.push(`Scope: kind=${item.scopeKind || '?'} key=${item.scopeKey || '?'} context=${item.contextKey || '?'} topic=${item.topicKey || '?'}`);
559
+ }
560
+ if (item.error) lines.push(`Error: ${item.error}`);
561
+
562
+ lines.push(`Memory result: candidates=${memoryResult.candidates || 0} promoted=${memoryResult.promoted || 0} quarantined=${memoryResult.quarantined || 0} skipped=${memoryResult.skipped || 0}`);
563
+ lines.push(`Candidate envelope: version=${envelope.version || '?'} hash=${shortHash(envelope.hash)} candidates=${envelope.candidateCount || 0}`);
564
+ 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}`);
565
+ lines.push(`Lineage: memories=${(lineage.memoryRecordIds || []).join(',') || 'none'} facts=${(lineage.factAssertionIds || []).join(',') || 'none'} evidenceRefs=${lineage.evidenceRefCount || 0} evidenceItems=${lineage.evidenceItemCount || 0}`);
566
+
567
+ if (report.review?.humanReviewText) {
568
+ lines.push('\nHuman review:\n' + report.review.humanReviewText);
569
+ }
570
+ if (report.review?.sessionStartText) {
571
+ lines.push('\nSessionStart:\n' + report.review.sessionStartText);
572
+ }
573
+ if (Array.isArray(report.candidates) && report.candidates.length > 0) {
574
+ lines.push('\nCandidates:');
575
+ for (const candidate of report.candidates) {
576
+ lines.push(` [${candidate.index}] ${candidate.action}${candidate.reason ? `/${candidate.reason}` : ''} ${candidate.memoryType || '?'} ${candidate.canonicalKey || '?'}${candidate.memoryRecordId ? ` memory=${candidate.memoryRecordId}` : ''}`);
577
+ }
578
+ }
579
+ return lines.join('\n');
580
+ }
581
+
582
+ async function cmdFinalization(aquifer, args) {
583
+ const verb = args._[1] || 'list';
584
+
585
+ if (verb === 'list') {
586
+ const rows = await aquifer.finalization.list({
587
+ status: args.flags.status || undefined,
588
+ agentId: args.flags['agent-id'] || undefined,
589
+ source: args.flags.source || undefined,
590
+ host: args.flags.host || undefined,
591
+ sessionId: args.flags['session-id'] || undefined,
592
+ transcriptHash: args.flags['transcript-hash'] || undefined,
593
+ phase: args.flags.phase || undefined,
594
+ mode: args.flags.mode || undefined,
595
+ limit: parsePositiveInt(args.flags.limit, 50),
596
+ });
597
+
598
+ if (args.flags.json) {
599
+ console.log(JSON.stringify({
600
+ readOnly: true,
601
+ finalizations: rows.map(summarizeFinalizationListRow),
602
+ }, null, 2));
603
+ return;
604
+ }
605
+ console.log(formatFinalizationList(rows));
606
+ return;
607
+ }
608
+
609
+ if (verb === 'inspect') {
610
+ const id = args.flags.id || args._[2] || undefined;
611
+ const sessionId = args.flags['session-id'];
612
+ if (id && (sessionId || args.flags['agent-id'] || args.flags.source || args.flags['transcript-hash'])) {
613
+ console.error('Usage: --id is mutually exclusive with --session-id/--agent-id/--source/--transcript-hash.');
614
+ process.exit(1);
615
+ }
616
+ if (!id && (!sessionId || !args.flags['agent-id'] || !args.flags.source)) {
617
+ console.error('Usage: aquifer finalization inspect (--id ID | --session-id ID --agent-id ID --source SOURCE [--transcript-hash HASH]) [--json]');
618
+ process.exit(1);
619
+ }
620
+ const report = await aquifer.finalization.inspect({
621
+ id,
622
+ sessionId: sessionId || undefined,
623
+ agentId: args.flags['agent-id'] || undefined,
624
+ source: args.flags.source || undefined,
625
+ transcriptHash: args.flags['transcript-hash'] || undefined,
626
+ phase: args.flags.phase || undefined,
627
+ });
628
+
629
+ if (args.flags.json) {
630
+ console.log(JSON.stringify(report, null, 2));
631
+ return;
632
+ }
633
+ console.log(formatFinalizationInspect(report));
634
+ return;
635
+ }
636
+
637
+ console.error('Usage: aquifer finalization <list|inspect> [options]');
638
+ process.exit(1);
639
+ }
640
+
476
641
  async function cmdLocalQuickstart(aquifer) {
477
642
  const cfg = aquifer.getConfig();
478
643
  console.log('Aquifer quickstart — verifying local starter backend.\n');
@@ -644,10 +809,319 @@ async function cmdBootstrap(aquifer, args) {
644
809
  }
645
810
  }
646
811
 
812
+ function formatExplain(result = {}) {
813
+ const selected = Array.isArray(result.selected) ? result.selected : [];
814
+ const excluded = Array.isArray(result.excluded) ? result.excluded : [];
815
+ const lines = [
816
+ `Explain ${result.lane || 'memory'}: selected=${selected.length} excluded=${excluded.length}`,
817
+ ];
818
+ if (selected.length > 0) {
819
+ lines.push('Selected:');
820
+ for (const row of selected) {
821
+ lines.push(` ${row.memoryId || row.id || '?'} ${row.canonicalKey || '?'} reason=${row.reason || 'selected'}`);
822
+ }
823
+ }
824
+ if (excluded.length > 0) {
825
+ lines.push('Excluded:');
826
+ for (const row of excluded) {
827
+ lines.push(` ${row.memoryId || row.id || '?'} ${row.canonicalKey || '?'} reason=${row.reason || 'unknown'}`);
828
+ }
829
+ }
830
+ if (result.budget) {
831
+ lines.push(`Budget: limit=${result.budget.limit || '?'} maxChars=${result.budget.maxChars || '?'} overflow=${result.budget.overflow === true}`);
832
+ }
833
+ return lines.join('\n');
834
+ }
835
+
836
+ function formatFeedbackCounts(counts = {}) {
837
+ const entries = Object.entries(counts).filter(([, count]) => count > 0);
838
+ if (entries.length === 0) return 'none';
839
+ return entries.map(([type, count]) => `${type}=${count}`).join(' ');
840
+ }
841
+
842
+ function formatReviewResolution(resolution = null) {
843
+ if (!resolution || !resolution.resolution) return 'none';
844
+ const at = resolution.resolvedAt ? ` at=${formatTimestamp(resolution.resolvedAt)}` : '';
845
+ const defer = resolution.deferUntil ? ` deferUntil=${formatTimestamp(resolution.deferUntil)}` : '';
846
+ return `${resolution.resolution}${at}${defer}`;
847
+ }
848
+
849
+ function formatReviewQueue(result = {}) {
850
+ if (result.available === false) {
851
+ return result.error || 'Memory review queue is unavailable.';
852
+ }
853
+ const rows = Array.isArray(result.items) ? result.items : (Array.isArray(result.reviewQueue) ? result.reviewQueue : []);
854
+ if (rows.length === 0) return 'No memory review items found.';
855
+ return rows.map(row => {
856
+ const scope = row.scope?.key ? ` scope=${row.scope.key}` : '';
857
+ const latest = row.latestFeedbackAt ? ` latest=${formatTimestamp(row.latestFeedbackAt)}` : '';
858
+ const resolution = row.resolution ? ` resolution=${formatReviewResolution(row.resolution)}` : '';
859
+ const counts = formatFeedbackCounts(row.feedbackCounts);
860
+ return `#${row.memoryId} severity=${row.severityScore} issues=${row.issueCount}/${row.feedbackCount} ${row.status || '?'}${scope} ${row.canonicalKey || '?'} feedback=${counts}${latest}${resolution}`;
861
+ }).join('\n');
862
+ }
863
+
864
+ function formatReviewInspect(result = {}) {
865
+ const memory = result.memory || {};
866
+ const review = result.review || {};
867
+ const visibility = memory.visibility || {};
868
+ const lines = [
869
+ `Memory #${memory.memoryId || '?'} ${memory.status || '?'} ${memory.canonicalKey || '?'}`,
870
+ `Scope: ${memory.scope?.kind || '?'}/${memory.scope?.key || '?'}`,
871
+ `Visibility: bootstrap=${visibility.bootstrap === true} recall=${visibility.recall === true}`,
872
+ `Feedback: issues=${review.issueCount || 0}/${review.feedbackCount || 0} ${formatFeedbackCounts(review.feedbackCounts)}`,
873
+ `Latest resolution: ${formatReviewResolution(review.latestResolution)}`,
874
+ ];
875
+ const events = Array.isArray(review.recentFeedback) ? review.recentFeedback : [];
876
+ if (events.length > 0) {
877
+ lines.push('Recent feedback:');
878
+ for (const event of events) {
879
+ const actor = event.actorId ? `${event.actorKind || 'actor'}:${event.actorId}` : (event.actorKind || 'actor');
880
+ lines.push(` #${event.id} ${event.feedbackType} ${actor} at=${formatTimestamp(event.createdAt)}`);
881
+ }
882
+ }
883
+ return lines.join('\n');
884
+ }
885
+
886
+ function formatReviewResolve(result = {}) {
887
+ const resolution = result.resolution || {};
888
+ const memory = result.memory || {};
889
+ const issueCount = result.review?.issueFeedbackCount || 0;
890
+ return [
891
+ `Review resolution recorded: ${resolution.resolution || '?'} for memory #${memory.memoryId || resolution.memoryId || '?'}`,
892
+ `Canonical key: ${memory.canonicalKey || resolution.canonicalKey || '?'}`,
893
+ `Issue feedback covered: ${issueCount} ${formatFeedbackCounts(result.review?.issueFeedbackCounts)}`,
894
+ `Memory mutated: ${result.memoryMutated === true}`,
895
+ ].join('\n');
896
+ }
897
+
898
+ async function cmdReview(aquifer, args) {
899
+ const verb = args._[1] || 'queue';
900
+ if (verb === 'queue') {
901
+ const activeScopePath = parseScopePath(args.flags['active-scope-path']);
902
+ const activeScopeKey = args.flags['active-scope-key']
903
+ || args.flags['scope-key']
904
+ || (activeScopePath ? activeScopePath[activeScopePath.length - 1] : undefined);
905
+ const result = await aquifer.review.queue({
906
+ feedbackType: args.flags['feedback-type'] || undefined,
907
+ memoryType: args.flags['memory-type'] || undefined,
908
+ visibility: args.flags.visibility || undefined,
909
+ activeScopeKey,
910
+ activeScopePath,
911
+ scopeKey: args.flags['scope-key'] || activeScopeKey,
912
+ canonicalKey: args.flags['canonical-key'] || undefined,
913
+ dateFrom: args.flags['date-from'] || undefined,
914
+ dateTo: args.flags['date-to'] || undefined,
915
+ asOf: args.flags['as-of'] || args.flags['snapshot-as-of'] || undefined,
916
+ includeResolved: args.flags['include-resolved'] === true,
917
+ limit: parsePositiveInt(args.flags.limit, 20),
918
+ });
919
+ if (args.flags.json) {
920
+ console.log(JSON.stringify(result, null, 2));
921
+ return;
922
+ }
923
+ console.log(formatReviewQueue(result));
924
+ return;
925
+ }
926
+
927
+ if (verb === 'inspect') {
928
+ const activeScopePath = parseScopePath(args.flags['active-scope-path']);
929
+ const activeScopeKey = args.flags['active-scope-key']
930
+ || args.flags['scope-key']
931
+ || (activeScopePath ? activeScopePath[activeScopePath.length - 1] : undefined);
932
+ const memoryId = args.flags['memory-id'] || args.flags.id || args._[2] || undefined;
933
+ const canonicalKey = args.flags['canonical-key'] || undefined;
934
+ if (memoryId && canonicalKey) {
935
+ console.error('Usage: --memory-id/--id is mutually exclusive with --canonical-key.');
936
+ process.exit(1);
937
+ }
938
+ if (!memoryId && !canonicalKey) {
939
+ console.error('Usage: aquifer review inspect (--memory-id ID | --canonical-key KEY) [--json]');
940
+ process.exit(1);
941
+ }
942
+ const result = await aquifer.review.inspect({
943
+ memoryId,
944
+ canonicalKey,
945
+ activeScopeKey,
946
+ activeScopePath,
947
+ scopeKey: args.flags['scope-key'] || activeScopeKey,
948
+ visibility: args.flags.visibility || undefined,
949
+ feedbackType: args.flags['feedback-type'] || undefined,
950
+ dateFrom: args.flags['date-from'] || undefined,
951
+ dateTo: args.flags['date-to'] || undefined,
952
+ asOf: args.flags['as-of'] || args.flags['snapshot-as-of'] || undefined,
953
+ limit: parsePositiveInt(args.flags.limit, 20),
954
+ });
955
+ if (args.flags.json) {
956
+ console.log(JSON.stringify(result, null, 2));
957
+ return;
958
+ }
959
+ console.log(formatReviewInspect(result));
960
+ return;
961
+ }
962
+
963
+ if (verb === 'resolve') {
964
+ const activeScopePath = parseScopePath(args.flags['active-scope-path']);
965
+ const activeScopeKey = args.flags['active-scope-key']
966
+ || args.flags['scope-key']
967
+ || (activeScopePath ? activeScopePath[activeScopePath.length - 1] : undefined);
968
+ const memoryId = args.flags['memory-id'] || args.flags.id || undefined;
969
+ const canonicalKey = args.flags['canonical-key'] || undefined;
970
+ if (memoryId && canonicalKey) {
971
+ console.error('Usage: --memory-id/--id is mutually exclusive with --canonical-key.');
972
+ process.exit(1);
973
+ }
974
+ if (!memoryId && !canonicalKey) {
975
+ console.error('Usage: aquifer review resolve (--memory-id ID | --canonical-key KEY) --resolution resolved|ignored|deferred [--json]');
976
+ process.exit(1);
977
+ }
978
+ if (!args.flags['expected-latest-issue-feedback-id'] && !args.flags['expected-latest-issue-feedback-at']) {
979
+ console.error('Usage: aquifer review resolve requires --expected-latest-issue-feedback-id or --expected-latest-issue-feedback-at.');
980
+ process.exit(1);
981
+ }
982
+ const result = await aquifer.review.resolve({
983
+ memoryId,
984
+ canonicalKey,
985
+ resolution: args.flags.resolution || undefined,
986
+ reason: args.flags.reason || args.flags.note || undefined,
987
+ actorKind: args.flags['actor-kind'] || undefined,
988
+ actorId: args.flags['actor-id'] || args.flags['agent-id'] || undefined,
989
+ deferUntil: args.flags['defer-until'] || undefined,
990
+ expectedLatestIssueFeedbackId: args.flags['expected-latest-issue-feedback-id'] || undefined,
991
+ expectedLatestIssueFeedbackAt: args.flags['expected-latest-issue-feedback-at'] || undefined,
992
+ activeScopeKey,
993
+ activeScopePath,
994
+ scopeKey: args.flags['scope-key'] || activeScopeKey,
995
+ visibility: args.flags.visibility || undefined,
996
+ asOf: args.flags['as-of'] || args.flags['snapshot-as-of'] || undefined,
997
+ });
998
+ if (args.flags.json) {
999
+ console.log(JSON.stringify(result, null, 2));
1000
+ return;
1001
+ }
1002
+ console.log(formatReviewResolve(result));
1003
+ return;
1004
+ }
1005
+
1006
+ console.error('Usage: aquifer review <queue|inspect|resolve> [options]');
1007
+ process.exit(1);
1008
+ }
1009
+
1010
+ async function cmdExplain(aquifer, args) {
1011
+ const lane = args._[1] || 'bootstrap';
1012
+ if (lane === 'bootstrap') {
1013
+ const result = await aquifer.memory.explainBootstrap({
1014
+ activeScopeKey: args.flags['active-scope-key'] || undefined,
1015
+ activeScopePath: parseScopePath(args.flags['active-scope-path']),
1016
+ limit: parsePositiveInt(args.flags.limit, 5),
1017
+ maxChars: parsePositiveInt(args.flags['max-chars'], 4000),
1018
+ });
1019
+ if (args.flags.json) {
1020
+ console.log(JSON.stringify(result, null, 2));
1021
+ return;
1022
+ }
1023
+ console.log(formatExplain(result));
1024
+ return;
1025
+ }
1026
+ if (lane === 'memory') {
1027
+ const query = args.flags.query || args._.slice(2).join(' ');
1028
+ if (!query) {
1029
+ console.error('Usage: aquifer explain memory --query TEXT --active-scope-key KEY [--limit N] [--json]');
1030
+ process.exit(1);
1031
+ }
1032
+ const result = await aquifer.memory.explainCurrent(query, {
1033
+ activeScopeKey: args.flags['active-scope-key'] || undefined,
1034
+ activeScopePath: parseScopePath(args.flags['active-scope-path']),
1035
+ limit: parsePositiveInt(args.flags.limit, 5),
1036
+ });
1037
+ if (args.flags.json) {
1038
+ console.log(JSON.stringify(result, null, 2));
1039
+ return;
1040
+ }
1041
+ console.log(formatExplain(result));
1042
+ return;
1043
+ }
1044
+ console.error('Usage: aquifer explain <bootstrap|memory> [options]');
1045
+ process.exit(1);
1046
+ }
1047
+
1048
+ function formatOperatorStatus(result = {}) {
1049
+ const compaction = result.compaction || {};
1050
+ const checkpoint = result.checkpoint || {};
1051
+ const lines = [
1052
+ `Compaction: ${compaction.available === false ? 'unavailable' : 'available'} statuses=${JSON.stringify(compaction.statusCounts || {})}`,
1053
+ `Checkpoint: ${checkpoint.available === false ? 'unavailable' : 'available'} statuses=${JSON.stringify(checkpoint.statusCounts || {})}`,
1054
+ ];
1055
+ if (Array.isArray(compaction.staleClaims) && compaction.staleClaims.length > 0) {
1056
+ lines.push(`Stale compaction claims: ${compaction.staleClaims.map(run => `#${run.id}/${run.cadence}`).join(', ')}`);
1057
+ }
1058
+ if (Array.isArray(compaction.latest) && compaction.latest.length > 0) {
1059
+ lines.push('Latest compaction: ' + compaction.latest.map(run => `#${run.id} ${run.status} ${run.cadence}`).join(', '));
1060
+ }
1061
+ if (Array.isArray(checkpoint.latest) && checkpoint.latest.length > 0) {
1062
+ lines.push('Latest checkpoint: ' + checkpoint.latest.map(run => `#${run.id} ${run.status} sources=${run.sourceCount}`).join(', '));
1063
+ }
1064
+ return lines.join('\n');
1065
+ }
1066
+
1067
+ function formatOperatorInspect(result = {}) {
1068
+ const run = result.run || {};
1069
+ const lines = [
1070
+ `${run.kind || 'operator'} run #${run.id || '?'} status=${run.status || '?'}`,
1071
+ ];
1072
+ if (run.kind === 'compaction') {
1073
+ lines.push(`Window: ${run.cadence || '?'} ${run.periodStart || '?'} -> ${run.periodEnd || '?'}`);
1074
+ lines.push(`Plan: candidates=${run.candidateCount || 0} statusUpdates=${run.statusUpdateCount || 0}`);
1075
+ if (run.leaseExpiresAt) lines.push(`Claim: worker=${run.workerId || '?'} leaseExpiresAt=${run.leaseExpiresAt}`);
1076
+ } else if (run.kind === 'checkpoint') {
1077
+ lines.push(`Checkpoint: scope=${run.scopeId || '?'} key=${run.checkpointKey || '?'}`);
1078
+ lines.push(`Range: ${run.fromFinalizationIdExclusive || 0} -> ${run.toFinalizationIdInclusive || '?'}`);
1079
+ lines.push(`Sources: ${run.sourceCount || 0}`);
1080
+ }
1081
+ if (run.error) lines.push(`Error: ${run.error}`);
1082
+ if (Array.isArray(result.sources) && result.sources.length > 0) {
1083
+ lines.push('Sources:');
1084
+ for (const source of result.sources) {
1085
+ lines.push(` [${source.sourceIndex}] finalization=${source.finalizationId} ${source.source || '?'}/${source.agentId || '?'}/${source.sessionId || '?'}`);
1086
+ }
1087
+ }
1088
+ return lines.join('\n');
1089
+ }
1090
+
647
1091
  async function cmdOperator(aquifer, args) {
648
1092
  const operatorVerb = args._[1] || 'compaction';
649
1093
  const cadenceVerbs = new Set(['manual', 'daily', 'weekly', 'monthly']);
650
1094
 
1095
+ if (operatorVerb === 'status') {
1096
+ const result = await aquifer.operator.status({
1097
+ limit: parsePositiveInt(args.flags.limit, 10),
1098
+ });
1099
+ if (args.flags.json) {
1100
+ console.log(JSON.stringify(result, null, 2));
1101
+ return;
1102
+ }
1103
+ console.log(formatOperatorStatus(result));
1104
+ return;
1105
+ }
1106
+
1107
+ if (operatorVerb === 'inspect') {
1108
+ const runId = args.flags['run-id'] || args.flags.id || args._[2];
1109
+ if (!runId) {
1110
+ console.error('Usage: aquifer operator inspect --run-id ID [--kind compaction|checkpoint] [--json]');
1111
+ process.exit(1);
1112
+ }
1113
+ const result = await aquifer.operator.inspect({
1114
+ runId,
1115
+ kind: args.flags.kind || 'compaction',
1116
+ });
1117
+ if (args.flags.json) {
1118
+ console.log(JSON.stringify(result, null, 2));
1119
+ return;
1120
+ }
1121
+ console.log(formatOperatorInspect(result));
1122
+ return;
1123
+ }
1124
+
651
1125
  if (operatorVerb === 'checkpoint') {
652
1126
  const synthesisSummary = readSynthesisSummaryFromFlags(args.flags);
653
1127
  const result = await aquifer.checkpoints.runProducer({
@@ -830,15 +1304,21 @@ Commands:
830
1304
  quickstart Verify end-to-end setup (migrate → commit → enrich → recall)
831
1305
  migrate Run database migrations
832
1306
  backend-info Show selected backend capabilities without connecting to a database
1307
+ install-openclaw Install/update OpenClaw MCP + extension wiring
1308
+ doctor Run read-only health diagnostics
833
1309
  recall <query> Search sessions (requires embed config)
834
1310
  evidence-recall <query> Search legacy session/evidence plane explicitly
835
1311
  feedback Record trust feedback on a session
836
1312
  memory-feedback Record curated memory feedback
837
1313
  feedback-stats Show trust feedback statistics and coverage
1314
+ review ... Inspect or resolve memory feedback review queue
838
1315
  backfill Enrich pending sessions
1316
+ backlog Explain and plan saved-content preparation work
839
1317
  operator ... Run operator-safe consolidation jobs
1318
+ finalization ... Inspect read-only finalization ledger
1319
+ explain ... Explain current-memory serving decisions
840
1320
  compact Plan or apply curated memory compaction
841
- stats Show database statistics
1321
+ stats Check memory readiness
842
1322
  export Export sessions as JSONL
843
1323
  bootstrap Show recent session context (for new session start)
844
1324
  codex-recovery ... Inspect or run Codex recovery/checkpoint flows
@@ -853,11 +1333,35 @@ Options:
853
1333
  --date-to YYYY-MM-DD End date
854
1334
  --entities A,B,C Entity names (comma-separated, recall)
855
1335
  --entity-mode any|all Entity match mode (recall, default: any)
1336
+ --id ID Finalization row id
1337
+ --host HOST Finalization host filter
1338
+ --status STATUS Finalization status filter
1339
+ --plan inspect|backfill|skip
1340
+ Saved-content dry-run action plan
1341
+ --diagnostics Include raw counters, buckets, samples, and serving diagnostics
1342
+ --phase PHASE Finalization phase filter
1343
+ --transcript-hash HASH Finalization transcript hash filter
1344
+ --run-id ID Operator run id
1345
+ --kind KIND Operator inspect kind: compaction|checkpoint
1346
+ --query TEXT Explain memory query
856
1347
  --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)
1348
+ --memory-id ID Curated memory record ID (memory-feedback, review inspect/resolve)
1349
+ --canonical-key KEY Active curated memory canonical key (memory-feedback, review queue/inspect/resolve)
859
1350
  --verdict helpful|unhelpful Feedback verdict (feedback)
860
1351
  --feedback-type TYPE Curated memory feedback type
1352
+ --memory-type TYPE Curated memory type filter (review queue)
1353
+ --visibility MODE Review visibility filter: either|both|bootstrap|recall
1354
+ --as-of ISO Review queue memory validity instant
1355
+ --include-resolved Include currently resolved review items in review queue
1356
+ --resolution ACTION Review resolution: resolved|ignored|deferred
1357
+ --reason TEXT Operator reason for review resolution
1358
+ --actor-kind KIND Review resolution actor kind: user|agent|system|curator
1359
+ --actor-id ID Review resolution actor id
1360
+ --defer-until ISO Re-open deferred review item after this instant
1361
+ --expected-latest-issue-feedback-id ID
1362
+ Reject review resolution if newer issue feedback exists
1363
+ --expected-latest-issue-feedback-at ISO
1364
+ Timestamp guard for review resolution snapshots
861
1365
  --note TEXT Feedback note (feedback)
862
1366
  --explain Show score breakdown per result (recall)
863
1367
  --allow-unsafe-debug Allow broad evidence-recall without audit boundary
@@ -865,6 +1369,11 @@ Options:
865
1369
  --dry-run Preview only (backfill)
866
1370
  --output PATH Output file (export)
867
1371
  --config PATH Config file path
1372
+ --openclaw-home PATH OpenClaw home path (install-openclaw)
1373
+ --skip-extension Do not wire the OpenClaw extension/plugin (install-openclaw)
1374
+ --skip-mcp Do not update mcp.servers.aquifer (install-openclaw)
1375
+ --force Move aside existing extension path if needed (install-openclaw)
1376
+ --link-current-package Development only: wire the current checkout instead of OpenClaw node_modules
868
1377
  --lookback-days N How far back in days (bootstrap, default: 14)
869
1378
  --max-chars N Max output characters (bootstrap, default: 4000)
870
1379
  --active-scope-key KEY Active curated memory scope key
@@ -890,6 +1399,17 @@ Options:
890
1399
  --min-messages N Min user messages to ingest (ingest-opencode, default: 3)
891
1400
 
892
1401
  Operator examples:
1402
+ aquifer doctor --json
1403
+ aquifer finalization list --status finalized --json
1404
+ aquifer finalization inspect --id 42 --json
1405
+ aquifer finalization inspect --session-id SESSION --agent-id main --source codex --transcript-hash HASH
1406
+ aquifer explain bootstrap --active-scope-key project:aquifer --json
1407
+ aquifer explain memory --query "serving contract" --active-scope-key project:aquifer --json
1408
+ aquifer review queue --scope-key project:aquifer --feedback-type incorrect --json
1409
+ aquifer review inspect --memory-id 42 --json
1410
+ aquifer review resolve --memory-id 42 --resolution resolved --reason "verified current" --json
1411
+ aquifer operator status --json
1412
+ aquifer operator inspect --run-id 42 --kind compaction --json
893
1413
  aquifer operator compaction daily --json
894
1414
  aquifer operator compaction daily --include-synthesis-prompt --json
895
1415
  aquifer operator compaction manual --period-start 2026-04-27T00:00:00Z --period-end 2026-04-28T00:00:00Z --apply
@@ -898,7 +1418,9 @@ Operator examples:
898
1418
  aquifer operator checkpoint --scope-id 7 --synthesis-summary-file /tmp/checkpoint-summary.json --apply --finalize --json
899
1419
  AQUIFER_BACKEND=local aquifer backend-info --json
900
1420
  aquifer codex-recovery checkpoint-heartbeat --hook-stdin --scope-key project:aquifer
1421
+ aquifer codex-recovery checkpoint-spool-status --json --limit 10
901
1422
  aquifer codex-recovery checkpoint-heartbeat-hook --scope-key project:aquifer --hooks-path "$CODEX_HOME/hooks.json" --json
1423
+ aquifer install-openclaw --openclaw-home "$OPENCLAW_HOME"
902
1424
  aquifer operator archive-distill --input /tmp/archive-snapshot.json --json`);
903
1425
  process.exit(0);
904
1426
  }
@@ -939,6 +1461,11 @@ Operator examples:
939
1461
  return;
940
1462
  }
941
1463
 
1464
+ if (command === 'install-openclaw') {
1465
+ await require('./openclaw-install').cmdInstallOpenClaw(args);
1466
+ return;
1467
+ }
1468
+
942
1469
  // All other commands need an Aquifer instance
943
1470
  const configOverrides = {};
944
1471
  if (args.flags.config) {
@@ -986,6 +1513,9 @@ Operator examples:
986
1513
  case 'migrate':
987
1514
  await cmdMigrate(aquifer, args);
988
1515
  break;
1516
+ case 'doctor':
1517
+ await cmdDoctor(aquifer, args);
1518
+ break;
989
1519
  case 'recall':
990
1520
  await cmdRecall(aquifer, args);
991
1521
  break;
@@ -1001,12 +1531,24 @@ Operator examples:
1001
1531
  case 'feedback-stats':
1002
1532
  await cmdFeedbackStats(aquifer, args);
1003
1533
  break;
1534
+ case 'review':
1535
+ await cmdReview(aquifer, args);
1536
+ break;
1004
1537
  case 'backfill':
1005
1538
  await cmdBackfill(aquifer, args);
1006
1539
  break;
1540
+ case 'backlog':
1541
+ await cmdBacklog(aquifer, args);
1542
+ break;
1007
1543
  case 'operator':
1008
1544
  await cmdOperator(aquifer, args);
1009
1545
  break;
1546
+ case 'finalization':
1547
+ await cmdFinalization(aquifer, args);
1548
+ break;
1549
+ case 'explain':
1550
+ await cmdExplain(aquifer, args);
1551
+ break;
1010
1552
  case 'compact':
1011
1553
  await cmdCompact(aquifer, args);
1012
1554
  break;
@@ -1039,8 +1581,20 @@ module.exports = {
1039
1581
  selectedBackendInfo,
1040
1582
  cmdBackendInfo,
1041
1583
  cmdMigrate,
1584
+ cmdDoctor,
1042
1585
  cmdLocalQuickstart,
1043
1586
  cmdOperator,
1587
+ cmdBacklog,
1588
+ cmdFinalization,
1589
+ cmdExplain,
1590
+ cmdReview,
1591
+ formatDoctor,
1592
+ formatFinalizationList,
1593
+ formatFinalizationInspect,
1594
+ formatExplain,
1595
+ formatReviewQueue,
1596
+ formatReviewInspect,
1597
+ cmdInstallOpenClaw: require('./openclaw-install').cmdInstallOpenClaw,
1044
1598
  readSynthesisSummaryFromFlags,
1045
1599
  };
1046
1600