@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
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
buildCheckpointCoverageFromView,
|
|
5
|
+
hashSnapshot,
|
|
6
|
+
promptSafeSynthesisInput,
|
|
7
|
+
stableJson,
|
|
8
|
+
} = require('../core/session-checkpoint-producer');
|
|
9
|
+
const { compactCurrentMemorySnapshot } = require('./codex-current-memory');
|
|
10
|
+
|
|
11
|
+
function positiveInt(value, fallback, max = 100000) {
|
|
12
|
+
const n = Number(value === undefined || value === null || value === '' ? fallback : value);
|
|
13
|
+
if (!Number.isFinite(n)) return fallback;
|
|
14
|
+
return Math.max(1, Math.min(max, Math.trunc(n)));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function buildActiveCheckpointScopeEnvelope(opts = {}) {
|
|
18
|
+
const envelope = opts.scopeEnvelope || opts.scope_envelope;
|
|
19
|
+
if (envelope && typeof envelope === 'object') return envelope;
|
|
20
|
+
const scopeKey = String(opts.activeScopeKey || opts.scopeKey || opts.scope_key || '').trim();
|
|
21
|
+
if (!scopeKey) {
|
|
22
|
+
throw new Error('active session checkpoint requires activeScopeKey or scopeKey');
|
|
23
|
+
}
|
|
24
|
+
const scopeKind = String(opts.activeScopeKind || opts.scopeKind || opts.scope_kind || 'project').trim();
|
|
25
|
+
const slotId = scopeKind === 'host_runtime' ? 'host' : (
|
|
26
|
+
['workspace', 'project', 'repo', 'task', 'session'].includes(scopeKind) ? scopeKind : 'target'
|
|
27
|
+
);
|
|
28
|
+
const promotable = !['session', 'task'].includes(slotId);
|
|
29
|
+
const slot = {
|
|
30
|
+
id: slotId,
|
|
31
|
+
slot: slotId,
|
|
32
|
+
scopeKind,
|
|
33
|
+
scopeKey,
|
|
34
|
+
label: scopeKey,
|
|
35
|
+
promotable,
|
|
36
|
+
allowedScopeKeys: promotable ? ['global', scopeKey] : ['global'],
|
|
37
|
+
};
|
|
38
|
+
if (!promotable) {
|
|
39
|
+
throw new Error(`active session checkpoint target scope is not promotable: ${scopeKind}`);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
policyVersion: 'scope_envelope_v1',
|
|
43
|
+
activeSlotId: slot.id,
|
|
44
|
+
activeScopeKey: scopeKey,
|
|
45
|
+
allowedScopeKeys: slot.allowedScopeKeys,
|
|
46
|
+
slots: [slot],
|
|
47
|
+
scopeById: { [slot.id]: slot },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function buildActiveSessionCheckpointInput(view = {}, opts = {}) {
|
|
52
|
+
if (!view || view.status !== 'ok') {
|
|
53
|
+
throw new Error(`active session checkpoint requires an ok transcript view; got ${view && view.status ? view.status : 'missing'}`);
|
|
54
|
+
}
|
|
55
|
+
const messageCount = Number.isFinite(Number(view.counts?.safeMessageCount))
|
|
56
|
+
? Number(view.counts.safeMessageCount)
|
|
57
|
+
: (Array.isArray(view.messages) ? view.messages.length : 0);
|
|
58
|
+
const userCount = Number(view.counts?.userCount || view.messages?.filter?.(m => m.role === 'user').length || 0);
|
|
59
|
+
const everyMessages = positiveInt(opts.checkpointEveryMessages || opts.everyMessages, 20, 1000);
|
|
60
|
+
const everyUserMessages = opts.checkpointEveryUserMessages || opts.everyUserMessages
|
|
61
|
+
? positiveInt(opts.checkpointEveryUserMessages || opts.everyUserMessages, 10, 1000)
|
|
62
|
+
: null;
|
|
63
|
+
const force = opts.force === true;
|
|
64
|
+
const due = force
|
|
65
|
+
|| messageCount >= everyMessages
|
|
66
|
+
|| (everyUserMessages !== null && userCount >= everyUserMessages);
|
|
67
|
+
const base = {
|
|
68
|
+
kind: 'codex_active_session_checkpoint_input_v1',
|
|
69
|
+
policyVersion: opts.policyVersion || 'codex_active_session_checkpoint_v1',
|
|
70
|
+
sourceOfTruth: 'codex_sanitized_live_transcript_view',
|
|
71
|
+
triggerKind: opts.triggerKind || 'message_count',
|
|
72
|
+
promotion: {
|
|
73
|
+
default: 'checkpoint_proposal_only',
|
|
74
|
+
requires: 'handoff_or_operator_review',
|
|
75
|
+
},
|
|
76
|
+
guards: {
|
|
77
|
+
checkpointIsProcessMaterial: true,
|
|
78
|
+
activeMemoryCommitExcluded: true,
|
|
79
|
+
dbWriteExcluded: true,
|
|
80
|
+
rawToolOutputExcluded: true,
|
|
81
|
+
debugIdsExcluded: true,
|
|
82
|
+
},
|
|
83
|
+
threshold: {
|
|
84
|
+
everyMessages,
|
|
85
|
+
everyUserMessages,
|
|
86
|
+
messageCount,
|
|
87
|
+
userCount,
|
|
88
|
+
due,
|
|
89
|
+
},
|
|
90
|
+
targetScope: {
|
|
91
|
+
activeScopeKey: opts.activeScopeKey || opts.scopeKey || null,
|
|
92
|
+
activeScopePath: opts.activeScopePath || null,
|
|
93
|
+
},
|
|
94
|
+
scopeEnvelope: buildActiveCheckpointScopeEnvelope(opts),
|
|
95
|
+
coverage: buildCheckpointCoverageFromView(view, opts),
|
|
96
|
+
transcript: {
|
|
97
|
+
sessionId: view.sessionId || null,
|
|
98
|
+
charCount: view.charCount ?? String(view.text || '').length,
|
|
99
|
+
fullCharCount: view.fullCharCount ?? view.counts?.fullCharCount ?? view.charCount ?? String(view.text || '').length,
|
|
100
|
+
approxPromptTokens: view.approxPromptTokens || Math.ceil(String(view.text || '').length / 3),
|
|
101
|
+
fullApproxPromptTokens: view.fullApproxPromptTokens || view.counts?.fullApproxPromptTokens || null,
|
|
102
|
+
truncated: Boolean(view.truncated || view.transcriptWindow?.truncated),
|
|
103
|
+
transcriptWindow: view.transcriptWindow || null,
|
|
104
|
+
text: view.text || '',
|
|
105
|
+
},
|
|
106
|
+
currentMemory: compactCurrentMemorySnapshot(opts.currentMemory || null, opts),
|
|
107
|
+
};
|
|
108
|
+
return {
|
|
109
|
+
...base,
|
|
110
|
+
inputHash: hashSnapshot(base),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildActiveSessionCheckpointPrompt(checkpointInput = {}, opts = {}) {
|
|
115
|
+
if (!checkpointInput || checkpointInput.kind !== 'codex_active_session_checkpoint_input_v1') {
|
|
116
|
+
throw new Error('buildActiveSessionCheckpointPrompt requires an active session checkpoint input');
|
|
117
|
+
}
|
|
118
|
+
const promptInput = promptSafeSynthesisInput(checkpointInput);
|
|
119
|
+
const maxFacts = Math.max(1, Math.min(24, opts.maxFacts || 8));
|
|
120
|
+
return [
|
|
121
|
+
'You are producing an Aquifer active-session checkpoint proposal for Codex.',
|
|
122
|
+
'Use only the <active_checkpoint_input> block. Do not use hidden tool output, injected context, or debug material.',
|
|
123
|
+
'This checkpoint is process material for later handoff. It is not active current memory and must not be treated as final truth.',
|
|
124
|
+
'Return compact JSON with this shape:',
|
|
125
|
+
'{"summaryText":"...","structuredSummary":{"facts":[],"decisions":[],"open_loops":[],"preferences":[],"constraints":[],"conclusions":[],"entity_notes":[],"states":[]},"coverage":{"coordinateSystem":"codex_sanitized_view_v1","coveredUntilMessageIndex":0,"coveredUntilChar":0}}',
|
|
126
|
+
`Keep facts/decisions/open_loops concrete and scoped. Use at most ${maxFacts} facts.`,
|
|
127
|
+
'Preserve the coverage object so a later handoff can skip the already-covered transcript prefix.',
|
|
128
|
+
'',
|
|
129
|
+
'<active_checkpoint_input>',
|
|
130
|
+
stableJson(promptInput),
|
|
131
|
+
'</active_checkpoint_input>',
|
|
132
|
+
].join('\n');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function prepareActiveSessionCheckpoint(aquifer, opts = {}, deps = {}) {
|
|
136
|
+
const materializeRecoveryTranscriptView = deps.materializeRecoveryTranscriptView;
|
|
137
|
+
const resolveCurrentMemoryForFinalization = deps.resolveCurrentMemoryForFinalization || (async () => null);
|
|
138
|
+
const view = opts.view || (opts.filePath && typeof materializeRecoveryTranscriptView === 'function'
|
|
139
|
+
? materializeRecoveryTranscriptView({
|
|
140
|
+
filePath: opts.filePath,
|
|
141
|
+
sessionId: opts.sessionId,
|
|
142
|
+
}, {
|
|
143
|
+
...opts,
|
|
144
|
+
maxRecoveryBytes: opts.maxCheckpointBytes ?? opts.maxRecoveryBytes,
|
|
145
|
+
maxRecoveryMessages: opts.maxCheckpointMessages ?? opts.maxRecoveryMessages,
|
|
146
|
+
maxRecoveryChars: opts.maxCheckpointChars ?? opts.maxRecoveryChars,
|
|
147
|
+
maxRecoveryPromptTokens: opts.maxCheckpointPromptTokens ?? opts.maxRecoveryPromptTokens,
|
|
148
|
+
})
|
|
149
|
+
: null);
|
|
150
|
+
if (!view || view.status !== 'ok') {
|
|
151
|
+
return {
|
|
152
|
+
status: view?.status || 'missing_view',
|
|
153
|
+
reason: view?.reason || null,
|
|
154
|
+
view,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const currentMemory = await resolveCurrentMemoryForFinalization(aquifer, opts);
|
|
158
|
+
const checkpointInput = buildActiveSessionCheckpointInput(view, {
|
|
159
|
+
...opts,
|
|
160
|
+
currentMemory,
|
|
161
|
+
});
|
|
162
|
+
if (!checkpointInput.threshold.due) {
|
|
163
|
+
return {
|
|
164
|
+
status: 'not_ready',
|
|
165
|
+
due: false,
|
|
166
|
+
checkpointInput,
|
|
167
|
+
view,
|
|
168
|
+
currentMemory,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
status: 'needs_agent_checkpoint',
|
|
173
|
+
due: true,
|
|
174
|
+
outputSchemaVersion: 'codex_active_session_checkpoint_v1',
|
|
175
|
+
checkpointInput,
|
|
176
|
+
view,
|
|
177
|
+
currentMemory,
|
|
178
|
+
prompt: buildActiveSessionCheckpointPrompt(checkpointInput, opts),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = {
|
|
183
|
+
buildActiveSessionCheckpointInput,
|
|
184
|
+
buildActiveSessionCheckpointPrompt,
|
|
185
|
+
prepareActiveSessionCheckpoint,
|
|
186
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function compactCurrentMemoryRow(row = {}) {
|
|
4
|
+
const payload = row.payload && typeof row.payload === 'object' ? row.payload : {};
|
|
5
|
+
const confidence = payload.confidence || payload.currentMemoryConfidence || null;
|
|
6
|
+
return {
|
|
7
|
+
memoryType: row.memoryType || row.memory_type || 'memory',
|
|
8
|
+
canonicalKey: row.canonicalKey || row.canonical_key || null,
|
|
9
|
+
scopeKey: row.scopeKey || row.scope_key || null,
|
|
10
|
+
summary: String(row.summary || row.title || '').replace(/\s+/g, ' ').trim(),
|
|
11
|
+
authority: row.authority || null,
|
|
12
|
+
confidence,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function currentMemoryRows(currentMemory = null) {
|
|
17
|
+
return Array.isArray(currentMemory?.memories)
|
|
18
|
+
? currentMemory.memories
|
|
19
|
+
: (Array.isArray(currentMemory?.items) ? currentMemory.items : []);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function currentMemoryLimit(opts = {}) {
|
|
23
|
+
return Math.max(0, Math.min(20, opts.maxCurrentMemoryItems || opts.currentMemoryLimit || 12));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function formatCurrentMemoryPromptBlock(currentMemory = null, opts = {}) {
|
|
27
|
+
const maxItems = currentMemoryLimit(opts);
|
|
28
|
+
const meta = currentMemory && currentMemory.meta ? currentMemory.meta : {};
|
|
29
|
+
const rows = currentMemoryRows(currentMemory);
|
|
30
|
+
const compactRows = rows.map(compactCurrentMemoryRow).filter(row => row.summary).slice(0, maxItems);
|
|
31
|
+
const attrs = [
|
|
32
|
+
`source="${meta.source || 'memory_records'}"`,
|
|
33
|
+
`serving_contract="${meta.servingContract || meta.serving_contract || 'current_memory_v1'}"`,
|
|
34
|
+
`count="${compactRows.length}"`,
|
|
35
|
+
`truncated="${Boolean(meta.truncated || rows.length > compactRows.length)}"`,
|
|
36
|
+
`degraded="${Boolean(meta.degraded || currentMemory?.error)}"`,
|
|
37
|
+
];
|
|
38
|
+
const lines = compactRows.map(row => {
|
|
39
|
+
const scope = row.scopeKey ? ` scope=${row.scopeKey}` : '';
|
|
40
|
+
const authority = row.authority ? ` authority=${row.authority}` : '';
|
|
41
|
+
const confidence = row.confidence ? ` confidence=${row.confidence}` : '';
|
|
42
|
+
return `- ${row.memoryType}${scope}${authority}${confidence}: ${row.summary}`;
|
|
43
|
+
});
|
|
44
|
+
if (currentMemory && currentMemory.error && lines.length === 0) {
|
|
45
|
+
lines.push(`- degraded: ${String(currentMemory.error).replace(/\s+/g, ' ').trim()}`);
|
|
46
|
+
}
|
|
47
|
+
if (lines.length === 0) lines.push('- none');
|
|
48
|
+
return [
|
|
49
|
+
`<current_memory ${attrs.join(' ')}>`,
|
|
50
|
+
...lines,
|
|
51
|
+
'</current_memory>',
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function compactCurrentMemorySnapshot(currentMemory = null, opts = {}) {
|
|
56
|
+
const maxItems = currentMemoryLimit(opts);
|
|
57
|
+
const meta = currentMemory && currentMemory.meta ? currentMemory.meta : {};
|
|
58
|
+
const rows = currentMemoryRows(currentMemory);
|
|
59
|
+
return {
|
|
60
|
+
memories: rows.map(compactCurrentMemoryRow).filter(row => row.summary).slice(0, maxItems),
|
|
61
|
+
meta: {
|
|
62
|
+
source: meta.source || 'memory_records',
|
|
63
|
+
servingContract: meta.servingContract || meta.serving_contract || 'current_memory_v1',
|
|
64
|
+
count: Math.min(rows.length, maxItems),
|
|
65
|
+
truncated: Boolean(meta.truncated || rows.length > maxItems),
|
|
66
|
+
degraded: Boolean(meta.degraded || currentMemory?.error),
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function resolveCurrentMemoryForFinalization(aquifer, opts = {}) {
|
|
72
|
+
if (opts.includeCurrentMemory === false) return null;
|
|
73
|
+
if (opts.currentMemory !== undefined) return opts.currentMemory;
|
|
74
|
+
const currentFn = aquifer?.memory?.current || aquifer?.memory?.listCurrentMemory;
|
|
75
|
+
if (typeof currentFn !== 'function') return null;
|
|
76
|
+
const limit = Math.max(1, Math.min(20, opts.currentMemoryLimit || opts.maxCurrentMemoryItems || 12));
|
|
77
|
+
try {
|
|
78
|
+
return await currentFn.call(aquifer.memory, {
|
|
79
|
+
tenantId: opts.tenantId,
|
|
80
|
+
activeScopeKey: opts.activeScopeKey || opts.scopeKey,
|
|
81
|
+
activeScopePath: opts.activeScopePath,
|
|
82
|
+
scopeId: opts.scopeId,
|
|
83
|
+
asOf: opts.asOf,
|
|
84
|
+
limit,
|
|
85
|
+
});
|
|
86
|
+
} catch (err) {
|
|
87
|
+
return {
|
|
88
|
+
memories: [],
|
|
89
|
+
meta: {
|
|
90
|
+
source: 'memory_records',
|
|
91
|
+
servingContract: 'current_memory_v1',
|
|
92
|
+
count: 0,
|
|
93
|
+
truncated: false,
|
|
94
|
+
degraded: true,
|
|
95
|
+
},
|
|
96
|
+
error: err.message,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
compactCurrentMemoryRow,
|
|
103
|
+
formatCurrentMemoryPromptBlock,
|
|
104
|
+
compactCurrentMemorySnapshot,
|
|
105
|
+
resolveCurrentMemoryForFinalization,
|
|
106
|
+
};
|