@shadowforge0/aquifer-memory 1.5.12 → 1.6.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/.env.example +23 -0
- package/README.md +78 -73
- package/README_CN.md +659 -0
- package/README_TW.md +680 -0
- package/aquifer.config.example.json +34 -0
- package/consumers/claude-code.js +11 -11
- package/consumers/cli.js +353 -52
- package/consumers/codex-handoff.js +152 -0
- package/consumers/codex.js +1549 -0
- package/consumers/default/daily-entries.js +23 -4
- package/consumers/default/index.js +2 -2
- package/consumers/default/prompts/summary.js +6 -6
- package/consumers/mcp.js +96 -5
- package/consumers/openclaw-ext/index.js +0 -1
- package/consumers/openclaw-plugin.js +1 -1
- package/consumers/shared/config.js +8 -0
- package/consumers/shared/factory.js +1 -0
- package/consumers/shared/ingest.js +1 -1
- package/consumers/shared/normalize.js +14 -3
- package/consumers/shared/recall-format.js +27 -0
- package/consumers/shared/summary-parser.js +151 -0
- package/core/aquifer.js +372 -18
- package/core/finalization-review.js +319 -0
- package/core/mcp-manifest.js +52 -2
- package/core/memory-bootstrap.js +188 -0
- package/core/memory-consolidation.js +1236 -0
- package/core/memory-promotion.js +544 -0
- package/core/memory-recall.js +247 -0
- package/core/memory-records.js +581 -0
- package/core/memory-safety-gate.js +224 -0
- package/core/session-finalization.js +350 -0
- package/core/storage.js +385 -2
- package/docs/getting-started.md +99 -0
- package/docs/postprocess-contract.md +2 -2
- package/docs/setup.md +51 -2
- package/package.json +25 -11
- package/pipeline/normalize/adapters/codex.js +106 -0
- package/pipeline/normalize/detect.js +3 -2
- package/schema/001-base.sql +3 -0
- package/schema/007-v1-foundation.sql +273 -0
- package/schema/008-session-finalizations.sql +50 -0
- package/schema/009-v1-assertion-plane.sql +193 -0
- package/schema/010-v1-finalization-review.sql +160 -0
- package/schema/011-v1-compaction-claim.sql +46 -0
- package/schema/012-v1-compaction-lease.sql +39 -0
- package/schema/013-v1-compaction-lineage.sql +193 -0
- package/scripts/codex-recovery.js +532 -0
- package/consumers/miranda/context-inject.js +0 -120
- package/consumers/miranda/daily-entries.js +0 -224
- package/consumers/miranda/index.js +0 -364
- package/consumers/miranda/instance.js +0 -55
- package/consumers/miranda/llm.js +0 -99
- package/consumers/miranda/profile.json +0 -145
- package/consumers/miranda/prompts/summary.js +0 -303
- package/consumers/miranda/recall-format.js +0 -76
- package/consumers/miranda/render-daily-md.js +0 -186
- package/consumers/miranda/workspace-files.js +0 -91
- package/scripts/drop-entity-state-history.sql +0 -17
- package/scripts/drop-insights.sql +0 -12
- package/scripts/install-openclaw.sh +0 -59
package/core/aquifer.js
CHANGED
|
@@ -10,6 +10,7 @@ const { hybridRank } = require('./hybrid-rank');
|
|
|
10
10
|
const { summarize } = require('../pipeline/summarize');
|
|
11
11
|
const { extractEntities } = require('../pipeline/extract-entities');
|
|
12
12
|
const { createEmbedder } = require('../pipeline/embed');
|
|
13
|
+
const { applyEnrichSafetyGate, sanitizeSummaryResult } = require('./memory-safety-gate');
|
|
13
14
|
|
|
14
15
|
// ---------------------------------------------------------------------------
|
|
15
16
|
// Schema name validation
|
|
@@ -278,6 +279,102 @@ function createAquifer(config = {}) {
|
|
|
278
279
|
// Override via config.ftsConfig if you need to force one or the other.
|
|
279
280
|
let ftsConfig = config.ftsConfig || null;
|
|
280
281
|
|
|
282
|
+
const memoryCfg = config.memory || {};
|
|
283
|
+
const memoryServingMode = memoryCfg.servingMode || process.env.AQUIFER_MEMORY_SERVING_MODE || 'legacy';
|
|
284
|
+
function splitScopePath(value) {
|
|
285
|
+
if (Array.isArray(value)) return value.map(v => String(v).trim()).filter(Boolean);
|
|
286
|
+
if (typeof value !== 'string') return null;
|
|
287
|
+
const parts = value.split(',').map(v => v.trim()).filter(Boolean);
|
|
288
|
+
return parts.length > 0 ? parts : null;
|
|
289
|
+
}
|
|
290
|
+
const defaultActiveScopeKey = memoryCfg.activeScopeKey || process.env.AQUIFER_MEMORY_ACTIVE_SCOPE_KEY || null;
|
|
291
|
+
const defaultActiveScopePath = splitScopePath(
|
|
292
|
+
memoryCfg.activeScopePath || process.env.AQUIFER_MEMORY_ACTIVE_SCOPE_PATH || null,
|
|
293
|
+
);
|
|
294
|
+
function resolveMemoryServingMode(opts = {}) {
|
|
295
|
+
const mode = opts.memoryMode || opts.servingMode || memoryServingMode;
|
|
296
|
+
if (mode === 'legacy' || mode === 'evidence') return 'legacy';
|
|
297
|
+
if (mode === 'curated') return 'curated';
|
|
298
|
+
throw new Error(`Invalid memory serving mode: "${mode}". Must be one of: legacy, curated`);
|
|
299
|
+
}
|
|
300
|
+
function withDefaultMemoryScope(opts = {}) {
|
|
301
|
+
const next = { ...opts };
|
|
302
|
+
if (!next.activeScopePath && defaultActiveScopePath) next.activeScopePath = defaultActiveScopePath;
|
|
303
|
+
if (!next.activeScopeKey && defaultActiveScopeKey) next.activeScopeKey = defaultActiveScopeKey;
|
|
304
|
+
return next;
|
|
305
|
+
}
|
|
306
|
+
function assertCuratedRecallOpts(opts = {}) {
|
|
307
|
+
const unsupported = [];
|
|
308
|
+
for (const key of ['agentId', 'agentIds', 'source', 'dateFrom', 'dateTo', 'entities', 'entityMode', 'mode', 'weights', 'rerank', 'allowUnsafeDebug', 'unsafeDebug']) {
|
|
309
|
+
if (opts[key] !== undefined && opts[key] !== null) unsupported.push(key);
|
|
310
|
+
}
|
|
311
|
+
if (unsupported.length > 0) {
|
|
312
|
+
throw new Error(`curated session_recall does not support legacy filters: ${unsupported.join(', ')}. Use activeScopeKey/activeScopePath or evidence_recall.`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function assertCuratedBootstrapOpts(opts = {}) {
|
|
316
|
+
const unsupported = [];
|
|
317
|
+
for (const key of ['agentId', 'source', 'lookbackDays', 'dateFrom', 'dateTo']) {
|
|
318
|
+
if (opts[key] !== undefined && opts[key] !== null) unsupported.push(key);
|
|
319
|
+
}
|
|
320
|
+
if (unsupported.length > 0) {
|
|
321
|
+
throw new Error(`curated session_bootstrap does not support legacy filters: ${unsupported.join(', ')}. Use activeScopeKey/activeScopePath.`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function hasEvidenceBoundary(opts = {}) {
|
|
325
|
+
return Boolean(
|
|
326
|
+
opts.agentId
|
|
327
|
+
|| (Array.isArray(opts.agentIds) && opts.agentIds.length > 0)
|
|
328
|
+
|| opts.source
|
|
329
|
+
|| opts.dateFrom
|
|
330
|
+
|| opts.dateTo
|
|
331
|
+
|| opts.host
|
|
332
|
+
|| opts.sessionId
|
|
333
|
+
|| opts.allowUnsafeDebug === true
|
|
334
|
+
|| opts.unsafeDebug === true
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
function curatedRecallTitle(row = {}) {
|
|
338
|
+
const title = row.title || row.summary || row.canonical_key || row.canonicalKey || row.memory_type || row.memoryType || 'memory';
|
|
339
|
+
return String(title).trim();
|
|
340
|
+
}
|
|
341
|
+
function curatedRecallSummary(row = {}) {
|
|
342
|
+
const summary = row.summary || row.title || row.canonical_key || row.canonicalKey || '';
|
|
343
|
+
return String(summary).trim();
|
|
344
|
+
}
|
|
345
|
+
function normalizeCuratedRecallRow(row = {}) {
|
|
346
|
+
const memoryId = row.memoryId || row.memory_id || row.id || null;
|
|
347
|
+
const canonicalKey = row.canonicalKey || row.canonical_key || null;
|
|
348
|
+
const memoryType = row.memoryType || row.memory_type || null;
|
|
349
|
+
const scopeKey = row.scopeKey || row.scope_key || null;
|
|
350
|
+
const scopeKind = row.scopeKind || row.scope_kind || null;
|
|
351
|
+
const summaryText = curatedRecallSummary(row) || null;
|
|
352
|
+
const title = curatedRecallTitle(row) || null;
|
|
353
|
+
const scoreValue = row.recall_score ?? row.score ?? row.lexical_rank ?? null;
|
|
354
|
+
const score = scoreValue === null ? null : Number(scoreValue);
|
|
355
|
+
return {
|
|
356
|
+
...row,
|
|
357
|
+
memoryId: memoryId === null ? null : String(memoryId),
|
|
358
|
+
canonicalKey,
|
|
359
|
+
memoryType,
|
|
360
|
+
scopeKey,
|
|
361
|
+
scopeKind,
|
|
362
|
+
title,
|
|
363
|
+
summaryText,
|
|
364
|
+
structuredSummary: {
|
|
365
|
+
title,
|
|
366
|
+
overview: summaryText,
|
|
367
|
+
},
|
|
368
|
+
startedAt: row.acceptedAt || row.accepted_at || row.observedAt || row.observed_at || null,
|
|
369
|
+
score: Number.isFinite(score) ? score : null,
|
|
370
|
+
feedbackTarget: {
|
|
371
|
+
kind: 'memory_feedback',
|
|
372
|
+
memoryId: memoryId === null ? null : String(memoryId),
|
|
373
|
+
canonicalKey,
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
281
378
|
// State-change extraction (Q3): off by default. When enabled, enrich() runs
|
|
282
379
|
// an extra LLM call to capture temporal state transitions on whitelisted
|
|
283
380
|
// entities. See pipeline/extract-state-changes.js + core/entity-state.js.
|
|
@@ -326,6 +423,13 @@ function createAquifer(config = {}) {
|
|
|
326
423
|
{ id: '004-completion', file: '004-completion.sql', always: true, signature: 'narratives' },
|
|
327
424
|
{ id: '005-entity-state-history',file: '005-entity-state-history.sql',gate: 'entities', signature: 'entity_state_history' },
|
|
328
425
|
{ id: '006-insights', file: '006-insights.sql', always: true, signature: 'insights' },
|
|
426
|
+
{ id: '007-v1-foundation', file: '007-v1-foundation.sql', always: true, signature: 'memory_records' },
|
|
427
|
+
{ id: '008-session-finalizations',file: '008-session-finalizations.sql',always: true, signature: 'session_finalizations' },
|
|
428
|
+
{ id: '009-v1-assertion-plane', file: '009-v1-assertion-plane.sql', always: true, signature: 'fact_assertions_v1' },
|
|
429
|
+
{ id: '010-v1-finalization-review',file: '010-v1-finalization-review.sql',always: true, signature: 'finalization_candidates' },
|
|
430
|
+
{ id: '011-v1-compaction-claim', file: '011-v1-compaction-claim.sql', always: true, signature: { table: 'compaction_runs', column: 'apply_token' } },
|
|
431
|
+
{ id: '012-v1-compaction-lease', file: '012-v1-compaction-lease.sql', always: true, signature: { table: 'compaction_runs', column: 'lease_expires_at' } },
|
|
432
|
+
{ id: '013-v1-compaction-lineage', file: '013-v1-compaction-lineage.sql', always: true, signature: 'compaction_candidates' },
|
|
329
433
|
];
|
|
330
434
|
|
|
331
435
|
function requiredMigrations() {
|
|
@@ -340,15 +444,38 @@ function createAquifer(config = {}) {
|
|
|
340
444
|
const required = MIGRATION_PLAN.filter(m => m.always
|
|
341
445
|
|| (m.gate === 'entities' && entitiesEnabled)
|
|
342
446
|
|| (m.gate === 'facts' && factsEnabled));
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
);
|
|
350
|
-
const
|
|
351
|
-
|
|
447
|
+
const tableSignatures = required
|
|
448
|
+
.map(m => m.signature)
|
|
449
|
+
.filter(signature => typeof signature === 'string');
|
|
450
|
+
const columnSignatures = required
|
|
451
|
+
.map(m => m.signature)
|
|
452
|
+
.filter(signature => signature && typeof signature === 'object');
|
|
453
|
+
const presentTables = new Set();
|
|
454
|
+
const presentColumns = new Set();
|
|
455
|
+
if (tableSignatures.length > 0) {
|
|
456
|
+
const r = await queryRunner.query(
|
|
457
|
+
`SELECT tablename FROM pg_tables
|
|
458
|
+
WHERE schemaname = $1 AND tablename = ANY($2::text[])`,
|
|
459
|
+
[schema, tableSignatures]
|
|
460
|
+
);
|
|
461
|
+
for (const row of r.rows) presentTables.add(row.tablename);
|
|
462
|
+
}
|
|
463
|
+
if (columnSignatures.length > 0) {
|
|
464
|
+
const tables = [...new Set(columnSignatures.map(signature => signature.table))];
|
|
465
|
+
const r = await queryRunner.query(
|
|
466
|
+
`SELECT table_name, column_name
|
|
467
|
+
FROM information_schema.columns
|
|
468
|
+
WHERE table_schema = $1 AND table_name = ANY($2::text[])`,
|
|
469
|
+
[schema, tables]
|
|
470
|
+
);
|
|
471
|
+
for (const row of r.rows) presentColumns.add(`${row.table_name}.${row.column_name}`);
|
|
472
|
+
}
|
|
473
|
+
return required
|
|
474
|
+
.filter(m => {
|
|
475
|
+
if (typeof m.signature === 'string') return presentTables.has(m.signature);
|
|
476
|
+
return presentColumns.has(`${m.signature.table}.${m.signature.column}`);
|
|
477
|
+
})
|
|
478
|
+
.map(m => m.id);
|
|
352
479
|
}
|
|
353
480
|
|
|
354
481
|
async function buildMigrationPlan(queryRunner) {
|
|
@@ -492,6 +619,49 @@ function createAquifer(config = {}) {
|
|
|
492
619
|
const insightsSql = loadSql('006-insights.sql', schema);
|
|
493
620
|
await client.query(insightsSql); ddlExecuted.push('006-insights');
|
|
494
621
|
|
|
622
|
+
// 8. v1 curated-memory foundation (always, additive). This creates
|
|
623
|
+
// a sidecar curated plane without changing the legacy session recall
|
|
624
|
+
// or bootstrap serving path.
|
|
625
|
+
const memoryV1Sql = loadSql('007-v1-foundation.sql', schema);
|
|
626
|
+
await client.query(memoryV1Sql); ddlExecuted.push('007-v1-foundation');
|
|
627
|
+
|
|
628
|
+
// 9. v1 session finalization ledger (always, additive). Finalization
|
|
629
|
+
// is the source-of-truth lifecycle for handoff/session-end/recovery
|
|
630
|
+
// triggers; local consumer markers are cache hints only.
|
|
631
|
+
const finalizationSql = loadSql('008-session-finalizations.sql', schema);
|
|
632
|
+
await client.query(finalizationSql); ddlExecuted.push('008-session-finalizations');
|
|
633
|
+
|
|
634
|
+
// 10. v1 structured assertion plane (always, additive). This keeps
|
|
635
|
+
// legacy 004-facts untouched and adds the minimal DB contract for
|
|
636
|
+
// backing structured assertions, tenant-safe scope ancestry guards,
|
|
637
|
+
// and compaction coverage fields.
|
|
638
|
+
const assertionPlaneSql = loadSql('009-v1-assertion-plane.sql', schema);
|
|
639
|
+
await client.query(assertionPlaneSql); ddlExecuted.push('009-v1-assertion-plane');
|
|
640
|
+
|
|
641
|
+
// 11. v1 finalization review and lineage (always, additive). This
|
|
642
|
+
// stores human review text, minimal SessionStart text, finalization
|
|
643
|
+
// candidate rows, and row-level created_by_finalization_id lineage.
|
|
644
|
+
const finalizationReviewSql = loadSql('010-v1-finalization-review.sql', schema);
|
|
645
|
+
await client.query(finalizationReviewSql); ddlExecuted.push('010-v1-finalization-review');
|
|
646
|
+
|
|
647
|
+
// 12. v1 compaction claim/apply guard (always, additive). This adds
|
|
648
|
+
// a minimal claim token and one-live-apply guard for compaction_runs;
|
|
649
|
+
// it does not create or promote aggregate memory.
|
|
650
|
+
const compactionClaimSql = loadSql('011-v1-compaction-claim.sql', schema);
|
|
651
|
+
await client.query(compactionClaimSql); ddlExecuted.push('011-v1-compaction-claim');
|
|
652
|
+
|
|
653
|
+
// 13. v1 compaction claim lease (always, additive). This persists
|
|
654
|
+
// row-level lease expiry so stale applying claims can be reclaimed
|
|
655
|
+
// without relying on caller clocks or changing runtime defaults.
|
|
656
|
+
const compactionLeaseSql = loadSql('012-v1-compaction-lease.sql', schema);
|
|
657
|
+
await client.query(compactionLeaseSql); ddlExecuted.push('012-v1-compaction-lease');
|
|
658
|
+
|
|
659
|
+
// 14. v1 compaction lineage and candidate ledger (always,
|
|
660
|
+
// additive). This records aggregate candidate outcomes and links
|
|
661
|
+
// promoted rows back to the compaction run that created them.
|
|
662
|
+
const compactionLineageSql = loadSql('013-v1-compaction-lineage.sql', schema);
|
|
663
|
+
await client.query(compactionLineageSql); ddlExecuted.push('013-v1-compaction-lineage');
|
|
664
|
+
|
|
495
665
|
migrated = true;
|
|
496
666
|
} finally {
|
|
497
667
|
await client.query('SELECT pg_advisory_unlock($1)', [lockKey]).catch((err) => {
|
|
@@ -850,9 +1020,12 @@ function createAquifer(config = {}) {
|
|
|
850
1020
|
? (typeof rawMessages === 'string' ? JSON.parse(rawMessages) : rawMessages)
|
|
851
1021
|
: null;
|
|
852
1022
|
const normalized = messages ? (messages.normalized || messages) : [];
|
|
1023
|
+
const safety = applyEnrichSafetyGate(normalized);
|
|
1024
|
+
const safeNormalized = safety.messages;
|
|
1025
|
+
const safetyGate = safety.meta;
|
|
853
1026
|
|
|
854
1027
|
// 2. Extract user turns
|
|
855
|
-
const turns = storage.extractUserTurns(
|
|
1028
|
+
const turns = storage.extractUserTurns(safeNormalized);
|
|
856
1029
|
|
|
857
1030
|
// Collected across pre-tx and tx phases; any non-empty warnings demote
|
|
858
1031
|
// the final status from 'succeeded' to 'partial' (see step 8 below).
|
|
@@ -863,7 +1036,7 @@ function createAquifer(config = {}) {
|
|
|
863
1036
|
let entityRaw = null;
|
|
864
1037
|
let extra = null;
|
|
865
1038
|
|
|
866
|
-
if (!skipSummary &&
|
|
1039
|
+
if (!skipSummary && safeNormalized.length > 0) {
|
|
867
1040
|
// Pre-transaction failures (customSummaryFn / summarize throws) would
|
|
868
1041
|
// otherwise bubble out and leave the session stuck in 'processing'
|
|
869
1042
|
// until stale reclaim. Capture as a warning so status ends 'partial',
|
|
@@ -871,13 +1044,13 @@ function createAquifer(config = {}) {
|
|
|
871
1044
|
try {
|
|
872
1045
|
if (customSummaryFn) {
|
|
873
1046
|
// Custom pipeline: caller handles LLM call and parsing
|
|
874
|
-
summaryResult = await customSummaryFn(
|
|
1047
|
+
summaryResult = await customSummaryFn(safeNormalized);
|
|
875
1048
|
if (summaryResult && summaryResult.entityRaw) entityRaw = summaryResult.entityRaw;
|
|
876
1049
|
if (summaryResult && summaryResult.extra) extra = summaryResult.extra;
|
|
877
1050
|
} else {
|
|
878
1051
|
// Built-in pipeline
|
|
879
1052
|
const doMergeEntities = entitiesEnabled && mergeCall && !skipEntities;
|
|
880
|
-
summaryResult = await summarize(
|
|
1053
|
+
summaryResult = await summarize(safeNormalized, {
|
|
881
1054
|
llmFn,
|
|
882
1055
|
promptFn: summarizePromptFn,
|
|
883
1056
|
mergeEntities: doMergeEntities,
|
|
@@ -890,6 +1063,11 @@ function createAquifer(config = {}) {
|
|
|
890
1063
|
warnings.push(`summary step failed: ${e.message}`);
|
|
891
1064
|
summaryResult = null;
|
|
892
1065
|
}
|
|
1066
|
+
if (summaryResult) {
|
|
1067
|
+
const sanitizedSummary = sanitizeSummaryResult(summaryResult);
|
|
1068
|
+
summaryResult = sanitizedSummary.summaryResult;
|
|
1069
|
+
safetyGate.summary = sanitizedSummary.meta;
|
|
1070
|
+
}
|
|
893
1071
|
}
|
|
894
1072
|
|
|
895
1073
|
// 4. Pre-compute all LLM/embed results BEFORE opening transaction
|
|
@@ -921,7 +1099,7 @@ function createAquifer(config = {}) {
|
|
|
921
1099
|
} else if (entityRaw) {
|
|
922
1100
|
parsedEntities = entity.parseEntityOutput(entityRaw);
|
|
923
1101
|
} else if (llmFn && !customSummaryFn) {
|
|
924
|
-
parsedEntities = await extractEntities(
|
|
1102
|
+
parsedEntities = await extractEntities(safeNormalized, { llmFn, promptFn: entityPromptFn });
|
|
925
1103
|
}
|
|
926
1104
|
} catch (e) { warnings.push(`entity extraction failed: ${e.message}`); }
|
|
927
1105
|
}
|
|
@@ -937,7 +1115,7 @@ function createAquifer(config = {}) {
|
|
|
937
1115
|
if (scopedEntities.length > 0) {
|
|
938
1116
|
try {
|
|
939
1117
|
const { extractStateChanges } = require('../pipeline/extract-state-changes');
|
|
940
|
-
const result = await extractStateChanges(
|
|
1118
|
+
const result = await extractStateChanges(safeNormalized, {
|
|
941
1119
|
llmFn,
|
|
942
1120
|
promptFn: stateChangesPromptFn,
|
|
943
1121
|
entities: scopedEntities.map(e => ({ name: e.name, aliases: e.aliases || [] })),
|
|
@@ -969,9 +1147,9 @@ function createAquifer(config = {}) {
|
|
|
969
1147
|
summaryText: summaryResult.summaryText,
|
|
970
1148
|
structuredSummary: summaryResult.structuredSummary,
|
|
971
1149
|
model: (optModel !== undefined ? optModel : session.model) || null, sourceHash: null,
|
|
972
|
-
msgCount:
|
|
1150
|
+
msgCount: safeNormalized.length,
|
|
973
1151
|
userCount: turns.length,
|
|
974
|
-
assistantCount:
|
|
1152
|
+
assistantCount: safeNormalized.filter(m => m.role === 'assistant').length,
|
|
975
1153
|
startedAt: session.started_at, endedAt: session.ended_at,
|
|
976
1154
|
embedding: summaryEmbedding,
|
|
977
1155
|
});
|
|
@@ -1132,8 +1310,10 @@ function createAquifer(config = {}) {
|
|
|
1132
1310
|
turnVectors,
|
|
1133
1311
|
extra,
|
|
1134
1312
|
normalized,
|
|
1313
|
+
sanitized: safeNormalized,
|
|
1135
1314
|
parsedEntities,
|
|
1136
1315
|
skipped: { summary: skipSummary, entities: skipEntities, turns: skipTurnEmbed },
|
|
1316
|
+
safetyGate,
|
|
1137
1317
|
turnsEmbedded,
|
|
1138
1318
|
entitiesFound,
|
|
1139
1319
|
warnings: [...warnings], // defensive copy — caller cannot mutate enrich warnings
|
|
@@ -1150,6 +1330,7 @@ function createAquifer(config = {}) {
|
|
|
1150
1330
|
entitiesFound,
|
|
1151
1331
|
warnings,
|
|
1152
1332
|
extra,
|
|
1333
|
+
safetyGate,
|
|
1153
1334
|
session: {
|
|
1154
1335
|
id: session.id,
|
|
1155
1336
|
sessionId,
|
|
@@ -1165,6 +1346,16 @@ function createAquifer(config = {}) {
|
|
|
1165
1346
|
// --- read path ---
|
|
1166
1347
|
|
|
1167
1348
|
async recall(query, opts = {}) {
|
|
1349
|
+
if (resolveMemoryServingMode(opts) === 'curated') {
|
|
1350
|
+
assertCuratedRecallOpts(opts);
|
|
1351
|
+
await ensureMigrated();
|
|
1352
|
+
const rows = await aquifer.memory.recall(query, withDefaultMemoryScope(opts));
|
|
1353
|
+
return rows.map(normalizeCuratedRecallRow);
|
|
1354
|
+
}
|
|
1355
|
+
return aquifer.evidenceRecall(query, { ...opts, allowBroadEvidence: true });
|
|
1356
|
+
},
|
|
1357
|
+
|
|
1358
|
+
async evidenceRecall(query, opts = {}) {
|
|
1168
1359
|
// Contract (aligned across core / manifest / consumer tools): query must
|
|
1169
1360
|
// be a non-empty string. Empty strings previously short-circuited to []
|
|
1170
1361
|
// silently — that masks caller bugs. Callers wanting "recent sessions"
|
|
@@ -1172,6 +1363,9 @@ function createAquifer(config = {}) {
|
|
|
1172
1363
|
if (typeof query !== 'string' || query.trim().length === 0) {
|
|
1173
1364
|
throw new Error('aquifer.recall(query): query must be a non-empty string');
|
|
1174
1365
|
}
|
|
1366
|
+
if (opts.allowBroadEvidence !== true && !hasEvidenceBoundary(opts)) {
|
|
1367
|
+
throw new Error('evidence_recall requires an audit boundary filter (agentId, source, dateFrom/dateTo, host, sessionId) or allowUnsafeDebug=true');
|
|
1368
|
+
}
|
|
1175
1369
|
|
|
1176
1370
|
const VALID_MODES = ['fts', 'hybrid', 'vector'];
|
|
1177
1371
|
const mode = opts.mode !== undefined ? opts.mode : 'hybrid';
|
|
@@ -1558,6 +1752,50 @@ function createAquifer(config = {}) {
|
|
|
1558
1752
|
});
|
|
1559
1753
|
},
|
|
1560
1754
|
|
|
1755
|
+
async memoryFeedback(memoryId, opts = {}) {
|
|
1756
|
+
let targetMemoryId = memoryId;
|
|
1757
|
+
let canonicalKey = opts.canonicalKey || null;
|
|
1758
|
+
if (memoryId && typeof memoryId === 'object') {
|
|
1759
|
+
targetMemoryId = memoryId.memoryId || memoryId.id || null;
|
|
1760
|
+
canonicalKey = memoryId.canonicalKey || memoryId.canonical_key || canonicalKey;
|
|
1761
|
+
}
|
|
1762
|
+
if (!targetMemoryId && !canonicalKey) {
|
|
1763
|
+
throw new Error('memoryFeedback(memoryId): memoryId or canonicalKey is required');
|
|
1764
|
+
}
|
|
1765
|
+
const feedbackType = opts.feedbackType || opts.verdict;
|
|
1766
|
+
if (!feedbackType) throw new Error('opts.feedbackType is required');
|
|
1767
|
+
await ensureMigrated();
|
|
1768
|
+
if (!targetMemoryId && canonicalKey) {
|
|
1769
|
+
const rows = await memoryRecords.findActiveByCanonicalKey({
|
|
1770
|
+
tenantId: opts.tenantId || tenantId,
|
|
1771
|
+
canonicalKey,
|
|
1772
|
+
});
|
|
1773
|
+
if (!rows[0]) throw new Error(`Active memory not found: ${canonicalKey}`);
|
|
1774
|
+
targetMemoryId = rows[0].id;
|
|
1775
|
+
canonicalKey = rows[0].canonical_key || rows[0].canonicalKey || canonicalKey;
|
|
1776
|
+
}
|
|
1777
|
+
const result = await aquifer.memory.recordFeedback({
|
|
1778
|
+
tenantId: opts.tenantId || tenantId,
|
|
1779
|
+
targetKind: 'memory_record',
|
|
1780
|
+
targetId: targetMemoryId,
|
|
1781
|
+
feedbackType,
|
|
1782
|
+
actorKind: opts.actorKind || 'user',
|
|
1783
|
+
actorId: opts.actorId || opts.agentId || null,
|
|
1784
|
+
queryFingerprint: opts.queryFingerprint || null,
|
|
1785
|
+
note: opts.note || null,
|
|
1786
|
+
metadata: {
|
|
1787
|
+
...(opts.metadata || {}),
|
|
1788
|
+
publicSurface: 'memoryFeedback',
|
|
1789
|
+
},
|
|
1790
|
+
});
|
|
1791
|
+
return {
|
|
1792
|
+
...result,
|
|
1793
|
+
memoryId: targetMemoryId === null ? null : String(targetMemoryId),
|
|
1794
|
+
canonicalKey,
|
|
1795
|
+
feedbackType,
|
|
1796
|
+
};
|
|
1797
|
+
},
|
|
1798
|
+
|
|
1561
1799
|
async feedbackStats(opts = {}) {
|
|
1562
1800
|
await ensureMigrated();
|
|
1563
1801
|
return storage.getFeedbackStats(pool, {
|
|
@@ -1600,7 +1838,7 @@ function createAquifer(config = {}) {
|
|
|
1600
1838
|
// --- public config accessor ---
|
|
1601
1839
|
|
|
1602
1840
|
getConfig() {
|
|
1603
|
-
return { schema, tenantId };
|
|
1841
|
+
return { schema, tenantId, memoryServingMode };
|
|
1604
1842
|
},
|
|
1605
1843
|
|
|
1606
1844
|
// v1.2.0: expose the internal pool so host persona layers can reuse it
|
|
@@ -1705,6 +1943,10 @@ function createAquifer(config = {}) {
|
|
|
1705
1943
|
|
|
1706
1944
|
async bootstrap(opts = {}) {
|
|
1707
1945
|
await ensureMigrated();
|
|
1946
|
+
if (resolveMemoryServingMode(opts) === 'curated') {
|
|
1947
|
+
assertCuratedBootstrapOpts(opts);
|
|
1948
|
+
return aquifer.memory.bootstrap(withDefaultMemoryScope(opts));
|
|
1949
|
+
}
|
|
1708
1950
|
|
|
1709
1951
|
const agentId = opts.agentId || null;
|
|
1710
1952
|
const source = opts.source || null;
|
|
@@ -1823,6 +2065,12 @@ function createAquifer(config = {}) {
|
|
|
1823
2065
|
const { createBundles } = require('./bundles');
|
|
1824
2066
|
const { createEntityState } = require('./entity-state');
|
|
1825
2067
|
const { createInsights } = require('./insights');
|
|
2068
|
+
const { createMemoryRecords } = require('./memory-records');
|
|
2069
|
+
const { createMemoryPromotion } = require('./memory-promotion');
|
|
2070
|
+
const { createMemoryBootstrap } = require('./memory-bootstrap');
|
|
2071
|
+
const { createMemoryRecall } = require('./memory-recall');
|
|
2072
|
+
const { createMemoryConsolidation } = require('./memory-consolidation');
|
|
2073
|
+
const { createSessionFinalization } = require('./session-finalization');
|
|
1826
2074
|
const qSchema = qi(schema);
|
|
1827
2075
|
aquifer.narratives = createNarratives({ pool, schema: qSchema, defaultTenantId: tenantId });
|
|
1828
2076
|
aquifer.timeline = createTimeline({ pool, schema: qSchema, defaultTenantId: tenantId });
|
|
@@ -1851,6 +2099,112 @@ function createAquifer(config = {}) {
|
|
|
1851
2099
|
dedup: config.insights && config.insights.dedup ? config.insights.dedup : undefined,
|
|
1852
2100
|
});
|
|
1853
2101
|
|
|
2102
|
+
const memoryRecords = createMemoryRecords({ pool, schema: qSchema, defaultTenantId: tenantId });
|
|
2103
|
+
const memoryPromotion = createMemoryPromotion({ records: memoryRecords });
|
|
2104
|
+
const memoryBootstrap = createMemoryBootstrap({ records: memoryRecords });
|
|
2105
|
+
const memoryRecall = createMemoryRecall({ pool, schema: qSchema, defaultTenantId: tenantId });
|
|
2106
|
+
const memoryConsolidation = createMemoryConsolidation({
|
|
2107
|
+
pool,
|
|
2108
|
+
schema: qSchema,
|
|
2109
|
+
defaultTenantId: tenantId,
|
|
2110
|
+
records: memoryRecords,
|
|
2111
|
+
});
|
|
2112
|
+
const sessionFinalization = createSessionFinalization({
|
|
2113
|
+
pool,
|
|
2114
|
+
schema,
|
|
2115
|
+
recordsSchema: qSchema,
|
|
2116
|
+
defaultTenantId: tenantId,
|
|
2117
|
+
});
|
|
2118
|
+
|
|
2119
|
+
// v1 curated-memory sidecar. Top-level recall/bootstrap can opt into this
|
|
2120
|
+
// plane through memory.servingMode while legacy/evidence mode remains
|
|
2121
|
+
// available for compatibility and debugging.
|
|
2122
|
+
aquifer.memory = {
|
|
2123
|
+
upsertScope: async (input = {}) => {
|
|
2124
|
+
await ensureMigrated();
|
|
2125
|
+
return memoryRecords.upsertScope(input);
|
|
2126
|
+
},
|
|
2127
|
+
createVersion: async (input = {}) => {
|
|
2128
|
+
await ensureMigrated();
|
|
2129
|
+
return memoryRecords.createVersion(input);
|
|
2130
|
+
},
|
|
2131
|
+
upsertMemory: async (input = {}) => {
|
|
2132
|
+
await ensureMigrated();
|
|
2133
|
+
return memoryRecords.upsertMemory(input);
|
|
2134
|
+
},
|
|
2135
|
+
linkEvidence: async (input = {}) => {
|
|
2136
|
+
await ensureMigrated();
|
|
2137
|
+
return memoryRecords.linkEvidence(input);
|
|
2138
|
+
},
|
|
2139
|
+
recordFeedback: async (input = {}) => {
|
|
2140
|
+
await ensureMigrated();
|
|
2141
|
+
return memoryRecords.recordFeedback(input);
|
|
2142
|
+
},
|
|
2143
|
+
extractCandidates: (input = {}) => memoryPromotion.extractCandidates(input),
|
|
2144
|
+
assessCandidate: (candidate = {}) => memoryPromotion.assessCandidate(candidate),
|
|
2145
|
+
promote: async (candidates = [], opts = {}) => {
|
|
2146
|
+
await ensureMigrated();
|
|
2147
|
+
return memoryPromotion.promote(candidates, opts);
|
|
2148
|
+
},
|
|
2149
|
+
bootstrap: async (opts = {}) => {
|
|
2150
|
+
await ensureMigrated();
|
|
2151
|
+
return memoryBootstrap.bootstrap(opts);
|
|
2152
|
+
},
|
|
2153
|
+
recall: async (query, opts = {}) => {
|
|
2154
|
+
await ensureMigrated();
|
|
2155
|
+
return memoryRecall.recall(query, opts);
|
|
2156
|
+
},
|
|
2157
|
+
consolidation: {
|
|
2158
|
+
plan: memoryConsolidation.plan,
|
|
2159
|
+
distillArchiveSnapshot: memoryConsolidation.distillArchiveSnapshot,
|
|
2160
|
+
runJob: async (input = {}) => {
|
|
2161
|
+
await ensureMigrated();
|
|
2162
|
+
return memoryConsolidation.runJob(input);
|
|
2163
|
+
},
|
|
2164
|
+
recordRun: async (input = {}) => {
|
|
2165
|
+
await ensureMigrated();
|
|
2166
|
+
return memoryConsolidation.recordRun(input);
|
|
2167
|
+
},
|
|
2168
|
+
claimRun: async (input = {}) => {
|
|
2169
|
+
await ensureMigrated();
|
|
2170
|
+
return memoryConsolidation.claimRun(input);
|
|
2171
|
+
},
|
|
2172
|
+
applyPlan: async (input = {}) => {
|
|
2173
|
+
await ensureMigrated();
|
|
2174
|
+
return memoryConsolidation.applyPlan(input);
|
|
2175
|
+
},
|
|
2176
|
+
executePlan: async (input = {}) => {
|
|
2177
|
+
await ensureMigrated();
|
|
2178
|
+
return memoryConsolidation.executePlan(input);
|
|
2179
|
+
},
|
|
2180
|
+
},
|
|
2181
|
+
};
|
|
2182
|
+
|
|
2183
|
+
aquifer.finalization = {
|
|
2184
|
+
createTask: async (input = {}) => {
|
|
2185
|
+
await ensureMigrated();
|
|
2186
|
+
return sessionFinalization.createTask(input);
|
|
2187
|
+
},
|
|
2188
|
+
get: async (input = {}) => {
|
|
2189
|
+
await ensureMigrated();
|
|
2190
|
+
return sessionFinalization.get(input);
|
|
2191
|
+
},
|
|
2192
|
+
list: async (input = {}) => {
|
|
2193
|
+
await ensureMigrated();
|
|
2194
|
+
return sessionFinalization.list(input);
|
|
2195
|
+
},
|
|
2196
|
+
updateStatus: async (input = {}) => {
|
|
2197
|
+
await ensureMigrated();
|
|
2198
|
+
return sessionFinalization.updateStatus(input);
|
|
2199
|
+
},
|
|
2200
|
+
finalizeSession: async (input = {}) => {
|
|
2201
|
+
await ensureMigrated();
|
|
2202
|
+
return sessionFinalization.finalizeSession(input);
|
|
2203
|
+
},
|
|
2204
|
+
};
|
|
2205
|
+
|
|
2206
|
+
aquifer.finalizeSession = aquifer.finalization.finalizeSession;
|
|
2207
|
+
|
|
1854
2208
|
return aquifer;
|
|
1855
2209
|
}
|
|
1856
2210
|
|