@shadowforge0/aquifer-memory 1.6.0 → 1.8.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.
Files changed (44) hide show
  1. package/.env.example +8 -0
  2. package/README.md +72 -0
  3. package/README_CN.md +17 -0
  4. package/README_TW.md +4 -0
  5. package/aquifer.config.example.json +19 -0
  6. package/consumers/cli.js +259 -12
  7. package/consumers/codex-active-checkpoint.js +186 -0
  8. package/consumers/codex-current-memory.js +106 -0
  9. package/consumers/codex-handoff.js +551 -6
  10. package/consumers/codex.js +209 -25
  11. package/consumers/mcp.js +144 -6
  12. package/consumers/shared/config.js +60 -1
  13. package/consumers/shared/factory.js +10 -3
  14. package/core/aquifer.js +357 -838
  15. package/core/backends/capabilities.js +89 -0
  16. package/core/backends/local.js +430 -0
  17. package/core/legacy-bootstrap.js +140 -0
  18. package/core/mcp-manifest.js +66 -2
  19. package/core/memory-bootstrap.js +20 -8
  20. package/core/memory-consolidation.js +365 -11
  21. package/core/memory-promotion.js +157 -26
  22. package/core/memory-recall.js +341 -22
  23. package/core/memory-records.js +347 -11
  24. package/core/memory-serving.js +132 -0
  25. package/core/postgres-migrations.js +533 -0
  26. package/core/public-session-filter.js +40 -0
  27. package/core/recall-runtime.js +115 -0
  28. package/core/scope-attribution.js +279 -0
  29. package/core/session-checkpoint-producer.js +412 -0
  30. package/core/session-checkpoints.js +432 -0
  31. package/core/session-finalization.js +98 -2
  32. package/core/storage-checkpoints.js +546 -0
  33. package/core/storage.js +121 -8
  34. package/docs/getting-started.md +6 -0
  35. package/docs/setup.md +66 -3
  36. package/package.json +8 -4
  37. package/schema/014-v1-checkpoint-runs.sql +349 -0
  38. package/schema/015-v1-evidence-items.sql +92 -0
  39. package/schema/016-v1-evidence-ref-multi-item.sql +19 -0
  40. package/schema/017-v1-memory-record-embeddings.sql +25 -0
  41. package/schema/018-v1-finalization-candidate-envelope.sql +39 -0
  42. package/scripts/codex-checkpoint-commands.js +464 -0
  43. package/scripts/codex-checkpoint-runtime.js +520 -0
  44. package/scripts/codex-recovery.js +246 -1
@@ -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
+ };