@shadowforge0/aquifer-memory 1.7.0 → 1.8.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.
- package/.env.example +8 -0
- package/README.md +66 -0
- package/aquifer.config.example.json +19 -0
- package/consumers/cli.js +217 -14
- package/consumers/codex-active-checkpoint.js +186 -0
- package/consumers/codex-current-memory.js +106 -0
- package/consumers/codex-handoff.js +442 -3
- package/consumers/codex.js +164 -107
- package/consumers/mcp.js +144 -6
- package/consumers/shared/config.js +60 -1
- package/consumers/shared/factory.js +10 -3
- package/core/aquifer.js +351 -840
- package/core/backends/capabilities.js +89 -0
- package/core/backends/local.js +430 -0
- package/core/legacy-bootstrap.js +140 -0
- package/core/mcp-manifest.js +66 -2
- package/core/memory-promotion.js +157 -26
- package/core/memory-recall.js +341 -22
- package/core/memory-records.js +128 -8
- package/core/memory-serving.js +132 -0
- package/core/postgres-migrations.js +533 -0
- package/core/public-session-filter.js +40 -0
- package/core/recall-runtime.js +115 -0
- package/core/scope-attribution.js +279 -0
- package/core/session-checkpoint-producer.js +412 -0
- package/core/session-checkpoints.js +432 -0
- package/core/session-finalization.js +82 -1
- package/core/storage-checkpoints.js +546 -0
- package/core/storage.js +121 -8
- package/docs/setup.md +22 -0
- package/package.json +8 -4
- package/schema/014-v1-checkpoint-runs.sql +349 -0
- package/schema/015-v1-evidence-items.sql +92 -0
- package/schema/016-v1-evidence-ref-multi-item.sql +19 -0
- package/schema/017-v1-memory-record-embeddings.sql +25 -0
- package/schema/018-v1-finalization-candidate-envelope.sql +39 -0
- package/scripts/codex-checkpoint-commands.js +464 -0
- package/scripts/codex-checkpoint-runtime.js +520 -0
- package/scripts/codex-recovery.js +105 -0
package/consumers/codex.js
CHANGED
|
@@ -22,6 +22,16 @@ const crypto = require('crypto');
|
|
|
22
22
|
const DEFAULT_CODEX_HOME = path.join(os.homedir(), '.codex');
|
|
23
23
|
const { normalizeMessages } = require('./shared/normalize');
|
|
24
24
|
const { applyEnrichSafetyGate } = require('../core/memory-safety-gate');
|
|
25
|
+
const {
|
|
26
|
+
buildActiveSessionCheckpointInput,
|
|
27
|
+
buildActiveSessionCheckpointPrompt,
|
|
28
|
+
prepareActiveSessionCheckpoint: prepareActiveSessionCheckpointImpl,
|
|
29
|
+
} = require('./codex-active-checkpoint');
|
|
30
|
+
const {
|
|
31
|
+
formatCurrentMemoryPromptBlock,
|
|
32
|
+
compactCurrentMemorySnapshot,
|
|
33
|
+
resolveCurrentMemoryForFinalization,
|
|
34
|
+
} = require('./codex-current-memory');
|
|
25
35
|
const DEFAULT_IDLE_MS = 5 * 60 * 1000;
|
|
26
36
|
const DEFAULT_CLAIM_TTL_MS = 5 * 60 * 1000;
|
|
27
37
|
const DEFAULT_MIN_BYTES = 1000;
|
|
@@ -1102,6 +1112,92 @@ function approxPromptTokens(text) {
|
|
|
1102
1112
|
return Math.ceil(String(text || '').length / 3);
|
|
1103
1113
|
}
|
|
1104
1114
|
|
|
1115
|
+
function recoveryPromptBudgetExceeded(text, opts = {}) {
|
|
1116
|
+
const promptTokens = approxPromptTokens(text);
|
|
1117
|
+
const maxChars = opts.maxChars;
|
|
1118
|
+
const maxPromptTokens = opts.maxPromptTokens;
|
|
1119
|
+
return {
|
|
1120
|
+
promptTokens,
|
|
1121
|
+
exceeded: Boolean(
|
|
1122
|
+
(Number.isFinite(maxChars) && maxChars > 0 && text.length > maxChars)
|
|
1123
|
+
|| (Number.isFinite(maxPromptTokens) && maxPromptTokens > 0 && promptTokens > maxPromptTokens)
|
|
1124
|
+
),
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function buildBoundedRecoveryTranscript(safeMessages = [], opts = {}) {
|
|
1129
|
+
const allowTail = opts.allowTail === true;
|
|
1130
|
+
const maxMessages = opts.maxMessages;
|
|
1131
|
+
const maxChars = opts.maxChars;
|
|
1132
|
+
const maxPromptTokens = opts.maxPromptTokens;
|
|
1133
|
+
const fullText = formatRecoveryTranscript(safeMessages);
|
|
1134
|
+
const fullPromptTokens = approxPromptTokens(fullText);
|
|
1135
|
+
let messages = safeMessages;
|
|
1136
|
+
let omittedMessageCount = 0;
|
|
1137
|
+
let truncatedByMessages = false;
|
|
1138
|
+
|
|
1139
|
+
if (Number.isFinite(maxMessages) && maxMessages > 0 && messages.length > maxMessages) {
|
|
1140
|
+
if (!allowTail) {
|
|
1141
|
+
return {
|
|
1142
|
+
status: 'deferred',
|
|
1143
|
+
reason: 'max_messages',
|
|
1144
|
+
messageCount: messages.length,
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
omittedMessageCount = messages.length - maxMessages;
|
|
1148
|
+
messages = messages.slice(-maxMessages);
|
|
1149
|
+
truncatedByMessages = true;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
let text = formatRecoveryTranscript(messages);
|
|
1153
|
+
let budget = recoveryPromptBudgetExceeded(text, { maxChars, maxPromptTokens });
|
|
1154
|
+
let truncatedByBudget = false;
|
|
1155
|
+
while (allowTail && budget.exceeded && messages.length > 1) {
|
|
1156
|
+
messages = messages.slice(1);
|
|
1157
|
+
omittedMessageCount += 1;
|
|
1158
|
+
truncatedByBudget = true;
|
|
1159
|
+
text = formatRecoveryTranscript(messages);
|
|
1160
|
+
budget = recoveryPromptBudgetExceeded(text, { maxChars, maxPromptTokens });
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (budget.exceeded) {
|
|
1164
|
+
return {
|
|
1165
|
+
status: 'deferred',
|
|
1166
|
+
reason: 'prompt_budget',
|
|
1167
|
+
charCount: text.length,
|
|
1168
|
+
approxPromptTokens: budget.promptTokens,
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
return {
|
|
1173
|
+
status: 'ok',
|
|
1174
|
+
messages,
|
|
1175
|
+
text,
|
|
1176
|
+
charCount: text.length,
|
|
1177
|
+
approxPromptTokens: budget.promptTokens,
|
|
1178
|
+
fullCharCount: fullText.length,
|
|
1179
|
+
fullApproxPromptTokens: fullPromptTokens,
|
|
1180
|
+
truncated: truncatedByMessages || truncatedByBudget,
|
|
1181
|
+
transcriptWindow: {
|
|
1182
|
+
mode: allowTail ? 'tail' : 'full',
|
|
1183
|
+
truncated: truncatedByMessages || truncatedByBudget,
|
|
1184
|
+
omittedMessageCount,
|
|
1185
|
+
includedMessageCount: messages.length,
|
|
1186
|
+
totalMessageCount: safeMessages.length,
|
|
1187
|
+
truncatedByMessages,
|
|
1188
|
+
truncatedByBudget,
|
|
1189
|
+
},
|
|
1190
|
+
coverage: {
|
|
1191
|
+
coordinateSystem: 'codex_sanitized_view_v1',
|
|
1192
|
+
messageIndexBase: 0,
|
|
1193
|
+
charIndexBase: 0,
|
|
1194
|
+
semantics: 'coveredUntilChar is the first uncovered zero-based char offset; messages up to coveredUntilMessageIndex are covered.',
|
|
1195
|
+
coveredUntilMessageIndex: safeMessages.length > 0 ? safeMessages.length - 1 : 0,
|
|
1196
|
+
coveredUntilChar: fullText.length,
|
|
1197
|
+
},
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1105
1201
|
function materializeRecoveryTranscriptView(candidate = {}, opts = {}) {
|
|
1106
1202
|
const filePath = candidate.filePath || candidate.metadata?.filePath;
|
|
1107
1203
|
if (!filePath) {
|
|
@@ -1115,6 +1211,7 @@ function materializeRecoveryTranscriptView(candidate = {}, opts = {}) {
|
|
|
1115
1211
|
const maxMessages = opts.maxRecoveryMessages ?? DEFAULT_RECOVERY_MAX_MESSAGES;
|
|
1116
1212
|
const maxChars = opts.maxRecoveryChars ?? DEFAULT_RECOVERY_MAX_CHARS;
|
|
1117
1213
|
const maxPromptTokens = opts.maxRecoveryPromptTokens ?? DEFAULT_RECOVERY_MAX_PROMPT_TOKENS;
|
|
1214
|
+
const allowTail = opts.tailOnMaxBudget === true || opts.tailOnBudget === true || opts.tail === true;
|
|
1118
1215
|
|
|
1119
1216
|
let stat;
|
|
1120
1217
|
try {
|
|
@@ -1122,7 +1219,7 @@ function materializeRecoveryTranscriptView(candidate = {}, opts = {}) {
|
|
|
1122
1219
|
} catch {
|
|
1123
1220
|
return { status: 'not_found', sessionId: candidate.sessionId || null, filePath };
|
|
1124
1221
|
}
|
|
1125
|
-
if (Number.isFinite(maxBytes) && maxBytes > 0 && stat.size > maxBytes) {
|
|
1222
|
+
if (!allowTail && Number.isFinite(maxBytes) && maxBytes > 0 && stat.size > maxBytes) {
|
|
1126
1223
|
return { status: 'deferred', reason: 'max_bytes', sessionId: candidate.sessionId || null, filePath, size: stat.size };
|
|
1127
1224
|
}
|
|
1128
1225
|
|
|
@@ -1154,7 +1251,13 @@ function materializeRecoveryTranscriptView(candidate = {}, opts = {}) {
|
|
|
1154
1251
|
safetyGate: safety.meta,
|
|
1155
1252
|
};
|
|
1156
1253
|
}
|
|
1157
|
-
|
|
1254
|
+
const bounded = buildBoundedRecoveryTranscript(safeMessages, {
|
|
1255
|
+
allowTail,
|
|
1256
|
+
maxMessages,
|
|
1257
|
+
maxChars,
|
|
1258
|
+
maxPromptTokens,
|
|
1259
|
+
});
|
|
1260
|
+
if (bounded.status === 'deferred' && bounded.reason === 'max_messages') {
|
|
1158
1261
|
return {
|
|
1159
1262
|
status: 'deferred',
|
|
1160
1263
|
reason: 'max_messages',
|
|
@@ -1166,20 +1269,16 @@ function materializeRecoveryTranscriptView(candidate = {}, opts = {}) {
|
|
|
1166
1269
|
safetyGate: safety.meta,
|
|
1167
1270
|
};
|
|
1168
1271
|
}
|
|
1169
|
-
|
|
1170
|
-
const text = formatRecoveryTranscript(safeMessages);
|
|
1171
|
-
const promptTokens = approxPromptTokens(text);
|
|
1172
|
-
if ((Number.isFinite(maxChars) && maxChars > 0 && text.length > maxChars)
|
|
1173
|
-
|| (Number.isFinite(maxPromptTokens) && maxPromptTokens > 0 && promptTokens > maxPromptTokens)) {
|
|
1272
|
+
if (bounded.status === 'deferred') {
|
|
1174
1273
|
return {
|
|
1175
1274
|
status: 'deferred',
|
|
1176
|
-
reason:
|
|
1275
|
+
reason: bounded.reason,
|
|
1177
1276
|
sessionId: parsed.sessionId,
|
|
1178
1277
|
fileSessionId: parsed.fileSessionId,
|
|
1179
1278
|
filePath,
|
|
1180
1279
|
transcriptHash,
|
|
1181
|
-
charCount:
|
|
1182
|
-
approxPromptTokens:
|
|
1280
|
+
charCount: bounded.charCount,
|
|
1281
|
+
approxPromptTokens: bounded.approxPromptTokens,
|
|
1183
1282
|
safetyGate: safety.meta,
|
|
1184
1283
|
};
|
|
1185
1284
|
}
|
|
@@ -1190,16 +1289,23 @@ function materializeRecoveryTranscriptView(candidate = {}, opts = {}) {
|
|
|
1190
1289
|
fileSessionId: parsed.fileSessionId,
|
|
1191
1290
|
filePath,
|
|
1192
1291
|
transcriptHash,
|
|
1193
|
-
messages:
|
|
1194
|
-
text,
|
|
1195
|
-
charCount:
|
|
1196
|
-
approxPromptTokens:
|
|
1292
|
+
messages: bounded.messages,
|
|
1293
|
+
text: bounded.text,
|
|
1294
|
+
charCount: bounded.charCount,
|
|
1295
|
+
approxPromptTokens: bounded.approxPromptTokens,
|
|
1296
|
+
fullCharCount: bounded.fullCharCount,
|
|
1297
|
+
fullApproxPromptTokens: bounded.fullApproxPromptTokens,
|
|
1298
|
+
truncated: bounded.truncated,
|
|
1299
|
+
transcriptWindow: bounded.transcriptWindow,
|
|
1300
|
+
coverage: bounded.coverage,
|
|
1197
1301
|
safetyGate: safety.meta,
|
|
1198
1302
|
counts: {
|
|
1199
1303
|
messageCount: parsed.normalized.messages.length,
|
|
1200
1304
|
safeMessageCount: safeMessages.length,
|
|
1201
1305
|
userCount: parsed.normalized.userCount,
|
|
1202
1306
|
assistantCount: parsed.normalized.assistantCount,
|
|
1307
|
+
fullCharCount: bounded.fullCharCount,
|
|
1308
|
+
fullApproxPromptTokens: bounded.fullApproxPromptTokens,
|
|
1203
1309
|
},
|
|
1204
1310
|
metadata: {
|
|
1205
1311
|
model: parsed.normalized.model,
|
|
@@ -1212,96 +1318,11 @@ function materializeRecoveryTranscriptView(candidate = {}, opts = {}) {
|
|
|
1212
1318
|
};
|
|
1213
1319
|
}
|
|
1214
1320
|
|
|
1215
|
-
function
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
memoryType: row.memoryType || row.memory_type || 'memory',
|
|
1220
|
-
canonicalKey: row.canonicalKey || row.canonical_key || null,
|
|
1221
|
-
scopeKey: row.scopeKey || row.scope_key || null,
|
|
1222
|
-
summary: String(row.summary || row.title || '').replace(/\s+/g, ' ').trim(),
|
|
1223
|
-
authority: row.authority || null,
|
|
1224
|
-
confidence,
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
function formatCurrentMemoryPromptBlock(currentMemory = null, opts = {}) {
|
|
1229
|
-
const maxItems = Math.max(0, Math.min(20, opts.maxCurrentMemoryItems || opts.currentMemoryLimit || 12));
|
|
1230
|
-
const meta = currentMemory && currentMemory.meta ? currentMemory.meta : {};
|
|
1231
|
-
const rows = Array.isArray(currentMemory?.memories)
|
|
1232
|
-
? currentMemory.memories
|
|
1233
|
-
: (Array.isArray(currentMemory?.items) ? currentMemory.items : []);
|
|
1234
|
-
const compactRows = rows.map(compactCurrentMemoryRow).filter(row => row.summary).slice(0, maxItems);
|
|
1235
|
-
const attrs = [
|
|
1236
|
-
`source="${meta.source || 'memory_records'}"`,
|
|
1237
|
-
`serving_contract="${meta.servingContract || meta.serving_contract || 'current_memory_v1'}"`,
|
|
1238
|
-
`count="${compactRows.length}"`,
|
|
1239
|
-
`truncated="${Boolean(meta.truncated || rows.length > compactRows.length)}"`,
|
|
1240
|
-
`degraded="${Boolean(meta.degraded || currentMemory?.error)}"`,
|
|
1241
|
-
];
|
|
1242
|
-
const lines = compactRows.map(row => {
|
|
1243
|
-
const scope = row.scopeKey ? ` scope=${row.scopeKey}` : '';
|
|
1244
|
-
const authority = row.authority ? ` authority=${row.authority}` : '';
|
|
1245
|
-
const confidence = row.confidence ? ` confidence=${row.confidence}` : '';
|
|
1246
|
-
return `- ${row.memoryType}${scope}${authority}${confidence}: ${row.summary}`;
|
|
1321
|
+
async function prepareActiveSessionCheckpoint(aquifer, opts = {}) {
|
|
1322
|
+
return prepareActiveSessionCheckpointImpl(aquifer, opts, {
|
|
1323
|
+
materializeRecoveryTranscriptView,
|
|
1324
|
+
resolveCurrentMemoryForFinalization,
|
|
1247
1325
|
});
|
|
1248
|
-
if (currentMemory && currentMemory.error && lines.length === 0) {
|
|
1249
|
-
lines.push(`- degraded: ${String(currentMemory.error).replace(/\s+/g, ' ').trim()}`);
|
|
1250
|
-
}
|
|
1251
|
-
if (lines.length === 0) lines.push('- none');
|
|
1252
|
-
return [
|
|
1253
|
-
`<current_memory ${attrs.join(' ')}>`,
|
|
1254
|
-
...lines,
|
|
1255
|
-
'</current_memory>',
|
|
1256
|
-
].join('\n');
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
function compactCurrentMemorySnapshot(currentMemory = null, opts = {}) {
|
|
1260
|
-
const maxItems = Math.max(0, Math.min(20, opts.maxCurrentMemoryItems || opts.currentMemoryLimit || 12));
|
|
1261
|
-
const meta = currentMemory && currentMemory.meta ? currentMemory.meta : {};
|
|
1262
|
-
const rows = Array.isArray(currentMemory?.memories)
|
|
1263
|
-
? currentMemory.memories
|
|
1264
|
-
: (Array.isArray(currentMemory?.items) ? currentMemory.items : []);
|
|
1265
|
-
return {
|
|
1266
|
-
memories: rows.map(compactCurrentMemoryRow).filter(row => row.summary).slice(0, maxItems),
|
|
1267
|
-
meta: {
|
|
1268
|
-
source: meta.source || 'memory_records',
|
|
1269
|
-
servingContract: meta.servingContract || meta.serving_contract || 'current_memory_v1',
|
|
1270
|
-
count: Math.min(rows.length, maxItems),
|
|
1271
|
-
truncated: Boolean(meta.truncated || rows.length > maxItems),
|
|
1272
|
-
degraded: Boolean(meta.degraded || currentMemory?.error),
|
|
1273
|
-
},
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
async function resolveCurrentMemoryForFinalization(aquifer, opts = {}) {
|
|
1278
|
-
if (opts.includeCurrentMemory === false) return null;
|
|
1279
|
-
if (opts.currentMemory !== undefined) return opts.currentMemory;
|
|
1280
|
-
const currentFn = aquifer?.memory?.current || aquifer?.memory?.listCurrentMemory;
|
|
1281
|
-
if (typeof currentFn !== 'function') return null;
|
|
1282
|
-
const limit = Math.max(1, Math.min(20, opts.currentMemoryLimit || opts.maxCurrentMemoryItems || 12));
|
|
1283
|
-
try {
|
|
1284
|
-
return await currentFn.call(aquifer.memory, {
|
|
1285
|
-
tenantId: opts.tenantId,
|
|
1286
|
-
activeScopeKey: opts.activeScopeKey || opts.scopeKey,
|
|
1287
|
-
activeScopePath: opts.activeScopePath,
|
|
1288
|
-
scopeId: opts.scopeId,
|
|
1289
|
-
asOf: opts.asOf,
|
|
1290
|
-
limit,
|
|
1291
|
-
});
|
|
1292
|
-
} catch (err) {
|
|
1293
|
-
return {
|
|
1294
|
-
memories: [],
|
|
1295
|
-
meta: {
|
|
1296
|
-
source: 'memory_records',
|
|
1297
|
-
servingContract: 'current_memory_v1',
|
|
1298
|
-
count: 0,
|
|
1299
|
-
truncated: false,
|
|
1300
|
-
degraded: true,
|
|
1301
|
-
},
|
|
1302
|
-
error: err.message,
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
1326
|
}
|
|
1306
1327
|
|
|
1307
1328
|
function buildFinalizationPrompt(view = {}, opts = {}) {
|
|
@@ -1346,6 +1367,30 @@ function normalizeFinalizationSummary(summary = {}) {
|
|
|
1346
1367
|
return { summaryText, structuredSummary };
|
|
1347
1368
|
}
|
|
1348
1369
|
|
|
1370
|
+
function inferScopeKindFromKey(scopeKey) {
|
|
1371
|
+
const key = String(scopeKey || '').trim();
|
|
1372
|
+
if (!key) return null;
|
|
1373
|
+
if (key === 'global') return 'global';
|
|
1374
|
+
const prefix = key.split(':')[0];
|
|
1375
|
+
return ['user', 'workspace', 'project', 'repo', 'task', 'session', 'host_runtime'].includes(prefix)
|
|
1376
|
+
? prefix
|
|
1377
|
+
: null;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
function resolveFinalizationScope(opts = {}) {
|
|
1381
|
+
const meta = opts.currentMemory && opts.currentMemory.meta ? opts.currentMemory.meta : {};
|
|
1382
|
+
const metaPath = Array.isArray(meta.activeScopePath) ? meta.activeScopePath : null;
|
|
1383
|
+
const scopeKey = opts.scopeKey
|
|
1384
|
+
|| opts.activeScopeKey
|
|
1385
|
+
|| meta.activeScopeKey
|
|
1386
|
+
|| (metaPath && metaPath.length > 0 ? metaPath[metaPath.length - 1] : null)
|
|
1387
|
+
|| null;
|
|
1388
|
+
return {
|
|
1389
|
+
scopeKey,
|
|
1390
|
+
scopeKind: opts.scopeKind || inferScopeKindFromKey(scopeKey),
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1349
1394
|
async function ensureCommittedForFinalization(aquifer, view = {}, opts = {}) {
|
|
1350
1395
|
const {
|
|
1351
1396
|
agentId = 'main',
|
|
@@ -1415,10 +1460,15 @@ async function finalizeTranscriptView(aquifer, view = {}, summary = {}, opts = {
|
|
|
1415
1460
|
trigger: mode,
|
|
1416
1461
|
...(opts.metadata || {}),
|
|
1417
1462
|
};
|
|
1463
|
+
let resolvedCurrentMemory = opts.currentMemory;
|
|
1418
1464
|
if (!metadata.currentMemory) {
|
|
1419
|
-
|
|
1420
|
-
if (
|
|
1465
|
+
resolvedCurrentMemory = await resolveCurrentMemoryForFinalization(aquifer, opts);
|
|
1466
|
+
if (resolvedCurrentMemory) metadata.currentMemory = compactCurrentMemorySnapshot(resolvedCurrentMemory, opts);
|
|
1421
1467
|
}
|
|
1468
|
+
const finalizationScope = resolveFinalizationScope({
|
|
1469
|
+
...opts,
|
|
1470
|
+
currentMemory: resolvedCurrentMemory,
|
|
1471
|
+
});
|
|
1422
1472
|
const result = await finalizeSession({
|
|
1423
1473
|
sessionId: view.sessionId,
|
|
1424
1474
|
agentId,
|
|
@@ -1435,13 +1485,15 @@ async function finalizeTranscriptView(aquifer, view = {}, summary = {}, opts = {
|
|
|
1435
1485
|
startedAt: view.metadata?.startedAt || null,
|
|
1436
1486
|
endedAt: view.metadata?.lastMessageAt || null,
|
|
1437
1487
|
embedding: opts.embedding || null,
|
|
1438
|
-
scopeKind:
|
|
1439
|
-
scopeKey:
|
|
1488
|
+
scopeKind: finalizationScope.scopeKind,
|
|
1489
|
+
scopeKey: finalizationScope.scopeKey,
|
|
1440
1490
|
contextKey: opts.contextKey || null,
|
|
1441
1491
|
topicKey: opts.topicKey || null,
|
|
1442
1492
|
authority: opts.authority || 'verified_summary',
|
|
1443
1493
|
candidates: Array.isArray(opts.candidates) ? opts.candidates : undefined,
|
|
1444
1494
|
candidatePayload: opts.candidatePayload || null,
|
|
1495
|
+
candidateEnvelope: opts.candidateEnvelope || null,
|
|
1496
|
+
coverage: opts.coverage || null,
|
|
1445
1497
|
metadata,
|
|
1446
1498
|
});
|
|
1447
1499
|
const humanReviewText = result.humanReviewText || '';
|
|
@@ -1594,6 +1646,8 @@ async function finalizeCodexSession(aquifer, input = {}, opts = {}) {
|
|
|
1594
1646
|
currentMemory: input.currentMemory !== undefined ? input.currentMemory : opts.currentMemory,
|
|
1595
1647
|
currentMemoryLimit: input.currentMemoryLimit || opts.currentMemoryLimit || null,
|
|
1596
1648
|
includeCurrentMemory: input.includeCurrentMemory !== undefined ? input.includeCurrentMemory : opts.includeCurrentMemory,
|
|
1649
|
+
candidateEnvelope: input.candidateEnvelope || opts.candidateEnvelope || null,
|
|
1650
|
+
coverage: input.coverage || opts.coverage || null,
|
|
1597
1651
|
});
|
|
1598
1652
|
}
|
|
1599
1653
|
|
|
@@ -1654,6 +1708,9 @@ module.exports = {
|
|
|
1654
1708
|
findDbEligibleRecoveryCandidates,
|
|
1655
1709
|
materializeRecoveryTranscriptView,
|
|
1656
1710
|
buildFinalizationPrompt,
|
|
1711
|
+
buildActiveSessionCheckpointInput,
|
|
1712
|
+
buildActiveSessionCheckpointPrompt,
|
|
1713
|
+
prepareActiveSessionCheckpoint,
|
|
1657
1714
|
prepareSessionStartRecovery,
|
|
1658
1715
|
recordRecoveryDecision,
|
|
1659
1716
|
finalizeTranscriptView,
|
package/consumers/mcp.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* This is the primary integration surface for Aquifer. Agent hosts (Claude Code,
|
|
8
8
|
* Codex, OpenCode, etc.) should integrate through this MCP server.
|
|
9
9
|
*
|
|
10
|
-
* Tools:
|
|
11
|
-
*
|
|
10
|
+
* Tools: memory_recall, historical_recall, session_recall, evidence_recall,
|
|
11
|
+
* session_feedback, memory_feedback, feedback_stats, session_bootstrap,
|
|
12
|
+
* memory_stats, memory_pending
|
|
12
13
|
*
|
|
13
14
|
* Usage:
|
|
14
15
|
* npx aquifer mcp
|
|
@@ -38,6 +39,32 @@ function formatResults(results, query, explain) {
|
|
|
38
39
|
return formatRecallResults(results, { query, showScore: true, showExplain: !!explain });
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
function scopeLabel(config = {}) {
|
|
43
|
+
if (Array.isArray(config.memoryActiveScopePath) && config.memoryActiveScopePath.length > 0) {
|
|
44
|
+
return config.memoryActiveScopePath.join(' > ');
|
|
45
|
+
}
|
|
46
|
+
return config.memoryActiveScopeKey || 'none';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sessionRecallLaneHeader(config = {}) {
|
|
50
|
+
const mode = config.memoryServingMode || 'legacy';
|
|
51
|
+
if (mode === 'curated') {
|
|
52
|
+
return `Serving lane: curated current memory (active scope: ${scopeLabel(config)})`;
|
|
53
|
+
}
|
|
54
|
+
return 'Serving lane: legacy evidence/session recall (not current memory)';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function memoryRecallLaneHeader(config = {}, params = {}) {
|
|
58
|
+
const scope = Array.isArray(params.activeScopePath) && params.activeScopePath.length > 0
|
|
59
|
+
? params.activeScopePath.join(' > ')
|
|
60
|
+
: (params.activeScopeKey || scopeLabel(config));
|
|
61
|
+
return `Serving lane: explicit current memory recall (active scope: ${scope || 'none'})`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function historicalRecallLaneHeader() {
|
|
65
|
+
return 'Serving lane: explicit historical/session recall';
|
|
66
|
+
}
|
|
67
|
+
|
|
41
68
|
// ---------------------------------------------------------------------------
|
|
42
69
|
// Start MCP server
|
|
43
70
|
// ---------------------------------------------------------------------------
|
|
@@ -63,9 +90,93 @@ async function main() {
|
|
|
63
90
|
version: packageVersion,
|
|
64
91
|
});
|
|
65
92
|
|
|
93
|
+
server.tool(
|
|
94
|
+
'memory_recall',
|
|
95
|
+
'Explicit current-memory recall on the active curated memory corpus. Use this for current state / next-step lookup; use historical_recall for older session detail.',
|
|
96
|
+
{
|
|
97
|
+
query: z.string().min(1).describe('Search query (keyword or natural language)'),
|
|
98
|
+
limit: z.number().int().min(1).max(20).optional().describe('Max results (default 5)'),
|
|
99
|
+
mode: z.enum(['fts', 'hybrid', 'vector']).optional().describe('Recall mode: "fts" (keyword only), "hybrid" (default, FTS + vector), "vector" (vector only)'),
|
|
100
|
+
explain: z.boolean().optional().describe('Include per-result score breakdown (diagnostic)'),
|
|
101
|
+
activeScopeKey: z.string().optional().describe('Active curated memory scope key'),
|
|
102
|
+
activeScopePath: z.array(z.string()).optional().describe('Ordered curated scope path'),
|
|
103
|
+
},
|
|
104
|
+
async (params) => {
|
|
105
|
+
try {
|
|
106
|
+
const aquifer = getAquifer();
|
|
107
|
+
const recallOpts = {
|
|
108
|
+
limit: params.limit || 5,
|
|
109
|
+
activeScopeKey: params.activeScopeKey || undefined,
|
|
110
|
+
activeScopePath: params.activeScopePath || undefined,
|
|
111
|
+
};
|
|
112
|
+
if (params.mode) recallOpts.mode = params.mode;
|
|
113
|
+
|
|
114
|
+
const results = await aquifer.memoryRecall(params.query, recallOpts);
|
|
115
|
+
const text = [
|
|
116
|
+
memoryRecallLaneHeader(aquifer.getConfig ? aquifer.getConfig() : {}, params),
|
|
117
|
+
'',
|
|
118
|
+
formatResults(results, params.query, params.explain),
|
|
119
|
+
].join('\n');
|
|
120
|
+
return { content: [{ type: 'text', text }] };
|
|
121
|
+
} catch (err) {
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: 'text', text: `memory_recall error: ${err.message}` }],
|
|
124
|
+
isError: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
server.tool(
|
|
131
|
+
'historical_recall',
|
|
132
|
+
'Explicit historical/session recall over stored sessions and summaries. Use this for timeline, detail, and session context lookup; use evidence_recall only for audit/debug/provenance.',
|
|
133
|
+
{
|
|
134
|
+
query: z.string().min(1).describe('Search query (keyword or natural language)'),
|
|
135
|
+
limit: z.number().int().min(1).max(20).optional().describe('Max results (default 5)'),
|
|
136
|
+
agentId: z.string().optional().describe('Filter by agent ID'),
|
|
137
|
+
source: z.string().optional().describe('Filter by source (e.g., gateway, cc)'),
|
|
138
|
+
dateFrom: z.string().optional().describe('Start date YYYY-MM-DD'),
|
|
139
|
+
dateTo: z.string().optional().describe('End date YYYY-MM-DD'),
|
|
140
|
+
entities: z.array(z.string()).optional().describe('Entity names to match'),
|
|
141
|
+
entityMode: z.enum(['any', 'all']).optional().describe('"any" (default, boost) or "all" (only sessions with every entity)'),
|
|
142
|
+
mode: z.enum(['fts', 'hybrid', 'vector']).optional().describe('Recall mode: "fts" (keyword only, no embed needed), "hybrid" (default, FTS + vector), "vector" (vector only)'),
|
|
143
|
+
explain: z.boolean().optional().describe('Include per-result score breakdown (diagnostic)'),
|
|
144
|
+
},
|
|
145
|
+
async (params) => {
|
|
146
|
+
try {
|
|
147
|
+
const aquifer = getAquifer();
|
|
148
|
+
const recallOpts = {
|
|
149
|
+
limit: params.limit || 5,
|
|
150
|
+
agentId: params.agentId || undefined,
|
|
151
|
+
source: params.source || undefined,
|
|
152
|
+
dateFrom: params.dateFrom || undefined,
|
|
153
|
+
dateTo: params.dateTo || undefined,
|
|
154
|
+
};
|
|
155
|
+
if (params.entities && params.entities.length > 0) {
|
|
156
|
+
recallOpts.entities = params.entities;
|
|
157
|
+
recallOpts.entityMode = params.entityMode || 'any';
|
|
158
|
+
}
|
|
159
|
+
if (params.mode) recallOpts.mode = params.mode;
|
|
160
|
+
|
|
161
|
+
const results = await aquifer.historicalRecall(params.query, recallOpts);
|
|
162
|
+
const text = [
|
|
163
|
+
historicalRecallLaneHeader(),
|
|
164
|
+
'',
|
|
165
|
+
formatResults(results, params.query, params.explain),
|
|
166
|
+
].join('\n');
|
|
167
|
+
return { content: [{ type: 'text', text }] };
|
|
168
|
+
} catch (err) {
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: 'text', text: `historical_recall error: ${err.message}` }],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
);
|
|
176
|
+
|
|
66
177
|
server.tool(
|
|
67
178
|
'session_recall',
|
|
68
|
-
'
|
|
179
|
+
'Compatibility recall surface. In curated serving mode this routes to current memory; in legacy serving mode it routes to historical/session recall. Prefer memory_recall for current state and historical_recall for timeline/detail lookup.',
|
|
69
180
|
{
|
|
70
181
|
query: z.string().min(1).describe('Search query (keyword or natural language)'),
|
|
71
182
|
limit: z.number().int().min(1).max(20).optional().describe('Max results (default 5)'),
|
|
@@ -100,7 +211,11 @@ async function main() {
|
|
|
100
211
|
if (params.mode) recallOpts.mode = params.mode;
|
|
101
212
|
|
|
102
213
|
const results = await aquifer.recall(params.query, recallOpts);
|
|
103
|
-
const text =
|
|
214
|
+
const text = [
|
|
215
|
+
sessionRecallLaneHeader(aquifer.getConfig ? aquifer.getConfig() : {}),
|
|
216
|
+
'',
|
|
217
|
+
formatResults(results, params.query, params.explain),
|
|
218
|
+
].join('\n');
|
|
104
219
|
return { content: [{ type: 'text', text }] };
|
|
105
220
|
} catch (err) {
|
|
106
221
|
return {
|
|
@@ -146,7 +261,11 @@ async function main() {
|
|
|
146
261
|
if (params.allowUnsafeDebug) recallOpts.allowUnsafeDebug = true;
|
|
147
262
|
|
|
148
263
|
const results = await aquifer.evidenceRecall(params.query, recallOpts);
|
|
149
|
-
const text =
|
|
264
|
+
const text = [
|
|
265
|
+
'Serving lane: explicit legacy/evidence recall',
|
|
266
|
+
'',
|
|
267
|
+
formatResults(results, params.query, params.explain),
|
|
268
|
+
].join('\n');
|
|
150
269
|
return { content: [{ type: 'text', text }] };
|
|
151
270
|
} catch (err) {
|
|
152
271
|
return {
|
|
@@ -255,13 +374,16 @@ async function main() {
|
|
|
255
374
|
|
|
256
375
|
server.tool(
|
|
257
376
|
'memory_stats',
|
|
258
|
-
'Return storage statistics for the Aquifer memory store
|
|
377
|
+
'Return storage statistics for the Aquifer memory store, serving mode, current-memory record coverage, and session date range.',
|
|
259
378
|
{},
|
|
260
379
|
async () => {
|
|
261
380
|
try {
|
|
262
381
|
const aquifer = getAquifer();
|
|
263
382
|
const stats = await aquifer.getStats();
|
|
264
383
|
const lines = [
|
|
384
|
+
`Backend: ${stats.backendKind || 'unknown'} (${stats.backendProfile || 'unknown'})`,
|
|
385
|
+
`Serving mode: ${stats.serving?.mode || 'legacy'}`,
|
|
386
|
+
`Active scope: ${stats.serving?.activeScopePath?.join(' > ') || stats.serving?.activeScopeKey || 'none'}`,
|
|
265
387
|
`Sessions: ${stats.sessionTotal} total`,
|
|
266
388
|
];
|
|
267
389
|
for (const [status, count] of Object.entries(stats.sessions)) {
|
|
@@ -270,7 +392,23 @@ async function main() {
|
|
|
270
392
|
lines.push(`Summaries: ${stats.summaries}`);
|
|
271
393
|
lines.push(`Turn embeddings: ${stats.turnEmbeddings}`);
|
|
272
394
|
lines.push(`Entities: ${stats.entities}`);
|
|
395
|
+
if (stats.memoryRecords) {
|
|
396
|
+
lines.push(`Memory records: ${stats.memoryRecords.total} total (${stats.memoryRecords.active} active, ${stats.memoryRecords.visibleInRecall} recall-visible, ${stats.memoryRecords.visibleInBootstrap} bootstrap-visible)`);
|
|
397
|
+
if (stats.memoryRecords.latest) lines.push(`Memory record range: ${new Date(stats.memoryRecords.earliest).toISOString().slice(0, 10)} → ${new Date(stats.memoryRecords.latest).toISOString().slice(0, 10)}`);
|
|
398
|
+
}
|
|
399
|
+
if (stats.sessionFinalizations?.available) {
|
|
400
|
+
const statusText = Object.entries(stats.sessionFinalizations.statuses || {})
|
|
401
|
+
.map(([status, count]) => `${status}: ${count}`)
|
|
402
|
+
.join(', ') || 'none';
|
|
403
|
+
lines.push(`Session finalizations: ${stats.sessionFinalizations.total} total (${statusText})`);
|
|
404
|
+
if (stats.sessionFinalizations.latestFinalizedAt) {
|
|
405
|
+
lines.push(`Latest finalization: ${new Date(stats.sessionFinalizations.latestFinalizedAt).toISOString().slice(0, 10)}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
273
408
|
if (stats.earliest) lines.push(`Date range: ${new Date(stats.earliest).toISOString().slice(0, 10)} → ${new Date(stats.latest).toISOString().slice(0, 10)}`);
|
|
409
|
+
if ((stats.serving?.mode || 'legacy') !== 'curated') {
|
|
410
|
+
lines.push('Warning: legacy serving returns session/evidence material; configure curated serving with an active scope for current-memory answers.');
|
|
411
|
+
}
|
|
274
412
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
275
413
|
} catch (err) {
|
|
276
414
|
return {
|