@shadowforge0/aquifer-memory 1.8.1 → 1.9.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 +1 -0
- package/README.md +82 -26
- package/README_CN.md +33 -23
- package/README_TW.md +25 -24
- package/aquifer.config.example.json +2 -1
- package/consumers/cli.js +587 -33
- package/consumers/codex-active-checkpoint.js +3 -1
- package/consumers/codex-current-memory.js +10 -6
- package/consumers/codex.js +6 -3
- package/consumers/default/daily-entries.js +2 -2
- package/consumers/default/index.js +40 -30
- package/consumers/default/prompts/summary.js +2 -2
- package/consumers/mcp.js +56 -46
- package/consumers/openclaw-ext/index.js +65 -7
- package/consumers/openclaw-ext/openclaw.plugin.json +1 -1
- package/consumers/openclaw-ext/package.json +1 -1
- package/consumers/openclaw-install.js +326 -0
- package/consumers/openclaw-plugin.js +105 -24
- package/consumers/shared/compat-recall.js +101 -0
- package/consumers/shared/config.js +2 -0
- package/consumers/shared/openclaw-product-tools.js +130 -0
- package/consumers/shared/recall-format.js +2 -2
- package/core/aquifer.js +553 -41
- package/core/backends/local.js +169 -1
- package/core/doctor.js +924 -0
- package/core/finalization-inspector.js +164 -0
- package/core/finalization-review.js +88 -42
- package/core/interface.js +629 -0
- package/core/mcp-manifest.js +11 -3
- package/core/memory-bootstrap.js +25 -27
- package/core/memory-consolidation.js +564 -42
- package/core/memory-explain.js +593 -0
- package/core/memory-promotion.js +392 -55
- package/core/memory-recall.js +75 -71
- package/core/memory-records.js +107 -108
- package/core/memory-review.js +891 -0
- package/core/memory-serving.js +61 -4
- package/core/memory-type-policy.js +298 -0
- package/core/operator-observability.js +249 -0
- package/core/postgres-migrations.js +22 -0
- package/core/session-checkpoint-producer.js +3 -1
- package/core/session-checkpoints.js +1 -1
- package/core/session-finalization.js +78 -3
- package/core/storage.js +124 -8
- package/docs/getting-started.md +50 -4
- package/docs/setup.md +163 -24
- package/package.json +5 -4
- package/schema/004-completion.sql +4 -4
- package/schema/010-v1-finalization-review.sql +72 -0
- package/schema/019-v1-memory-review-resolutions.sql +53 -0
- package/schema/020-v1-assistant-shaping-memory.sql +30 -0
- package/scripts/backfill-canonical-key.js +1 -1
- package/scripts/codex-checkpoint-commands.js +28 -0
- package/scripts/codex-checkpoint-runtime.js +109 -0
- package/scripts/codex-recovery.js +16 -4
- package/scripts/diagnose-fts-zh.js +1 -1
- package/scripts/extract-insights-from-recent-sessions.js +4 -4
package/core/memory-serving.js
CHANGED
|
@@ -1,12 +1,53 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
function splitScopePath(value) {
|
|
4
|
-
if (Array.isArray(value))
|
|
4
|
+
if (Array.isArray(value)) {
|
|
5
|
+
const parts = value.map(v => String(v).trim()).filter(Boolean);
|
|
6
|
+
return parts.length > 0 ? parts : null;
|
|
7
|
+
}
|
|
5
8
|
if (typeof value !== 'string') return null;
|
|
6
9
|
const parts = value.split(',').map(v => v.trim()).filter(Boolean);
|
|
7
10
|
return parts.length > 0 ? parts : null;
|
|
8
11
|
}
|
|
9
12
|
|
|
13
|
+
function uniqueList(values = []) {
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const value of values) {
|
|
17
|
+
const key = String(value || '').trim();
|
|
18
|
+
if (!key || seen.has(key)) continue;
|
|
19
|
+
seen.add(key);
|
|
20
|
+
out.push(key);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeScopeList(value) {
|
|
26
|
+
return uniqueList(splitScopePath(value) || []);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function requestedScopeKeys(opts = {}) {
|
|
30
|
+
const fromPath = normalizeScopeList(opts.activeScopePath);
|
|
31
|
+
const keys = [];
|
|
32
|
+
if (fromPath.length > 0) keys.push(...fromPath);
|
|
33
|
+
if (opts.activeScopeKey) keys.push(opts.activeScopeKey);
|
|
34
|
+
if (opts.scopeKey) keys.push(opts.scopeKey);
|
|
35
|
+
if (opts.resolvedScopeKey) keys.push(opts.resolvedScopeKey);
|
|
36
|
+
if (opts.scopeKeys) keys.push(...normalizeScopeList(opts.scopeKeys));
|
|
37
|
+
return uniqueList(keys);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function assertAllowedScopeRequest(opts = {}) {
|
|
41
|
+
const allowed = normalizeScopeList(opts.allowedScopeKeys);
|
|
42
|
+
if (allowed.length === 0) return;
|
|
43
|
+
const allowedSet = new Set(allowed);
|
|
44
|
+
const requested = requestedScopeKeys(opts);
|
|
45
|
+
const denied = requested.filter(key => !allowedSet.has(key));
|
|
46
|
+
if (denied.length > 0) {
|
|
47
|
+
throw new Error(`Requested memory scope is outside allowedScopeKeys: ${denied.join(', ')}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
10
51
|
function hasEvidenceBoundary(opts = {}) {
|
|
11
52
|
return Boolean(
|
|
12
53
|
opts.agentId
|
|
@@ -89,7 +130,13 @@ function normalizeCuratedRecallRow(row = {}) {
|
|
|
89
130
|
function createMemoryServingRuntime(memoryCfg = {}, env = process.env) {
|
|
90
131
|
const servingMode = memoryCfg.servingMode || env.AQUIFER_MEMORY_SERVING_MODE || 'legacy';
|
|
91
132
|
const defaultActiveScopeKey = memoryCfg.activeScopeKey || null;
|
|
92
|
-
const
|
|
133
|
+
const configuredActiveScopePath = splitScopePath(memoryCfg.activeScopePath || null);
|
|
134
|
+
const defaultActiveScopePath = configuredActiveScopePath
|
|
135
|
+
|| uniqueList(['global', defaultActiveScopeKey].filter(Boolean));
|
|
136
|
+
const configuredAllowedScopeKeys = normalizeScopeList(memoryCfg.allowedScopeKeys || env.AQUIFER_MEMORY_ALLOWED_SCOPE_KEYS || null);
|
|
137
|
+
const defaultAllowedScopeKeys = configuredAllowedScopeKeys.length > 0
|
|
138
|
+
? configuredAllowedScopeKeys
|
|
139
|
+
: defaultActiveScopePath;
|
|
93
140
|
|
|
94
141
|
function resolveMode(opts = {}) {
|
|
95
142
|
const mode = opts.memoryMode || opts.servingMode || servingMode;
|
|
@@ -100,12 +147,18 @@ function createMemoryServingRuntime(memoryCfg = {}, env = process.env) {
|
|
|
100
147
|
|
|
101
148
|
function withDefaultScope(opts = {}) {
|
|
102
149
|
const next = { ...opts };
|
|
103
|
-
|
|
150
|
+
const scopeIdOnly = next.scopeId && !next.activeScopePath && !next.activeScopeKey;
|
|
151
|
+
if (!next.allowedScopeKeys && defaultAllowedScopeKeys) {
|
|
152
|
+
next.allowedScopeKeys = defaultAllowedScopeKeys;
|
|
153
|
+
}
|
|
154
|
+
if (!scopeIdOnly && !next.activeScopePath && defaultActiveScopePath) next.activeScopePath = defaultActiveScopePath;
|
|
104
155
|
if (Array.isArray(next.activeScopePath) && next.activeScopePath.length > 0) {
|
|
105
156
|
if (!next.activeScopeKey) next.activeScopeKey = next.activeScopePath[next.activeScopePath.length - 1];
|
|
157
|
+
assertAllowedScopeRequest(next);
|
|
106
158
|
return next;
|
|
107
159
|
}
|
|
108
|
-
if (!next.activeScopeKey && defaultActiveScopeKey) next.activeScopeKey = defaultActiveScopeKey;
|
|
160
|
+
if (!scopeIdOnly && !next.activeScopeKey && defaultActiveScopeKey) next.activeScopeKey = defaultActiveScopeKey;
|
|
161
|
+
assertAllowedScopeRequest(next);
|
|
109
162
|
return next;
|
|
110
163
|
}
|
|
111
164
|
|
|
@@ -114,6 +167,7 @@ function createMemoryServingRuntime(memoryCfg = {}, env = process.env) {
|
|
|
114
167
|
assertCuratedRecallOpts,
|
|
115
168
|
defaultActiveScopeKey,
|
|
116
169
|
defaultActiveScopePath,
|
|
170
|
+
defaultAllowedScopeKeys,
|
|
117
171
|
hasEvidenceBoundary,
|
|
118
172
|
normalizeCuratedRecallRow,
|
|
119
173
|
resolveMode,
|
|
@@ -123,10 +177,13 @@ function createMemoryServingRuntime(memoryCfg = {}, env = process.env) {
|
|
|
123
177
|
}
|
|
124
178
|
|
|
125
179
|
module.exports = {
|
|
180
|
+
assertAllowedScopeRequest,
|
|
126
181
|
assertCuratedBootstrapOpts,
|
|
127
182
|
assertCuratedRecallOpts,
|
|
128
183
|
createMemoryServingRuntime,
|
|
129
184
|
hasEvidenceBoundary,
|
|
130
185
|
normalizeCuratedRecallRow,
|
|
186
|
+
normalizeScopeList,
|
|
187
|
+
requestedScopeKeys,
|
|
131
188
|
splitScopePath,
|
|
132
189
|
};
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MEMORY_TYPE_POLICY = Object.freeze({
|
|
4
|
+
assistant_shaping: {
|
|
5
|
+
label: '回答塑形',
|
|
6
|
+
active: true,
|
|
7
|
+
defaultInheritance: 'defaultable',
|
|
8
|
+
defaultAspect: 'assistant_shaping',
|
|
9
|
+
bootstrapPriority: 0,
|
|
10
|
+
recallRank: 95,
|
|
11
|
+
runtimeContract: true,
|
|
12
|
+
},
|
|
13
|
+
constraint: {
|
|
14
|
+
label: '限制',
|
|
15
|
+
active: true,
|
|
16
|
+
defaultInheritance: 'additive',
|
|
17
|
+
defaultAspect: 'constraint',
|
|
18
|
+
bootstrapPriority: 1,
|
|
19
|
+
recallRank: 80,
|
|
20
|
+
},
|
|
21
|
+
state: {
|
|
22
|
+
label: '狀態',
|
|
23
|
+
active: true,
|
|
24
|
+
defaultInheritance: 'defaultable',
|
|
25
|
+
defaultAspect: 'state',
|
|
26
|
+
bootstrapPriority: 2,
|
|
27
|
+
recallRank: 60,
|
|
28
|
+
},
|
|
29
|
+
open_loop: {
|
|
30
|
+
label: '未完成',
|
|
31
|
+
active: true,
|
|
32
|
+
defaultInheritance: 'non_inheritable',
|
|
33
|
+
defaultAspect: 'open_loop',
|
|
34
|
+
bootstrapPriority: 3,
|
|
35
|
+
recallRank: 55,
|
|
36
|
+
},
|
|
37
|
+
decision: {
|
|
38
|
+
label: '決策',
|
|
39
|
+
active: true,
|
|
40
|
+
defaultInheritance: 'non_inheritable',
|
|
41
|
+
defaultAspect: 'decision',
|
|
42
|
+
bootstrapPriority: 4,
|
|
43
|
+
recallRank: 50,
|
|
44
|
+
},
|
|
45
|
+
preference: {
|
|
46
|
+
label: '偏好',
|
|
47
|
+
active: true,
|
|
48
|
+
defaultInheritance: 'defaultable',
|
|
49
|
+
defaultAspect: 'preference',
|
|
50
|
+
bootstrapPriority: 5,
|
|
51
|
+
recallRank: 70,
|
|
52
|
+
},
|
|
53
|
+
fact: {
|
|
54
|
+
label: '事實',
|
|
55
|
+
active: true,
|
|
56
|
+
defaultInheritance: 'defaultable',
|
|
57
|
+
defaultAspect: 'fact',
|
|
58
|
+
bootstrapPriority: 6,
|
|
59
|
+
recallRank: 40,
|
|
60
|
+
},
|
|
61
|
+
conclusion: {
|
|
62
|
+
label: '判斷',
|
|
63
|
+
active: true,
|
|
64
|
+
defaultInheritance: 'defaultable',
|
|
65
|
+
defaultAspect: 'conclusion',
|
|
66
|
+
bootstrapPriority: 7,
|
|
67
|
+
recallRank: 30,
|
|
68
|
+
},
|
|
69
|
+
entity_note: {
|
|
70
|
+
label: '註記',
|
|
71
|
+
active: true,
|
|
72
|
+
defaultInheritance: 'defaultable',
|
|
73
|
+
defaultAspect: 'entity_note',
|
|
74
|
+
bootstrapPriority: 8,
|
|
75
|
+
recallRank: 20,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const AUTHORITY_POLICY = Object.freeze({
|
|
80
|
+
raw_transcript: { strength: 0, sortPriority: 6 },
|
|
81
|
+
llm_inference: { strength: 1, sortPriority: 5 },
|
|
82
|
+
verified_summary: { strength: 2, sortPriority: 4 },
|
|
83
|
+
manual: { strength: 3, sortPriority: 2 },
|
|
84
|
+
system: { strength: 4, sortPriority: 3 },
|
|
85
|
+
executable_evidence: { strength: 5, sortPriority: 1 },
|
|
86
|
+
user_explicit: { strength: 6, sortPriority: 0 },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const FEEDBACK_WEIGHT = Object.freeze({
|
|
90
|
+
helpful: 0.15,
|
|
91
|
+
confirm: 0.10,
|
|
92
|
+
irrelevant: -0.20,
|
|
93
|
+
scope_mismatch: -0.25,
|
|
94
|
+
stale: -0.30,
|
|
95
|
+
incorrect: -0.50,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const ASSISTANT_SHAPING_KINDS = Object.freeze([
|
|
99
|
+
'response_style',
|
|
100
|
+
'interaction_boundary',
|
|
101
|
+
'retrieval_policy',
|
|
102
|
+
'tool_routing',
|
|
103
|
+
'memory_policy',
|
|
104
|
+
'user_preference',
|
|
105
|
+
'correction_pattern',
|
|
106
|
+
'decision_style',
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
const ASSISTANT_SHAPING_SERVING_IMPACTS = Object.freeze([
|
|
110
|
+
'changes_future_responses',
|
|
111
|
+
'changes_retrieval_selection',
|
|
112
|
+
'changes_bootstrap_selection',
|
|
113
|
+
'changes_tool_routing',
|
|
114
|
+
'changes_memory_promotion',
|
|
115
|
+
'sets_user_boundary',
|
|
116
|
+
'sets_assistant_behavior',
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
const ASSISTANT_SHAPING_PROMPT_LINES = Object.freeze([
|
|
120
|
+
'Use assistant_shaping only when a memory changes durable cross-consumer runtime behavior for the user: future response style, interaction boundary, retrieval selection, tool routing, or memory promotion policy.',
|
|
121
|
+
'Each assistant_shaping item must include guidance or policy text plus shapingKind, servingImpact, and userRelevance. For non-explicit-user-instruction summaries, include temporalSupport showing repeated observations, multiple sessions, or a consolidation window.',
|
|
122
|
+
'Treat shapingKind and servingImpact as extensible hints, not as a closed product taxonomy. If the item only reports current task state, implementation facts, or a single answer, keep it in the ordinary memory layer instead.',
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
function normalizeToken(value) {
|
|
126
|
+
return String(value || '').trim().toLowerCase();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function memoryTypePolicy(type) {
|
|
130
|
+
return MEMORY_TYPE_POLICY[normalizeToken(type)] || null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function activeMemoryTypes() {
|
|
134
|
+
return Object.keys(MEMORY_TYPE_POLICY).filter(type => MEMORY_TYPE_POLICY[type].active);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isActiveMemoryType(type) {
|
|
138
|
+
const policy = memoryTypePolicy(type);
|
|
139
|
+
return Boolean(policy && policy.active);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function defaultInheritanceForMemoryType(type) {
|
|
143
|
+
return memoryTypePolicy(type)?.defaultInheritance || 'defaultable';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function defaultAspectForMemoryType(type) {
|
|
147
|
+
return memoryTypePolicy(type)?.defaultAspect || normalizeToken(type) || 'memory';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function memoryTypeLabel(type) {
|
|
151
|
+
return memoryTypePolicy(type)?.label || '記憶';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function memoryTypeBootstrapPriority(type) {
|
|
155
|
+
return memoryTypePolicy(type)?.bootstrapPriority ?? 99;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function memoryTypeRecallRank(type) {
|
|
159
|
+
return memoryTypePolicy(type)?.recallRank ?? 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function memoryTypeRuntimeContract(type) {
|
|
163
|
+
return memoryTypePolicy(type)?.runtimeContract === true;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function authorityPolicy(authority) {
|
|
167
|
+
return AUTHORITY_POLICY[normalizeToken(authority)] || null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function authorityStrength(authority) {
|
|
171
|
+
return authorityPolicy(authority)?.strength ?? 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function authoritySortPriority(authority) {
|
|
175
|
+
return authorityPolicy(authority)?.sortPriority ?? 99;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function feedbackWeight(type) {
|
|
179
|
+
return FEEDBACK_WEIGHT[normalizeToken(type)] || 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function sqlLiteral(value) {
|
|
183
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function caseSql(expr, entries, valueKey, fallback) {
|
|
187
|
+
const clauses = entries
|
|
188
|
+
.map(([key, policy]) => `WHEN ${sqlLiteral(key)} THEN ${policy[valueKey]}`)
|
|
189
|
+
.join('\n ');
|
|
190
|
+
return `CASE ${expr}\n ${clauses}\n ELSE ${fallback}\n END`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function memoryTypeBootstrapPrioritySql(expr = 'm.memory_type') {
|
|
194
|
+
return caseSql(expr, Object.entries(MEMORY_TYPE_POLICY), 'bootstrapPriority', 99);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function memoryTypeRecallRankSql(expr = 'm.memory_type') {
|
|
198
|
+
const entries = Object.entries(MEMORY_TYPE_POLICY)
|
|
199
|
+
.map(([key, policy]) => [key, { recallWeight: (policy.recallRank || 0) / 100 }]);
|
|
200
|
+
return caseSql(expr, entries, 'recallWeight', 0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function authoritySortPrioritySql(expr = 'm.authority') {
|
|
204
|
+
return caseSql(expr, Object.entries(AUTHORITY_POLICY), 'sortPriority', 99);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function feedbackWeightSql(expr = 'f.feedback_type') {
|
|
208
|
+
const entries = Object.entries(FEEDBACK_WEIGHT).map(([key, weight]) => [key, { weight }]);
|
|
209
|
+
return caseSql(expr, entries, 'weight', 0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function memoryTypeOf(record = {}) {
|
|
213
|
+
return record.memoryType || record.memory_type || record.type || 'memory';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function memorySummaryText(record = {}) {
|
|
217
|
+
return String(record.summary || record.summaryText || record.title || '').replace(/\s+/g, ' ').trim();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function assistantShapingMetadata(record = {}) {
|
|
221
|
+
const payload = record.payload && typeof record.payload === 'object' ? record.payload : {};
|
|
222
|
+
const entries = [
|
|
223
|
+
['policy', payload.policyKey || payload.policy_key || record.policyKey || record.policy_key],
|
|
224
|
+
['kind', payload.shapingKind || payload.shaping_kind || payload.kind || payload.category || record.shapingKind || record.shaping_kind],
|
|
225
|
+
['impact', payload.servingImpact || payload.serving_impact || payload.impact || record.servingImpact || record.serving_impact],
|
|
226
|
+
];
|
|
227
|
+
return entries
|
|
228
|
+
.map(([key, value]) => [key, String(value || '').trim()])
|
|
229
|
+
.filter(([, value]) => value);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function formatRuntimeMemoryLine(record = {}, opts = {}) {
|
|
233
|
+
const type = memoryTypeOf(record);
|
|
234
|
+
const label = opts.localizedLabel === true ? memoryTypeLabel(type) : type;
|
|
235
|
+
const attrs = [];
|
|
236
|
+
const scope = record.scopeKey || record.scope_key;
|
|
237
|
+
const authority = record.authority;
|
|
238
|
+
const confidence = record.confidence || record.payload?.confidence || record.payload?.currentMemoryConfidence;
|
|
239
|
+
if (opts.includeScope === true && scope) attrs.push(`scope=${scope}`);
|
|
240
|
+
if (opts.includeAuthority === true && authority) attrs.push(`authority=${authority}`);
|
|
241
|
+
if (opts.includeConfidence === true && confidence) attrs.push(`confidence=${confidence}`);
|
|
242
|
+
const suffix = attrs.length > 0 ? ` ${attrs.join(' ')}` : '';
|
|
243
|
+
const metadata = type === 'assistant_shaping'
|
|
244
|
+
? assistantShapingMetadata(record).map(([key, value]) => `${key}=${value}`).join(' ')
|
|
245
|
+
: '';
|
|
246
|
+
const metadataSuffix = metadata ? ` (${metadata})` : '';
|
|
247
|
+
return `- ${label}${suffix}: ${memorySummaryText(record)}${metadataSuffix}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function assistantShapingKindAllowed(value) {
|
|
251
|
+
return ASSISTANT_SHAPING_KINDS.includes(normalizeToken(value));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function assistantShapingServingImpactAllowed(value) {
|
|
255
|
+
const token = normalizeToken(value);
|
|
256
|
+
return assistantShapingServingImpactKnown(token)
|
|
257
|
+
|| /^(changes|sets|routes|selects|filters|prioritizes|suppresses)_[a-z0-9_]+$/.test(token);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function assistantShapingServingImpactKnown(value) {
|
|
261
|
+
return ASSISTANT_SHAPING_SERVING_IMPACTS.includes(normalizeToken(value));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function assistantShapingPromptLines() {
|
|
265
|
+
return ASSISTANT_SHAPING_PROMPT_LINES.slice();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = {
|
|
269
|
+
ASSISTANT_SHAPING_KINDS,
|
|
270
|
+
ASSISTANT_SHAPING_SERVING_IMPACTS,
|
|
271
|
+
AUTHORITY_POLICY,
|
|
272
|
+
FEEDBACK_WEIGHT,
|
|
273
|
+
MEMORY_TYPE_POLICY,
|
|
274
|
+
activeMemoryTypes,
|
|
275
|
+
assistantShapingKindAllowed,
|
|
276
|
+
assistantShapingMetadata,
|
|
277
|
+
assistantShapingPromptLines,
|
|
278
|
+
assistantShapingServingImpactAllowed,
|
|
279
|
+
assistantShapingServingImpactKnown,
|
|
280
|
+
authoritySortPriority,
|
|
281
|
+
authoritySortPrioritySql,
|
|
282
|
+
authorityStrength,
|
|
283
|
+
defaultAspectForMemoryType,
|
|
284
|
+
defaultInheritanceForMemoryType,
|
|
285
|
+
feedbackWeight,
|
|
286
|
+
feedbackWeightSql,
|
|
287
|
+
formatRuntimeMemoryLine,
|
|
288
|
+
isActiveMemoryType,
|
|
289
|
+
memorySummaryText,
|
|
290
|
+
memoryTypeBootstrapPriority,
|
|
291
|
+
memoryTypeBootstrapPrioritySql,
|
|
292
|
+
memoryTypeLabel,
|
|
293
|
+
memoryTypeOf,
|
|
294
|
+
memoryTypePolicy,
|
|
295
|
+
memoryTypeRecallRank,
|
|
296
|
+
memoryTypeRecallRankSql,
|
|
297
|
+
memoryTypeRuntimeContract,
|
|
298
|
+
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function parseJsonObject(value, fallback = {}) {
|
|
4
|
+
if (value === null || value === undefined) return fallback;
|
|
5
|
+
if (typeof value === 'string') {
|
|
6
|
+
try {
|
|
7
|
+
const parsed = JSON.parse(value);
|
|
8
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : fallback;
|
|
9
|
+
} catch {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : fallback;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function rowTime(value) {
|
|
17
|
+
if (!value) return null;
|
|
18
|
+
const parsed = new Date(value);
|
|
19
|
+
return isNaN(parsed.getTime()) ? String(value) : parsed.toISOString();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function summarizeCompactionRun(row = {}) {
|
|
23
|
+
const output = parseJsonObject(row.output, {});
|
|
24
|
+
const plan = output.plan || output;
|
|
25
|
+
const candidates = Array.isArray(plan.candidates) ? plan.candidates : [];
|
|
26
|
+
const statusUpdates = Array.isArray(plan.statusUpdates || plan.status_updates)
|
|
27
|
+
? (plan.statusUpdates || plan.status_updates)
|
|
28
|
+
: [];
|
|
29
|
+
return {
|
|
30
|
+
id: row.id,
|
|
31
|
+
kind: 'compaction',
|
|
32
|
+
status: row.status,
|
|
33
|
+
cadence: row.cadence,
|
|
34
|
+
periodStart: row.period_start,
|
|
35
|
+
periodEnd: row.period_end,
|
|
36
|
+
policyVersion: row.policy_version || null,
|
|
37
|
+
workerId: row.worker_id || null,
|
|
38
|
+
claimedAt: rowTime(row.claimed_at),
|
|
39
|
+
leaseExpiresAt: rowTime(row.lease_expires_at),
|
|
40
|
+
appliedAt: rowTime(row.applied_at),
|
|
41
|
+
reclaimedAt: rowTime(row.reclaimed_at),
|
|
42
|
+
error: row.error || null,
|
|
43
|
+
sourceCoverage: parseJsonObject(row.source_coverage, {}),
|
|
44
|
+
outputCoverage: parseJsonObject(row.output_coverage, {}),
|
|
45
|
+
candidateCount: candidates.length,
|
|
46
|
+
statusUpdateCount: statusUpdates.length,
|
|
47
|
+
createdAt: rowTime(row.created_at),
|
|
48
|
+
updatedAt: rowTime(row.updated_at),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function summarizeCheckpointRun(row = {}, sourceCount = 0) {
|
|
53
|
+
const payload = parseJsonObject(row.checkpoint_payload, {});
|
|
54
|
+
return {
|
|
55
|
+
id: row.id,
|
|
56
|
+
kind: 'checkpoint',
|
|
57
|
+
status: row.status,
|
|
58
|
+
scopeId: row.scope_id,
|
|
59
|
+
checkpointKey: row.checkpoint_key,
|
|
60
|
+
fromFinalizationIdExclusive: row.from_finalization_id_exclusive,
|
|
61
|
+
toFinalizationIdInclusive: row.to_finalization_id_inclusive,
|
|
62
|
+
windowStart: rowTime(row.window_start),
|
|
63
|
+
windowEnd: rowTime(row.window_end),
|
|
64
|
+
claimedAt: rowTime(row.claimed_at),
|
|
65
|
+
finalizedAt: rowTime(row.finalized_at),
|
|
66
|
+
error: row.error || null,
|
|
67
|
+
sourceCount,
|
|
68
|
+
checkpointPayloadKeys: Object.keys(payload).sort(),
|
|
69
|
+
createdAt: rowTime(row.created_at),
|
|
70
|
+
updatedAt: rowTime(row.updated_at),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function sourceValue(row = {}, key) {
|
|
75
|
+
const metadata = parseJsonObject(row.metadata, {});
|
|
76
|
+
return row[key] ?? metadata[key] ?? null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function tableMissingError(error) {
|
|
80
|
+
return error && error.code === '42P01';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function createOperatorObservability({ pool, schema, defaultTenantId = 'default' }) {
|
|
84
|
+
async function compactionStatus(input = {}) {
|
|
85
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
86
|
+
const limit = Math.max(1, Math.min(50, input.limit || 10));
|
|
87
|
+
try {
|
|
88
|
+
const [latest, stale, counts] = await Promise.all([
|
|
89
|
+
pool.query(
|
|
90
|
+
`SELECT *
|
|
91
|
+
FROM ${schema}.compaction_runs
|
|
92
|
+
WHERE tenant_id = $1
|
|
93
|
+
ORDER BY COALESCE(applied_at, created_at) DESC, id DESC
|
|
94
|
+
LIMIT $2`,
|
|
95
|
+
[tenantId, limit],
|
|
96
|
+
),
|
|
97
|
+
pool.query(
|
|
98
|
+
`SELECT *
|
|
99
|
+
FROM ${schema}.compaction_runs
|
|
100
|
+
WHERE tenant_id = $1
|
|
101
|
+
AND status = 'applying'
|
|
102
|
+
AND lease_expires_at IS NOT NULL
|
|
103
|
+
AND lease_expires_at < transaction_timestamp()
|
|
104
|
+
ORDER BY lease_expires_at ASC, id ASC
|
|
105
|
+
LIMIT $2`,
|
|
106
|
+
[tenantId, limit],
|
|
107
|
+
),
|
|
108
|
+
pool.query(
|
|
109
|
+
`SELECT status, COUNT(*)::int AS count
|
|
110
|
+
FROM ${schema}.compaction_runs
|
|
111
|
+
WHERE tenant_id = $1
|
|
112
|
+
GROUP BY status`,
|
|
113
|
+
[tenantId],
|
|
114
|
+
),
|
|
115
|
+
]);
|
|
116
|
+
return {
|
|
117
|
+
available: true,
|
|
118
|
+
latest: latest.rows.map(summarizeCompactionRun),
|
|
119
|
+
staleClaims: stale.rows.map(summarizeCompactionRun),
|
|
120
|
+
statusCounts: Object.fromEntries(counts.rows.map(row => [row.status, row.count])),
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (tableMissingError(error)) {
|
|
124
|
+
return { available: false, latest: [], staleClaims: [], statusCounts: {}, error: 'compaction_runs table is missing' };
|
|
125
|
+
}
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function checkpointStatus(input = {}) {
|
|
131
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
132
|
+
const limit = Math.max(1, Math.min(50, input.limit || 10));
|
|
133
|
+
try {
|
|
134
|
+
const [latest, counts] = await Promise.all([
|
|
135
|
+
pool.query(
|
|
136
|
+
`SELECT c.*, COUNT(s.id)::int AS source_count
|
|
137
|
+
FROM ${schema}.checkpoint_runs c
|
|
138
|
+
LEFT JOIN ${schema}.checkpoint_run_sources s
|
|
139
|
+
ON s.tenant_id = c.tenant_id
|
|
140
|
+
AND s.checkpoint_run_id = c.id
|
|
141
|
+
WHERE c.tenant_id = $1
|
|
142
|
+
GROUP BY c.id
|
|
143
|
+
ORDER BY c.updated_at DESC, c.id DESC
|
|
144
|
+
LIMIT $2`,
|
|
145
|
+
[tenantId, limit],
|
|
146
|
+
),
|
|
147
|
+
pool.query(
|
|
148
|
+
`SELECT status, COUNT(*)::int AS count
|
|
149
|
+
FROM ${schema}.checkpoint_runs
|
|
150
|
+
WHERE tenant_id = $1
|
|
151
|
+
GROUP BY status`,
|
|
152
|
+
[tenantId],
|
|
153
|
+
),
|
|
154
|
+
]);
|
|
155
|
+
return {
|
|
156
|
+
available: true,
|
|
157
|
+
latest: latest.rows.map(row => summarizeCheckpointRun(row, row.source_count || 0)),
|
|
158
|
+
statusCounts: Object.fromEntries(counts.rows.map(row => [row.status, row.count])),
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (tableMissingError(error)) {
|
|
162
|
+
return { available: false, latest: [], statusCounts: {}, error: 'checkpoint_runs table is missing' };
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function status(input = {}) {
|
|
169
|
+
const [compaction, checkpoint] = await Promise.all([
|
|
170
|
+
compactionStatus(input),
|
|
171
|
+
checkpointStatus(input),
|
|
172
|
+
]);
|
|
173
|
+
return {
|
|
174
|
+
readOnly: true,
|
|
175
|
+
compaction,
|
|
176
|
+
checkpoint,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function inspect(input = {}) {
|
|
181
|
+
const tenantId = input.tenantId || defaultTenantId;
|
|
182
|
+
const runId = input.runId || input.id;
|
|
183
|
+
if (!runId) throw new Error('runId is required');
|
|
184
|
+
const kind = input.kind || 'compaction';
|
|
185
|
+
if (kind === 'compaction') {
|
|
186
|
+
const result = await pool.query(
|
|
187
|
+
`SELECT *
|
|
188
|
+
FROM ${schema}.compaction_runs
|
|
189
|
+
WHERE tenant_id = $1
|
|
190
|
+
AND id = $2
|
|
191
|
+
LIMIT 1`,
|
|
192
|
+
[tenantId, runId],
|
|
193
|
+
);
|
|
194
|
+
if (!result.rows[0]) throw new Error('Compaction run not found');
|
|
195
|
+
return {
|
|
196
|
+
readOnly: true,
|
|
197
|
+
run: summarizeCompactionRun(result.rows[0]),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (kind === 'checkpoint') {
|
|
201
|
+
const [run, sources] = await Promise.all([
|
|
202
|
+
pool.query(
|
|
203
|
+
`SELECT *
|
|
204
|
+
FROM ${schema}.checkpoint_runs
|
|
205
|
+
WHERE tenant_id = $1
|
|
206
|
+
AND id = $2
|
|
207
|
+
LIMIT 1`,
|
|
208
|
+
[tenantId, runId],
|
|
209
|
+
),
|
|
210
|
+
pool.query(
|
|
211
|
+
`SELECT id, finalization_id, source_index, session_id, transcript_hash, finalized_at, metadata
|
|
212
|
+
FROM ${schema}.checkpoint_run_sources
|
|
213
|
+
WHERE tenant_id = $1
|
|
214
|
+
AND checkpoint_run_id = $2
|
|
215
|
+
ORDER BY source_index ASC, id ASC`,
|
|
216
|
+
[tenantId, runId],
|
|
217
|
+
),
|
|
218
|
+
]);
|
|
219
|
+
if (!run.rows[0]) throw new Error('Checkpoint run not found');
|
|
220
|
+
return {
|
|
221
|
+
readOnly: true,
|
|
222
|
+
run: summarizeCheckpointRun(run.rows[0], sources.rows.length),
|
|
223
|
+
sources: sources.rows.map(row => ({
|
|
224
|
+
id: row.id,
|
|
225
|
+
finalizationId: row.finalization_id,
|
|
226
|
+
sourceIndex: row.source_index,
|
|
227
|
+
sessionId: row.session_id,
|
|
228
|
+
agentId: sourceValue(row, 'agentId') || sourceValue(row, 'agent_id'),
|
|
229
|
+
source: sourceValue(row, 'source'),
|
|
230
|
+
status: sourceValue(row, 'status'),
|
|
231
|
+
transcriptHashPrefix: row.transcript_hash ? String(row.transcript_hash).slice(0, 12) : null,
|
|
232
|
+
finalizedAt: rowTime(row.finalized_at),
|
|
233
|
+
})),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
throw new Error('kind must be compaction or checkpoint');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
status,
|
|
241
|
+
inspect,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = {
|
|
246
|
+
createOperatorObservability,
|
|
247
|
+
summarizeCompactionRun,
|
|
248
|
+
summarizeCheckpointRun,
|
|
249
|
+
};
|
|
@@ -81,6 +81,26 @@ const MIGRATION_PLAN = [
|
|
|
81
81
|
{ table: 'finalization_candidates', column: 'candidate_hash' },
|
|
82
82
|
],
|
|
83
83
|
},
|
|
84
|
+
{
|
|
85
|
+
id: '019-v1-memory-review-resolutions',
|
|
86
|
+
file: '019-v1-memory-review-resolutions.sql',
|
|
87
|
+
always: true,
|
|
88
|
+
signature: [
|
|
89
|
+
'memory_review_resolutions',
|
|
90
|
+
{ index: 'idx_memory_review_resolutions_memory_latest' },
|
|
91
|
+
{ index: 'idx_memory_review_resolutions_canonical_latest' },
|
|
92
|
+
{ index: 'idx_memory_review_resolutions_defer_until' },
|
|
93
|
+
{ index: 'idx_feedback_memory_review_latest' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: '020-v1-assistant-shaping-memory',
|
|
98
|
+
file: '020-v1-assistant-shaping-memory.sql',
|
|
99
|
+
always: true,
|
|
100
|
+
signature: [
|
|
101
|
+
{ index: 'idx_memory_records_assistant_shaping_bootstrap' },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
84
104
|
];
|
|
85
105
|
|
|
86
106
|
function createPostgresMigrationRuntime(opts = {}) {
|
|
@@ -296,6 +316,8 @@ function createPostgresMigrationRuntime(opts = {}) {
|
|
|
296
316
|
['016-v1-evidence-ref-multi-item.sql', '016-v1-evidence-ref-multi-item'],
|
|
297
317
|
['017-v1-memory-record-embeddings.sql', '017-v1-memory-record-embeddings'],
|
|
298
318
|
['018-v1-finalization-candidate-envelope.sql', '018-v1-finalization-candidate-envelope'],
|
|
319
|
+
['019-v1-memory-review-resolutions.sql', '019-v1-memory-review-resolutions'],
|
|
320
|
+
['020-v1-assistant-shaping-memory.sql', '020-v1-assistant-shaping-memory'],
|
|
299
321
|
]) {
|
|
300
322
|
await client.query(loadSql(migration[0], schema));
|
|
301
323
|
ddlExecuted.push(migration[1]);
|