@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/package.json
CHANGED
|
@@ -1,38 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shadowforge0/aquifer-memory",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "PG-native long-term memory for AI agents. Turn-level embedding, hybrid RRF ranking, optional knowledge graph. MCP server, CLI, and library API.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
7
7
|
"index.js",
|
|
8
|
+
"README_TW.md",
|
|
9
|
+
"README_CN.md",
|
|
10
|
+
"aquifer.config.example.json",
|
|
8
11
|
"core/",
|
|
9
12
|
"pipeline/",
|
|
10
13
|
"schema/",
|
|
11
|
-
"consumers/",
|
|
12
|
-
"consumers/
|
|
14
|
+
"consumers/cli.js",
|
|
15
|
+
"consumers/mcp.js",
|
|
16
|
+
"consumers/claude-code.js",
|
|
17
|
+
"consumers/codex.js",
|
|
18
|
+
"consumers/codex-handoff.js",
|
|
19
|
+
"consumers/openclaw-plugin.js",
|
|
20
|
+
"consumers/opencode.js",
|
|
21
|
+
"consumers/shared/",
|
|
13
22
|
"consumers/default/",
|
|
14
23
|
"consumers/openclaw-ext/",
|
|
15
|
-
"docs/",
|
|
24
|
+
"docs/getting-started.md",
|
|
25
|
+
"docs/postprocess-contract.md",
|
|
26
|
+
"docs/setup.md",
|
|
27
|
+
".env.example",
|
|
16
28
|
"scripts/backfill-canonical-key.js",
|
|
17
29
|
"scripts/diagnose-fts-zh.js",
|
|
18
30
|
"scripts/diagnose-vector.js",
|
|
19
|
-
"scripts/
|
|
20
|
-
"scripts/drop-insights.sql",
|
|
31
|
+
"scripts/codex-recovery.js",
|
|
21
32
|
"scripts/extract-insights-from-recent-sessions.js",
|
|
22
33
|
"scripts/find-dburl-hints.js",
|
|
23
|
-
"scripts/install-openclaw.sh",
|
|
24
34
|
"scripts/smoke.mjs"
|
|
25
35
|
],
|
|
26
36
|
"bin": {
|
|
27
|
-
"aquifer": "
|
|
37
|
+
"aquifer": "consumers/cli.js"
|
|
28
38
|
},
|
|
29
39
|
"exports": {
|
|
30
40
|
".": "./index.js",
|
|
31
41
|
"./consumers/mcp": "./consumers/mcp.js",
|
|
32
42
|
"./consumers/openclaw-plugin": "./consumers/openclaw-plugin.js",
|
|
33
43
|
"./consumers/opencode": "./consumers/opencode.js",
|
|
44
|
+
"./consumers/codex-handoff": "./consumers/codex-handoff.js",
|
|
34
45
|
"./consumers/claude-code": "./consumers/claude-code.js",
|
|
35
|
-
"./consumers/
|
|
46
|
+
"./consumers/codex": "./consumers/codex.js",
|
|
36
47
|
"./consumers/default": "./consumers/default/index.js",
|
|
37
48
|
"./consumers/openclaw-ext": "./consumers/openclaw-ext/index.js",
|
|
38
49
|
"./consumers/shared/config": "./consumers/shared/config.js",
|
|
@@ -41,6 +52,7 @@
|
|
|
41
52
|
"./consumers/shared/normalize": "./consumers/shared/normalize.js",
|
|
42
53
|
"./consumers/shared/ingest": "./consumers/shared/ingest.js",
|
|
43
54
|
"./consumers/shared/recall-format": "./consumers/shared/recall-format.js",
|
|
55
|
+
"./consumers/shared/summary-parser": "./consumers/shared/summary-parser.js",
|
|
44
56
|
"./consumers/shared/llm-autodetect": "./consumers/shared/llm-autodetect.js"
|
|
45
57
|
},
|
|
46
58
|
"repository": {
|
|
@@ -55,8 +67,10 @@
|
|
|
55
67
|
"scripts": {
|
|
56
68
|
"test": "node --test test/*.test.js",
|
|
57
69
|
"test:integration": "node --test test/integration.test.js",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
70
|
+
"test:release:package": "node --test test/package-surface.test.js test/mcp-manifest.test.js",
|
|
71
|
+
"test:release:db": "node -e \"if (!process.env.AQUIFER_TEST_DB_URL) { console.error('AQUIFER_TEST_DB_URL is required for test:release:db'); process.exit(1); }\" && node --test test/consumer-mcp.integration.test.js test/consumer-cli.integration.test.js test/codex-finalization-serving.integration.test.js",
|
|
72
|
+
"lint": "eslint index.js core/*.js consumers/cli.js consumers/mcp.js consumers/claude-code.js consumers/codex.js consumers/codex-handoff.js consumers/openclaw-plugin.js consumers/opencode.js consumers/shared/*.js consumers/default/*.js consumers/default/prompts/*.js consumers/openclaw-ext/*.js pipeline/*.js pipeline/consolidation/*.js scripts/*.js test/*.js",
|
|
73
|
+
"hooks:install": "git config core.hooksPath .githooks"
|
|
60
74
|
},
|
|
61
75
|
"dependencies": {
|
|
62
76
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Codex CLI adapter — maps Codex rollout JSONL entries into the same
|
|
5
|
+
* intermediate shape used by the shared normalize pipeline.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { MAX_NARRATION_CHARS } = require('../constants');
|
|
9
|
+
const { parseTimestamp } = require('../timestamp');
|
|
10
|
+
|
|
11
|
+
function extractCodexText(content) {
|
|
12
|
+
if (!Array.isArray(content)) return '';
|
|
13
|
+
return content
|
|
14
|
+
.filter(item => item && item.type === 'output_text' && typeof item.text === 'string')
|
|
15
|
+
.map(item => item.text.trim())
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.join('\n\n');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function codexToolName(entry) {
|
|
21
|
+
const payload = entry?.payload || {};
|
|
22
|
+
if (entry?.type === 'response_item' && payload.type === 'function_call') {
|
|
23
|
+
return payload.name || payload.call_id || 'function_call';
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function nextCodexEntryIsTool(rawEntries, idx) {
|
|
29
|
+
for (let j = idx + 1; j < rawEntries.length; j++) {
|
|
30
|
+
const toolName = codexToolName(rawEntries[j]);
|
|
31
|
+
if (toolName) return true;
|
|
32
|
+
const payload = rawEntries[j]?.payload || {};
|
|
33
|
+
if (rawEntries[j]?.type === 'event_msg' && payload.type === 'user_message') return false;
|
|
34
|
+
if (
|
|
35
|
+
rawEntries[j]?.type === 'response_item'
|
|
36
|
+
&& payload.type === 'message'
|
|
37
|
+
&& payload.role === 'assistant'
|
|
38
|
+
&& payload.phase === 'final_answer'
|
|
39
|
+
) return false;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
name: 'codex',
|
|
46
|
+
|
|
47
|
+
detect(entry) {
|
|
48
|
+
return entry?.type === 'session_meta'
|
|
49
|
+
|| entry?.type === 'turn_context'
|
|
50
|
+
|| entry?.type === 'event_msg'
|
|
51
|
+
|| entry?.type === 'response_item';
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
toIntermediate(entry, ctx) {
|
|
55
|
+
const { idx, rawEntries } = ctx;
|
|
56
|
+
const payload = entry?.payload || {};
|
|
57
|
+
|
|
58
|
+
if (entry?.type === 'event_msg' && payload.type === 'user_message') {
|
|
59
|
+
const text = String(payload.message || '').trim();
|
|
60
|
+
return {
|
|
61
|
+
idx,
|
|
62
|
+
role: 'user',
|
|
63
|
+
text,
|
|
64
|
+
timestamp: parseTimestamp(entry),
|
|
65
|
+
toolNames: [],
|
|
66
|
+
commandName: null,
|
|
67
|
+
isInterrupt: false,
|
|
68
|
+
adapterSkip: null,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const toolName = codexToolName(entry);
|
|
73
|
+
if (toolName) return { idx, toolNames: [toolName], adapterSkip: 'toolOnly' };
|
|
74
|
+
|
|
75
|
+
if (entry?.type === 'response_item' && payload.type === 'message' && payload.role === 'assistant') {
|
|
76
|
+
const text = extractCodexText(payload.content);
|
|
77
|
+
const toolNames = [];
|
|
78
|
+
if (!text) return { idx, toolNames, adapterSkip: 'empty' };
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
payload.phase === 'commentary'
|
|
82
|
+
&& text.length < MAX_NARRATION_CHARS
|
|
83
|
+
&& nextCodexEntryIsTool(rawEntries, idx)
|
|
84
|
+
) {
|
|
85
|
+
return { idx, toolNames, adapterSkip: 'narration' };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
idx,
|
|
90
|
+
role: 'assistant',
|
|
91
|
+
text,
|
|
92
|
+
timestamp: parseTimestamp(entry),
|
|
93
|
+
toolNames,
|
|
94
|
+
commandName: null,
|
|
95
|
+
isInterrupt: false,
|
|
96
|
+
adapterSkip: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { idx, toolNames: [], adapterSkip: 'nonMessage' };
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
routinePatterns: [],
|
|
104
|
+
|
|
105
|
+
skipCommands: [],
|
|
106
|
+
};
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const gatewayAdapter = require('./adapters/gateway');
|
|
4
4
|
const claudeCodeAdapter = require('./adapters/claude-code');
|
|
5
|
+
const codexAdapter = require('./adapters/codex');
|
|
5
6
|
|
|
6
|
-
const ADAPTERS = [gatewayAdapter, claudeCodeAdapter];
|
|
7
|
+
const ADAPTERS = [gatewayAdapter, claudeCodeAdapter, codexAdapter];
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Auto-detect the client type from raw session entries.
|
|
10
11
|
* Samples the first 5 entries and picks the adapter with the most matches.
|
|
11
12
|
* @param {any[]} rawEntries
|
|
12
|
-
* @returns {string} Client name ('gateway' | 'claude-code')
|
|
13
|
+
* @returns {string} Client name ('gateway' | 'claude-code' | 'codex')
|
|
13
14
|
* @throws {Error} If entries are empty, no adapter matches, or detection is ambiguous
|
|
14
15
|
*/
|
|
15
16
|
function detectClient(rawEntries) {
|
package/schema/001-base.sql
CHANGED
|
@@ -160,6 +160,9 @@ CREATE TABLE IF NOT EXISTS ${schema}.session_summaries (
|
|
|
160
160
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
161
161
|
);
|
|
162
162
|
|
|
163
|
+
ALTER TABLE ${schema}.session_summaries
|
|
164
|
+
ALTER COLUMN model DROP NOT NULL;
|
|
165
|
+
|
|
163
166
|
-- Cleanup legacy segment-era schema artifacts so migrate() converges old installs.
|
|
164
167
|
-- Wrapped because the implicit sequence on session_segments can be referenced from
|
|
165
168
|
-- other schemas (e.g. bench/staging created via CREATE TABLE LIKE), which would
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
-- Aquifer v1 curated-memory foundation
|
|
2
|
+
-- Requires: 001-base.sql applied first
|
|
3
|
+
-- Usage: replace ${schema} with actual schema name
|
|
4
|
+
--
|
|
5
|
+
-- This migration is additive. It creates a sidecar curated-memory plane while
|
|
6
|
+
-- leaving the existing 1.5.x sessions/session_summaries/turn_embeddings path
|
|
7
|
+
-- untouched. Legacy session tables remain evidence/source material until a
|
|
8
|
+
-- later serving-mode switch.
|
|
9
|
+
|
|
10
|
+
-- =========================================================================
|
|
11
|
+
-- Scopes: explicit applicability boundary for curated memory
|
|
12
|
+
-- =========================================================================
|
|
13
|
+
CREATE TABLE IF NOT EXISTS ${schema}.scopes (
|
|
14
|
+
id BIGSERIAL PRIMARY KEY,
|
|
15
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
16
|
+
scope_kind TEXT NOT NULL
|
|
17
|
+
CHECK (scope_kind IN (
|
|
18
|
+
'global','user','workspace','project','event','session',
|
|
19
|
+
'host_runtime','assistant_instance'
|
|
20
|
+
)),
|
|
21
|
+
scope_key TEXT NOT NULL CHECK (btrim(scope_key) <> ''),
|
|
22
|
+
parent_scope_id BIGINT REFERENCES ${schema}.scopes(id) ON DELETE SET NULL,
|
|
23
|
+
inheritance_mode TEXT NOT NULL DEFAULT 'defaultable'
|
|
24
|
+
CHECK (inheritance_mode IN ('exclusive','defaultable','additive','non_inheritable')),
|
|
25
|
+
context_key TEXT,
|
|
26
|
+
topic_key TEXT,
|
|
27
|
+
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
28
|
+
active_from TIMESTAMPTZ,
|
|
29
|
+
active_to TIMESTAMPTZ,
|
|
30
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
31
|
+
CHECK (active_to IS NULL OR active_from IS NULL OR active_to > active_from)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_scopes_tenant_kind_key
|
|
35
|
+
ON ${schema}.scopes (tenant_id, scope_kind, scope_key);
|
|
36
|
+
|
|
37
|
+
CREATE INDEX IF NOT EXISTS idx_scopes_parent
|
|
38
|
+
ON ${schema}.scopes (parent_scope_id)
|
|
39
|
+
WHERE parent_scope_id IS NOT NULL;
|
|
40
|
+
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_scopes_context_topic
|
|
42
|
+
ON ${schema}.scopes (tenant_id, context_key, topic_key)
|
|
43
|
+
WHERE context_key IS NOT NULL OR topic_key IS NOT NULL;
|
|
44
|
+
|
|
45
|
+
COMMENT ON TABLE ${schema}.scopes IS
|
|
46
|
+
'v1 curated-memory scope tree. Scope controls applicability; default is not global promotion.';
|
|
47
|
+
|
|
48
|
+
-- =========================================================================
|
|
49
|
+
-- Versions: policy/model/schema provenance for deterministic replay
|
|
50
|
+
-- =========================================================================
|
|
51
|
+
CREATE TABLE IF NOT EXISTS ${schema}.versions (
|
|
52
|
+
id BIGSERIAL PRIMARY KEY,
|
|
53
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
54
|
+
version_kind TEXT NOT NULL
|
|
55
|
+
CHECK (version_kind IN (
|
|
56
|
+
'schema','normalizer','extractor','promotion_policy',
|
|
57
|
+
'embedding_model','ranker','bootstrap_policy','recall_policy','other'
|
|
58
|
+
)),
|
|
59
|
+
version TEXT NOT NULL CHECK (btrim(version) <> ''),
|
|
60
|
+
version_hash TEXT NOT NULL CHECK (btrim(version_hash) <> ''),
|
|
61
|
+
active BOOLEAN NOT NULL DEFAULT false,
|
|
62
|
+
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
63
|
+
released_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
64
|
+
retired_at TIMESTAMPTZ,
|
|
65
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
66
|
+
CHECK (retired_at IS NULL OR retired_at >= released_at)
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_versions_tenant_kind_hash
|
|
70
|
+
ON ${schema}.versions (tenant_id, version_kind, version_hash);
|
|
71
|
+
|
|
72
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_versions_one_active_per_kind
|
|
73
|
+
ON ${schema}.versions (tenant_id, version_kind)
|
|
74
|
+
WHERE active;
|
|
75
|
+
|
|
76
|
+
COMMENT ON TABLE ${schema}.versions IS
|
|
77
|
+
'v1 replay metadata for schema, normalizer, extractor, promotion, embedding, ranker, and bootstrap policies.';
|
|
78
|
+
|
|
79
|
+
-- =========================================================================
|
|
80
|
+
-- Memory records: runtime-visible curated memory
|
|
81
|
+
-- =========================================================================
|
|
82
|
+
CREATE TABLE IF NOT EXISTS ${schema}.memory_records (
|
|
83
|
+
id BIGSERIAL PRIMARY KEY,
|
|
84
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
85
|
+
memory_type TEXT NOT NULL
|
|
86
|
+
CHECK (memory_type IN (
|
|
87
|
+
'fact','state','decision','preference','constraint',
|
|
88
|
+
'entity_note','open_loop','conclusion'
|
|
89
|
+
)),
|
|
90
|
+
canonical_key TEXT NOT NULL CHECK (btrim(canonical_key) <> ''),
|
|
91
|
+
scope_id BIGINT NOT NULL REFERENCES ${schema}.scopes(id) ON DELETE RESTRICT,
|
|
92
|
+
context_key TEXT,
|
|
93
|
+
topic_key TEXT,
|
|
94
|
+
title TEXT,
|
|
95
|
+
summary TEXT NOT NULL DEFAULT '',
|
|
96
|
+
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
97
|
+
status TEXT NOT NULL DEFAULT 'candidate'
|
|
98
|
+
CHECK (status IN (
|
|
99
|
+
'candidate','active','stale','superseded','revoked',
|
|
100
|
+
'tombstoned','quarantined','archived'
|
|
101
|
+
)),
|
|
102
|
+
authority TEXT NOT NULL DEFAULT 'llm_inference'
|
|
103
|
+
CHECK (authority IN (
|
|
104
|
+
'user_explicit','executable_evidence','verified_summary',
|
|
105
|
+
'llm_inference','raw_transcript','manual','system'
|
|
106
|
+
)),
|
|
107
|
+
accepted_at TIMESTAMPTZ,
|
|
108
|
+
valid_from TIMESTAMPTZ,
|
|
109
|
+
valid_to TIMESTAMPTZ,
|
|
110
|
+
stale_after TIMESTAMPTZ,
|
|
111
|
+
superseded_by BIGINT REFERENCES ${schema}.memory_records(id) ON DELETE SET NULL,
|
|
112
|
+
version_id BIGINT REFERENCES ${schema}.versions(id) ON DELETE SET NULL,
|
|
113
|
+
visible_in_bootstrap BOOLEAN NOT NULL DEFAULT false,
|
|
114
|
+
visible_in_recall BOOLEAN NOT NULL DEFAULT false,
|
|
115
|
+
rank_features JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
116
|
+
search_tsv TSVECTOR,
|
|
117
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
118
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
119
|
+
CHECK (valid_to IS NULL OR valid_from IS NULL OR valid_to > valid_from),
|
|
120
|
+
CHECK (
|
|
121
|
+
status = 'active'
|
|
122
|
+
OR (visible_in_bootstrap = false AND visible_in_recall = false)
|
|
123
|
+
)
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_records_active_canonical
|
|
127
|
+
ON ${schema}.memory_records (tenant_id, canonical_key)
|
|
128
|
+
WHERE status = 'active';
|
|
129
|
+
|
|
130
|
+
CREATE INDEX IF NOT EXISTS idx_memory_records_scope_bootstrap
|
|
131
|
+
ON ${schema}.memory_records (tenant_id, scope_id, status, visible_in_bootstrap, accepted_at DESC, id)
|
|
132
|
+
WHERE visible_in_bootstrap;
|
|
133
|
+
|
|
134
|
+
CREATE INDEX IF NOT EXISTS idx_memory_records_scope_recall
|
|
135
|
+
ON ${schema}.memory_records (tenant_id, scope_id, status, visible_in_recall, context_key, topic_key)
|
|
136
|
+
WHERE visible_in_recall;
|
|
137
|
+
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_memory_records_superseded_by
|
|
139
|
+
ON ${schema}.memory_records (superseded_by)
|
|
140
|
+
WHERE superseded_by IS NOT NULL;
|
|
141
|
+
|
|
142
|
+
CREATE INDEX IF NOT EXISTS idx_memory_records_search_tsv
|
|
143
|
+
ON ${schema}.memory_records USING GIN (search_tsv)
|
|
144
|
+
WHERE visible_in_recall;
|
|
145
|
+
|
|
146
|
+
CREATE OR REPLACE FUNCTION ${schema}.memory_records_search_tsv_update()
|
|
147
|
+
RETURNS trigger
|
|
148
|
+
LANGUAGE plpgsql
|
|
149
|
+
AS $$
|
|
150
|
+
BEGIN
|
|
151
|
+
NEW.search_tsv :=
|
|
152
|
+
to_tsvector('simple',
|
|
153
|
+
COALESCE(NEW.title, '') || ' ' ||
|
|
154
|
+
COALESCE(NEW.summary, '') || ' ' ||
|
|
155
|
+
COALESCE(NEW.context_key, '') || ' ' ||
|
|
156
|
+
COALESCE(NEW.topic_key, '')
|
|
157
|
+
);
|
|
158
|
+
NEW.updated_at := now();
|
|
159
|
+
RETURN NEW;
|
|
160
|
+
END;
|
|
161
|
+
$$;
|
|
162
|
+
|
|
163
|
+
DROP TRIGGER IF EXISTS trg_memory_records_search_tsv
|
|
164
|
+
ON ${schema}.memory_records;
|
|
165
|
+
|
|
166
|
+
CREATE TRIGGER trg_memory_records_search_tsv
|
|
167
|
+
BEFORE INSERT OR UPDATE OF title, summary, context_key, topic_key
|
|
168
|
+
ON ${schema}.memory_records
|
|
169
|
+
FOR EACH ROW
|
|
170
|
+
EXECUTE FUNCTION ${schema}.memory_records_search_tsv_update();
|
|
171
|
+
|
|
172
|
+
COMMENT ON TABLE ${schema}.memory_records IS
|
|
173
|
+
'v1 curated memory serving source. bootstrap/session_recall read this plane when curated serving mode is enabled.';
|
|
174
|
+
|
|
175
|
+
-- =========================================================================
|
|
176
|
+
-- Evidence refs: provenance links to legacy or future evidence sources
|
|
177
|
+
-- =========================================================================
|
|
178
|
+
CREATE TABLE IF NOT EXISTS ${schema}.evidence_refs (
|
|
179
|
+
id BIGSERIAL PRIMARY KEY,
|
|
180
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
181
|
+
owner_kind TEXT NOT NULL
|
|
182
|
+
CHECK (owner_kind IN ('memory_record','fact','candidate')),
|
|
183
|
+
owner_id BIGINT NOT NULL,
|
|
184
|
+
source_kind TEXT NOT NULL
|
|
185
|
+
CHECK (source_kind IN (
|
|
186
|
+
'session','session_summary','turn_embedding','insight',
|
|
187
|
+
'entity_state','evidence_item','raw_event','external'
|
|
188
|
+
)),
|
|
189
|
+
source_ref TEXT NOT NULL CHECK (btrim(source_ref) <> ''),
|
|
190
|
+
relation_kind TEXT NOT NULL DEFAULT 'supporting'
|
|
191
|
+
CHECK (relation_kind IN ('primary','supporting','contradicting','derived_from','imported_from')),
|
|
192
|
+
weight REAL NOT NULL DEFAULT 1.0 CHECK (weight >= 0),
|
|
193
|
+
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
194
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_evidence_refs_dedupe
|
|
198
|
+
ON ${schema}.evidence_refs (tenant_id, owner_kind, owner_id, source_kind, source_ref, relation_kind);
|
|
199
|
+
|
|
200
|
+
CREATE INDEX IF NOT EXISTS idx_evidence_refs_owner
|
|
201
|
+
ON ${schema}.evidence_refs (tenant_id, owner_kind, owner_id);
|
|
202
|
+
|
|
203
|
+
CREATE INDEX IF NOT EXISTS idx_evidence_refs_source
|
|
204
|
+
ON ${schema}.evidence_refs (tenant_id, source_kind, source_ref);
|
|
205
|
+
|
|
206
|
+
COMMENT ON TABLE ${schema}.evidence_refs IS
|
|
207
|
+
'Append-only provenance links. Source may point to legacy sessions today or evidence_items/raw_events later.';
|
|
208
|
+
|
|
209
|
+
-- =========================================================================
|
|
210
|
+
-- Feedback: append-only v1 feedback events, not truth mutation
|
|
211
|
+
-- =========================================================================
|
|
212
|
+
CREATE TABLE IF NOT EXISTS ${schema}.feedback (
|
|
213
|
+
id BIGSERIAL PRIMARY KEY,
|
|
214
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
215
|
+
target_kind TEXT NOT NULL
|
|
216
|
+
CHECK (target_kind IN ('memory_record','fact','candidate','recall_result','bootstrap','session')),
|
|
217
|
+
target_id TEXT NOT NULL CHECK (btrim(target_id) <> ''),
|
|
218
|
+
feedback_type TEXT NOT NULL
|
|
219
|
+
CHECK (feedback_type IN (
|
|
220
|
+
'helpful','irrelevant','scope_mismatch',
|
|
221
|
+
'confirm','stale','superseded','incorrect','conflict','expired',
|
|
222
|
+
'promote','pin','unpin','authority_mismatch','sensitive','archive'
|
|
223
|
+
)),
|
|
224
|
+
actor_kind TEXT NOT NULL DEFAULT 'user'
|
|
225
|
+
CHECK (actor_kind IN ('user','agent','system','curator')),
|
|
226
|
+
actor_id TEXT,
|
|
227
|
+
query_fingerprint TEXT,
|
|
228
|
+
note TEXT,
|
|
229
|
+
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
230
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_target
|
|
234
|
+
ON ${schema}.feedback (tenant_id, target_kind, target_id, created_at DESC);
|
|
235
|
+
|
|
236
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_type
|
|
237
|
+
ON ${schema}.feedback (tenant_id, feedback_type, created_at DESC);
|
|
238
|
+
|
|
239
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_query
|
|
240
|
+
ON ${schema}.feedback (tenant_id, query_fingerprint)
|
|
241
|
+
WHERE query_fingerprint IS NOT NULL;
|
|
242
|
+
|
|
243
|
+
COMMENT ON TABLE ${schema}.feedback IS
|
|
244
|
+
'Append-only v1 feedback events. Feedback may affect ranking/review, not memory truth.';
|
|
245
|
+
|
|
246
|
+
-- =========================================================================
|
|
247
|
+
-- Compaction runs: deterministic daily/weekly/monthly consolidation ledger
|
|
248
|
+
-- =========================================================================
|
|
249
|
+
CREATE TABLE IF NOT EXISTS ${schema}.compaction_runs (
|
|
250
|
+
id BIGSERIAL PRIMARY KEY,
|
|
251
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
252
|
+
cadence TEXT NOT NULL CHECK (cadence IN ('session','daily','weekly','monthly','manual')),
|
|
253
|
+
period_start TIMESTAMPTZ NOT NULL,
|
|
254
|
+
period_end TIMESTAMPTZ NOT NULL,
|
|
255
|
+
input_hash TEXT NOT NULL CHECK (btrim(input_hash) <> ''),
|
|
256
|
+
policy_version TEXT NOT NULL DEFAULT 'v1',
|
|
257
|
+
status TEXT NOT NULL DEFAULT 'planned'
|
|
258
|
+
CHECK (status IN ('planned','applied','failed','skipped')),
|
|
259
|
+
output JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
260
|
+
error TEXT,
|
|
261
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
262
|
+
applied_at TIMESTAMPTZ,
|
|
263
|
+
CHECK (period_end > period_start)
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_compaction_runs_dedupe
|
|
267
|
+
ON ${schema}.compaction_runs (tenant_id, cadence, period_start, period_end, input_hash, policy_version);
|
|
268
|
+
|
|
269
|
+
CREATE INDEX IF NOT EXISTS idx_compaction_runs_status
|
|
270
|
+
ON ${schema}.compaction_runs (tenant_id, cadence, status, period_end DESC);
|
|
271
|
+
|
|
272
|
+
COMMENT ON TABLE ${schema}.compaction_runs IS
|
|
273
|
+
'v1 deterministic consolidation ledger. Runs record candidates/status updates; they do not bypass promotion policy.';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
-- Aquifer v1 session finalization ledger
|
|
2
|
+
-- Requires: 001-base.sql and 007-v1-foundation.sql applied first
|
|
3
|
+
-- Usage: replace ${schema} with actual schema name
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS ${schema}.session_finalizations (
|
|
6
|
+
id BIGSERIAL PRIMARY KEY,
|
|
7
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
8
|
+
session_row_id BIGINT NOT NULL REFERENCES ${schema}.sessions(id) ON DELETE CASCADE,
|
|
9
|
+
source TEXT NOT NULL DEFAULT 'api',
|
|
10
|
+
host TEXT NOT NULL DEFAULT 'codex',
|
|
11
|
+
agent_id TEXT NOT NULL DEFAULT 'main',
|
|
12
|
+
session_id TEXT NOT NULL CHECK (btrim(session_id) <> ''),
|
|
13
|
+
transcript_hash TEXT NOT NULL CHECK (btrim(transcript_hash) <> ''),
|
|
14
|
+
phase TEXT NOT NULL DEFAULT 'curated_memory_v1',
|
|
15
|
+
mode TEXT NOT NULL DEFAULT 'handoff'
|
|
16
|
+
CHECK (mode IN (
|
|
17
|
+
'handoff','session_end','session_start_recovery','afterburn','manual'
|
|
18
|
+
)),
|
|
19
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
20
|
+
CHECK (status IN (
|
|
21
|
+
'pending','processing','finalized','failed','skipped','declined','deferred'
|
|
22
|
+
)),
|
|
23
|
+
finalizer_model TEXT,
|
|
24
|
+
scope_kind TEXT,
|
|
25
|
+
scope_key TEXT,
|
|
26
|
+
context_key TEXT,
|
|
27
|
+
topic_key TEXT,
|
|
28
|
+
summary_row_id BIGINT,
|
|
29
|
+
memory_result JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
30
|
+
error TEXT,
|
|
31
|
+
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
32
|
+
claimed_at TIMESTAMPTZ,
|
|
33
|
+
finalized_at TIMESTAMPTZ,
|
|
34
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
35
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_session_finalizations_identity
|
|
39
|
+
ON ${schema}.session_finalizations (
|
|
40
|
+
tenant_id, source, agent_id, session_id, transcript_hash, phase
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_session_finalizations_status
|
|
44
|
+
ON ${schema}.session_finalizations (tenant_id, host, status, updated_at DESC);
|
|
45
|
+
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_session_finalizations_session
|
|
47
|
+
ON ${schema}.session_finalizations (tenant_id, agent_id, session_id, updated_at DESC);
|
|
48
|
+
|
|
49
|
+
COMMENT ON TABLE ${schema}.session_finalizations IS
|
|
50
|
+
'v1 finalization ledger. DB is source of truth; local consumer markers are recovery hints only.';
|