@illuma-ai/agents 1.1.25 → 1.3.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/dist/cjs/agents/AgentContext.cjs +20 -3
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/spawnPath.cjs +104 -0
- package/dist/cjs/common/spawnPath.cjs.map +1 -0
- package/dist/cjs/graphs/Graph.cjs +87 -31
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/HandoffRegistry.cjs +143 -0
- package/dist/cjs/graphs/HandoffRegistry.cjs.map +1 -0
- package/dist/cjs/graphs/MultiAgentGraph.cjs +587 -184
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/graphs/phases/flushLoop.cjs +214 -0
- package/dist/cjs/graphs/phases/flushLoop.cjs.map +1 -0
- package/dist/cjs/graphs/phases/memoryFlushPhase.cjs +102 -0
- package/dist/cjs/graphs/phases/memoryFlushPhase.cjs.map +1 -0
- package/dist/cjs/llm/bedrock/index.cjs +4 -3
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +115 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/memory/citations.cjs +69 -0
- package/dist/cjs/memory/citations.cjs.map +1 -0
- package/dist/cjs/memory/compositeBackend.cjs +60 -0
- package/dist/cjs/memory/compositeBackend.cjs.map +1 -0
- package/dist/cjs/memory/constants.cjs +232 -0
- package/dist/cjs/memory/constants.cjs.map +1 -0
- package/dist/cjs/memory/embeddings.cjs +151 -0
- package/dist/cjs/memory/embeddings.cjs.map +1 -0
- package/dist/cjs/memory/factory.cjs +95 -0
- package/dist/cjs/memory/factory.cjs.map +1 -0
- package/dist/cjs/memory/migrate.cjs +81 -0
- package/dist/cjs/memory/migrate.cjs.map +1 -0
- package/dist/cjs/memory/mmr.cjs +138 -0
- package/dist/cjs/memory/mmr.cjs.map +1 -0
- package/dist/cjs/memory/paths.cjs +217 -0
- package/dist/cjs/memory/paths.cjs.map +1 -0
- package/dist/cjs/memory/pgvectorStore.cjs +225 -0
- package/dist/cjs/memory/pgvectorStore.cjs.map +1 -0
- package/dist/cjs/memory/recallTracking.cjs +98 -0
- package/dist/cjs/memory/recallTracking.cjs.map +1 -0
- package/dist/cjs/memory/schema.sql +51 -0
- package/dist/cjs/memory/temporalDecay.cjs +118 -0
- package/dist/cjs/memory/temporalDecay.cjs.map +1 -0
- package/dist/cjs/nodes/ApprovalGateNode.cjs +1 -1
- package/dist/cjs/nodes/ApprovalGateNode.cjs.map +1 -1
- package/dist/cjs/prompts/memoryFlushPrompt.cjs +49 -0
- package/dist/cjs/prompts/memoryFlushPrompt.cjs.map +1 -0
- package/dist/cjs/run.cjs +16 -3
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +4 -4
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/AskUser.cjs +6 -1
- package/dist/cjs/tools/AskUser.cjs.map +1 -1
- package/dist/cjs/tools/BrowserTools.cjs +1 -1
- package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +127 -10
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/approval/constants.cjs +2 -2
- package/dist/cjs/tools/approval/constants.cjs.map +1 -1
- package/dist/cjs/tools/memory/index.cjs +58 -0
- package/dist/cjs/tools/memory/index.cjs.map +1 -0
- package/dist/cjs/tools/memory/memoryAppendTool.cjs +69 -0
- package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -0
- package/dist/cjs/tools/memory/memoryGetTool.cjs +49 -0
- package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -0
- package/dist/cjs/tools/memory/memorySearchTool.cjs +65 -0
- package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -0
- package/dist/cjs/tools/memory/shared.cjs +106 -0
- package/dist/cjs/tools/memory/shared.cjs.map +1 -0
- package/dist/cjs/types/graph.cjs.map +1 -1
- package/dist/cjs/utils/childAgentContext.cjs +242 -0
- package/dist/cjs/utils/childAgentContext.cjs.map +1 -0
- package/dist/cjs/utils/events.cjs +36 -4
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/cjs/utils/finishReasons.cjs +44 -0
- package/dist/cjs/utils/finishReasons.cjs.map +1 -0
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/cjs/utils/logging.cjs +34 -0
- package/dist/cjs/utils/logging.cjs.map +1 -0
- package/dist/cjs/utils/toolCallNormalization.cjs +250 -0
- package/dist/cjs/utils/toolCallNormalization.cjs.map +1 -0
- package/dist/esm/agents/AgentContext.mjs +20 -3
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/spawnPath.mjs +95 -0
- package/dist/esm/common/spawnPath.mjs.map +1 -0
- package/dist/esm/graphs/Graph.mjs +87 -31
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/HandoffRegistry.mjs +141 -0
- package/dist/esm/graphs/HandoffRegistry.mjs.map +1 -0
- package/dist/esm/graphs/MultiAgentGraph.mjs +587 -184
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/graphs/phases/flushLoop.mjs +209 -0
- package/dist/esm/graphs/phases/flushLoop.mjs.map +1 -0
- package/dist/esm/graphs/phases/memoryFlushPhase.mjs +99 -0
- package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -0
- package/dist/esm/llm/bedrock/index.mjs +4 -3
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/main.mjs +21 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/memory/citations.mjs +64 -0
- package/dist/esm/memory/citations.mjs.map +1 -0
- package/dist/esm/memory/compositeBackend.mjs +58 -0
- package/dist/esm/memory/compositeBackend.mjs.map +1 -0
- package/dist/esm/memory/constants.mjs +198 -0
- package/dist/esm/memory/constants.mjs.map +1 -0
- package/dist/esm/memory/embeddings.mjs +148 -0
- package/dist/esm/memory/embeddings.mjs.map +1 -0
- package/dist/esm/memory/factory.mjs +93 -0
- package/dist/esm/memory/factory.mjs.map +1 -0
- package/dist/esm/memory/migrate.mjs +78 -0
- package/dist/esm/memory/migrate.mjs.map +1 -0
- package/dist/esm/memory/mmr.mjs +130 -0
- package/dist/esm/memory/mmr.mjs.map +1 -0
- package/dist/esm/memory/paths.mjs +207 -0
- package/dist/esm/memory/paths.mjs.map +1 -0
- package/dist/esm/memory/pgvectorStore.mjs +223 -0
- package/dist/esm/memory/pgvectorStore.mjs.map +1 -0
- package/dist/esm/memory/recallTracking.mjs +94 -0
- package/dist/esm/memory/recallTracking.mjs.map +1 -0
- package/dist/esm/memory/schema.sql +51 -0
- package/dist/esm/memory/temporalDecay.mjs +110 -0
- package/dist/esm/memory/temporalDecay.mjs.map +1 -0
- package/dist/esm/nodes/ApprovalGateNode.mjs +1 -1
- package/dist/esm/nodes/ApprovalGateNode.mjs.map +1 -1
- package/dist/esm/prompts/memoryFlushPrompt.mjs +44 -0
- package/dist/esm/prompts/memoryFlushPrompt.mjs.map +1 -0
- package/dist/esm/run.mjs +16 -3
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +4 -4
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/AskUser.mjs +6 -1
- package/dist/esm/tools/AskUser.mjs.map +1 -1
- package/dist/esm/tools/BrowserTools.mjs +1 -1
- package/dist/esm/tools/BrowserTools.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +128 -11
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/approval/constants.mjs +2 -2
- package/dist/esm/tools/approval/constants.mjs.map +1 -1
- package/dist/esm/tools/memory/index.mjs +46 -0
- package/dist/esm/tools/memory/index.mjs.map +1 -0
- package/dist/esm/tools/memory/memoryAppendTool.mjs +67 -0
- package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -0
- package/dist/esm/tools/memory/memoryGetTool.mjs +47 -0
- package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -0
- package/dist/esm/tools/memory/memorySearchTool.mjs +63 -0
- package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -0
- package/dist/esm/tools/memory/shared.mjs +98 -0
- package/dist/esm/tools/memory/shared.mjs.map +1 -0
- package/dist/esm/types/graph.mjs.map +1 -1
- package/dist/esm/utils/childAgentContext.mjs +237 -0
- package/dist/esm/utils/childAgentContext.mjs.map +1 -0
- package/dist/esm/utils/events.mjs +36 -5
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/esm/utils/finishReasons.mjs +41 -0
- package/dist/esm/utils/finishReasons.mjs.map +1 -0
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/dist/esm/utils/logging.mjs +31 -0
- package/dist/esm/utils/logging.mjs.map +1 -0
- package/dist/esm/utils/toolCallNormalization.mjs +247 -0
- package/dist/esm/utils/toolCallNormalization.mjs.map +1 -0
- package/dist/types/common/index.d.ts +1 -0
- package/dist/types/common/spawnPath.d.ts +59 -0
- package/dist/types/graphs/HandoffRegistry.d.ts +97 -0
- package/dist/types/graphs/MultiAgentGraph.d.ts +58 -18
- package/dist/types/graphs/index.d.ts +1 -0
- package/dist/types/graphs/phases/flushLoop.d.ts +106 -0
- package/dist/types/graphs/phases/memoryFlushPhase.d.ts +100 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/memory/__tests__/mockBackend.d.ts +40 -0
- package/dist/types/memory/citations.d.ts +39 -0
- package/dist/types/memory/compositeBackend.d.ts +30 -0
- package/dist/types/memory/constants.d.ts +121 -0
- package/dist/types/memory/embeddings.d.ts +15 -0
- package/dist/types/memory/factory.d.ts +23 -0
- package/dist/types/memory/index.d.ts +21 -0
- package/dist/types/memory/migrate.d.ts +14 -0
- package/dist/types/memory/mmr.d.ts +50 -0
- package/dist/types/memory/paths.d.ts +107 -0
- package/dist/types/memory/pgvectorStore.d.ts +56 -0
- package/dist/types/memory/recallTracking.d.ts +30 -0
- package/dist/types/memory/temporalDecay.d.ts +53 -0
- package/dist/types/memory/types.d.ts +182 -0
- package/dist/types/prompts/memoryFlushPrompt.d.ts +54 -0
- package/dist/types/run.d.ts +1 -0
- package/dist/types/tools/AskUser.d.ts +1 -1
- package/dist/types/tools/BrowserTools.d.ts +2 -2
- package/dist/types/tools/approval/constants.d.ts +2 -2
- package/dist/types/tools/memory/index.d.ts +39 -0
- package/dist/types/tools/memory/memoryAppendTool.d.ts +27 -0
- package/dist/types/tools/memory/memoryGetTool.d.ts +22 -0
- package/dist/types/tools/memory/memorySearchTool.d.ts +22 -0
- package/dist/types/tools/memory/shared.d.ts +106 -0
- package/dist/types/types/graph.d.ts +16 -3
- package/dist/types/utils/childAgentContext.d.ts +99 -0
- package/dist/types/utils/events.d.ts +21 -0
- package/dist/types/utils/finishReasons.d.ts +32 -0
- package/dist/types/utils/logging.d.ts +2 -0
- package/dist/types/utils/toolCallNormalization.d.ts +44 -0
- package/package.json +6 -4
- package/src/agents/AgentContext.ts +26 -3
- package/src/common/__tests__/enum.test.ts +4 -2
- package/src/common/__tests__/spawnPath.test.ts +110 -0
- package/src/common/index.ts +1 -0
- package/src/common/spawnPath.ts +101 -0
- package/src/graphs/Graph.ts +94 -43
- package/src/graphs/HandoffRegistry.ts +199 -0
- package/src/graphs/MultiAgentGraph.ts +694 -226
- package/src/graphs/__tests__/HandoffRegistry.test.ts +410 -0
- package/src/graphs/__tests__/multi-agent-delegate.test.ts +61 -16
- package/src/graphs/__tests__/multi-agent-edges.test.ts +4 -2
- package/src/graphs/__tests__/multi-agent-nested-subgraph.test.ts +221 -0
- package/src/graphs/__tests__/structured-output.integration.test.ts +212 -118
- package/src/graphs/contextManagement.e2e.test.ts +1 -1
- package/src/graphs/index.ts +1 -0
- package/src/graphs/phases/__tests__/flushLoop.test.ts +264 -0
- package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +37 -0
- package/src/graphs/phases/__tests__/runMemoryFlush.test.ts +150 -0
- package/src/graphs/phases/flushLoop.ts +303 -0
- package/src/graphs/phases/memoryFlushPhase.ts +209 -0
- package/src/index.ts +30 -1
- package/src/llm/bedrock/index.ts +4 -5
- package/src/memory/__tests__/citations.test.ts +61 -0
- package/src/memory/__tests__/compositeBackend.test.ts +79 -0
- package/src/memory/__tests__/isolation.test.ts +206 -0
- package/src/memory/__tests__/mmr.test.ts +148 -0
- package/src/memory/__tests__/mockBackend.ts +161 -0
- package/src/memory/__tests__/paths.test.ts +168 -0
- package/src/memory/__tests__/recallTracking.test.ts +96 -0
- package/src/memory/__tests__/temporalDecay.test.ts +151 -0
- package/src/memory/citations.ts +80 -0
- package/src/memory/compositeBackend.ts +99 -0
- package/src/memory/constants.ts +229 -0
- package/src/memory/embeddings.ts +188 -0
- package/src/memory/factory.ts +111 -0
- package/src/memory/index.ts +46 -0
- package/src/memory/migrate.ts +116 -0
- package/src/memory/mmr.ts +161 -0
- package/src/memory/paths.ts +258 -0
- package/src/memory/pgvectorStore.ts +324 -0
- package/src/memory/recallTracking.ts +127 -0
- package/src/memory/schema.sql +51 -0
- package/src/memory/temporalDecay.ts +134 -0
- package/src/memory/types.ts +185 -0
- package/src/nodes/ApprovalGateNode.ts +4 -10
- package/src/nodes/__tests__/ApprovalGateNode.test.ts +11 -20
- package/src/prompts/memoryFlushPrompt.ts +78 -0
- package/src/run.ts +17 -6
- package/src/scripts/test-bedrock-handoff-autonomous.ts +56 -20
- package/src/specs/agent-handoffs-bedrock.integration.test.ts +8 -5
- package/src/specs/agent-handoffs.test.ts +8 -2
- package/src/stream.ts +4 -6
- package/src/tools/AskUser.ts +7 -2
- package/src/tools/BrowserTools.ts +3 -5
- package/src/tools/ToolNode.ts +150 -13
- package/src/tools/__tests__/ToolApproval.test.ts +22 -9
- package/src/tools/approval/__tests__/constants.test.ts +4 -4
- package/src/tools/approval/constants.ts +2 -2
- package/src/tools/memory/__tests__/memoryTools.test.ts +205 -0
- package/src/tools/memory/index.ts +96 -0
- package/src/tools/memory/memoryAppendTool.ts +101 -0
- package/src/tools/memory/memoryGetTool.ts +53 -0
- package/src/tools/memory/memorySearchTool.ts +80 -0
- package/src/tools/memory/shared.ts +169 -0
- package/src/tools/search/search.test.ts +6 -1
- package/src/types/graph.ts +16 -3
- package/src/utils/__tests__/childAgentContext.test.ts +217 -0
- package/src/utils/__tests__/finishReasons.test.ts +55 -0
- package/src/utils/__tests__/toolCallNormalization.test.ts +181 -0
- package/src/utils/childAgentContext.ts +259 -0
- package/src/utils/events.ts +37 -4
- package/src/utils/finishReasons.ts +40 -0
- package/src/utils/llm.ts +0 -1
- package/src/utils/logging.ts +45 -8
- package/src/utils/toolCallNormalization.ts +271 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { DEFAULT_MEMORY_TABLE, DEFAULT_MEMORY_DIMENSIONS } from './constants.mjs';
|
|
5
|
+
import { getMemoryEmbedder } from './embeddings.mjs';
|
|
6
|
+
|
|
7
|
+
function resolveSchemaSqlPath() {
|
|
8
|
+
// Works under both ESM (import.meta.url) and compiled CJS (fall back to __dirname).
|
|
9
|
+
const meta = import.meta;
|
|
10
|
+
const metaUrl = typeof meta.url === 'string' ? meta.url : undefined;
|
|
11
|
+
if (metaUrl) {
|
|
12
|
+
return join(dirname(fileURLToPath(metaUrl)), 'schema.sql');
|
|
13
|
+
}
|
|
14
|
+
const globalDirname = globalThis
|
|
15
|
+
.__dirname;
|
|
16
|
+
return join(globalDirname ?? process.cwd(), 'schema.sql');
|
|
17
|
+
}
|
|
18
|
+
async function runMemoryMigration(opts) {
|
|
19
|
+
const table = opts.table ?? DEFAULT_MEMORY_TABLE;
|
|
20
|
+
const schemaPath = resolveSchemaSqlPath();
|
|
21
|
+
const schemaSql = readFileSync(schemaPath, 'utf8').replace(/agent_memories/g, table);
|
|
22
|
+
// Drop the legacy `(agent_id, path)` unique constraint BEFORE running
|
|
23
|
+
// the schema SQL. The schema creates the new
|
|
24
|
+
// `(agent_id, user_id, path)` NULLS-NOT-DISTINCT constraint, which
|
|
25
|
+
// must not collide with the old one. Dropping by the legacy name is a
|
|
26
|
+
// no-op on fresh installs where the constraint never existed, and on
|
|
27
|
+
// upgrades it cleanly frees the unique-index slot so the new
|
|
28
|
+
// constraint can be installed.
|
|
29
|
+
await opts.pool
|
|
30
|
+
.query(`ALTER TABLE ${table} DROP CONSTRAINT IF EXISTS ${table}_agent_path_uq`)
|
|
31
|
+
.catch(() => undefined);
|
|
32
|
+
await opts.pool.query(schemaSql);
|
|
33
|
+
// Adopt legacy rows if the table pre-existed with an older shape.
|
|
34
|
+
// Idempotent — every statement is no-op-safe on a fresh schema.
|
|
35
|
+
await opts.pool
|
|
36
|
+
.query(`ALTER TABLE ${table} ALTER COLUMN user_id DROP NOT NULL`)
|
|
37
|
+
.catch(() => undefined);
|
|
38
|
+
await opts.pool.query(`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`);
|
|
39
|
+
await opts.pool.query(`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS last_user_id TEXT`);
|
|
40
|
+
// Belt-and-suspenders: if the schema CREATE was skipped because the
|
|
41
|
+
// table already existed AND the new constraint was absent, add it
|
|
42
|
+
// explicitly. Swallow duplicate-object errors on happy-path re-runs.
|
|
43
|
+
await opts.pool
|
|
44
|
+
.query(`ALTER TABLE ${table}
|
|
45
|
+
ADD CONSTRAINT ${table}_agent_user_path_uq
|
|
46
|
+
UNIQUE NULLS NOT DISTINCT (agent_id, user_id, path)`)
|
|
47
|
+
.catch(() => undefined);
|
|
48
|
+
if (opts.skipEmbedderProbe)
|
|
49
|
+
return;
|
|
50
|
+
// Vector dimension sanity check — refuses to serve if the column width
|
|
51
|
+
// disagrees with the live embedder and the table has data. See plan §3.
|
|
52
|
+
const embedder = getMemoryEmbedder();
|
|
53
|
+
const liveDim = embedder.dimensions || DEFAULT_MEMORY_DIMENSIONS;
|
|
54
|
+
// pgvector stores the declared width directly in atttypmod (unlike varchar
|
|
55
|
+
// which uses atttypmod = N + VARHDRSZ). Reading it raw gives the true dim.
|
|
56
|
+
const dimResult = await opts.pool.query(`
|
|
57
|
+
SELECT atttypmod AS dim
|
|
58
|
+
FROM pg_attribute
|
|
59
|
+
WHERE attrelid = to_regclass($1)
|
|
60
|
+
AND attname = 'embedding'
|
|
61
|
+
`, [table]);
|
|
62
|
+
const columnDim = dimResult.rows[0]?.dim;
|
|
63
|
+
if (columnDim && Number(columnDim) !== liveDim) {
|
|
64
|
+
const countRes = await opts.pool.query(`SELECT COUNT(*)::int AS n FROM ${table}`);
|
|
65
|
+
const rowCount = Number(countRes.rows[0]?.n ?? 0);
|
|
66
|
+
if (rowCount === 0) {
|
|
67
|
+
await opts.pool.query(`ALTER TABLE ${table} ALTER COLUMN embedding TYPE VECTOR(${liveDim})`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
throw new Error(`Memory vector dimension mismatch: column is VECTOR(${columnDim}) but ` +
|
|
71
|
+
`embedder "${embedder.model}" produces ${liveDim}-d vectors, and ${rowCount} ` +
|
|
72
|
+
`rows exist. Refusing to serve memory. Admin must run a reindex job.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export { runMemoryMigration };
|
|
78
|
+
//# sourceMappingURL=migrate.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.mjs","sources":["../../../src/memory/migrate.ts"],"sourcesContent":["/**\n * Idempotent schema install + vector-dimension safety check.\n *\n * Called once at agents-library startup by host's `memoryStore.js` singleton\n * factory. Safe to call multiple times — every statement is `IF NOT EXISTS`.\n */\nimport type { Pool } from 'pg';\nimport { readFileSync } from 'fs';\nimport { join, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { DEFAULT_MEMORY_DIMENSIONS, DEFAULT_MEMORY_TABLE } from './constants';\nimport { getMemoryEmbedder } from './embeddings';\n\nexport interface MigrationOptions {\n pool: Pool;\n table?: string;\n /** If true, skip the live embedder probe (tests). */\n skipEmbedderProbe?: boolean;\n}\n\nfunction resolveSchemaSqlPath(): string {\n // Works under both ESM (import.meta.url) and compiled CJS (fall back to __dirname).\n const meta = import.meta as unknown as { url?: string };\n const metaUrl = typeof meta.url === 'string' ? meta.url : undefined;\n if (metaUrl) {\n return join(dirname(fileURLToPath(metaUrl)), 'schema.sql');\n }\n const globalDirname = (globalThis as unknown as { __dirname?: string })\n .__dirname;\n return join(globalDirname ?? process.cwd(), 'schema.sql');\n}\n\nexport async function runMemoryMigration(\n opts: MigrationOptions\n): Promise<void> {\n const table = opts.table ?? DEFAULT_MEMORY_TABLE;\n const schemaPath = resolveSchemaSqlPath();\n const schemaSql = readFileSync(schemaPath, 'utf8').replace(\n /agent_memories/g,\n table\n );\n\n // Drop the legacy `(agent_id, path)` unique constraint BEFORE running\n // the schema SQL. The schema creates the new\n // `(agent_id, user_id, path)` NULLS-NOT-DISTINCT constraint, which\n // must not collide with the old one. Dropping by the legacy name is a\n // no-op on fresh installs where the constraint never existed, and on\n // upgrades it cleanly frees the unique-index slot so the new\n // constraint can be installed.\n await opts.pool\n .query(\n `ALTER TABLE ${table} DROP CONSTRAINT IF EXISTS ${table}_agent_path_uq`\n )\n .catch(() => undefined);\n\n await opts.pool.query(schemaSql);\n\n // Adopt legacy rows if the table pre-existed with an older shape.\n // Idempotent — every statement is no-op-safe on a fresh schema.\n await opts.pool\n .query(`ALTER TABLE ${table} ALTER COLUMN user_id DROP NOT NULL`)\n .catch(() => undefined);\n await opts.pool.query(\n `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`\n );\n await opts.pool.query(\n `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS last_user_id TEXT`\n );\n // Belt-and-suspenders: if the schema CREATE was skipped because the\n // table already existed AND the new constraint was absent, add it\n // explicitly. Swallow duplicate-object errors on happy-path re-runs.\n await opts.pool\n .query(\n `ALTER TABLE ${table}\n ADD CONSTRAINT ${table}_agent_user_path_uq\n UNIQUE NULLS NOT DISTINCT (agent_id, user_id, path)`\n )\n .catch(() => undefined);\n\n if (opts.skipEmbedderProbe) return;\n\n // Vector dimension sanity check — refuses to serve if the column width\n // disagrees with the live embedder and the table has data. See plan §3.\n const embedder = getMemoryEmbedder();\n const liveDim = embedder.dimensions || DEFAULT_MEMORY_DIMENSIONS;\n\n // pgvector stores the declared width directly in atttypmod (unlike varchar\n // which uses atttypmod = N + VARHDRSZ). Reading it raw gives the true dim.\n const dimResult = await opts.pool.query(\n `\n SELECT atttypmod AS dim\n FROM pg_attribute\n WHERE attrelid = to_regclass($1)\n AND attname = 'embedding'\n `,\n [table]\n );\n const columnDim = dimResult.rows[0]?.dim;\n if (columnDim && Number(columnDim) !== liveDim) {\n const countRes = await opts.pool.query(\n `SELECT COUNT(*)::int AS n FROM ${table}`\n );\n const rowCount = Number(countRes.rows[0]?.n ?? 0);\n if (rowCount === 0) {\n await opts.pool.query(\n `ALTER TABLE ${table} ALTER COLUMN embedding TYPE VECTOR(${liveDim})`\n );\n } else {\n throw new Error(\n `Memory vector dimension mismatch: column is VECTOR(${columnDim}) but ` +\n `embedder \"${embedder.model}\" produces ${liveDim}-d vectors, and ${rowCount} ` +\n `rows exist. Refusing to serve memory. Admin must run a reindex job.`\n );\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maximal Marginal Relevance (MMR) re-ranking — Phase 2.
|
|
3
|
+
*
|
|
4
|
+
* Ported from upstream `extensions/memory-core/src/memory/mmr.ts` with
|
|
5
|
+
* minor adaptation for our `MemoryEntry` shape (content vs snippet, id vs
|
|
6
|
+
* path+startLine). Behavior is identical: normalize scores, iteratively
|
|
7
|
+
* pick the item that maximizes `λ * relevance - (1-λ) * max_similarity`
|
|
8
|
+
* using Jaccard on tokenized content.
|
|
9
|
+
*
|
|
10
|
+
* @see Carbonell & Goldstein, "The Use of MMR, Diversity-Based Reranking" (1998)
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_MMR_CONFIG = {
|
|
13
|
+
enabled: false,
|
|
14
|
+
lambda: 0.7,
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* CJK Unified Ideographs + Extension A, Hiragana/Katakana, Hangul.
|
|
18
|
+
* These lack whitespace boundaries so we must tokenize them differently.
|
|
19
|
+
*/
|
|
20
|
+
const CJK_RE = /[\u3040-\u309f\u30a0-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uac00-\ud7af\u1100-\u11ff]/;
|
|
21
|
+
/**
|
|
22
|
+
* Tokenize content into a set for Jaccard similarity.
|
|
23
|
+
*
|
|
24
|
+
* ASCII: alphanumeric + underscore runs, lowercased.
|
|
25
|
+
* CJK: each char becomes a unigram; consecutive pairs become a bigram.
|
|
26
|
+
* Non-adjacent CJK chars (e.g. `我a好`) do NOT form a bigram.
|
|
27
|
+
*/
|
|
28
|
+
function tokenize(text) {
|
|
29
|
+
const lower = (text ?? '').toLowerCase();
|
|
30
|
+
const ascii = lower.match(/[a-z0-9_]+/g) ?? [];
|
|
31
|
+
const chars = Array.from(lower);
|
|
32
|
+
const cjkData = [];
|
|
33
|
+
for (let i = 0; i < chars.length; i++) {
|
|
34
|
+
if (CJK_RE.test(chars[i]))
|
|
35
|
+
cjkData.push({ char: chars[i], index: i });
|
|
36
|
+
}
|
|
37
|
+
const bigrams = [];
|
|
38
|
+
for (let i = 0; i < cjkData.length - 1; i++) {
|
|
39
|
+
if (cjkData[i + 1].index === cjkData[i].index + 1) {
|
|
40
|
+
bigrams.push(cjkData[i].char + cjkData[i + 1].char);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return new Set([...ascii, ...bigrams, ...cjkData.map((d) => d.char)]);
|
|
44
|
+
}
|
|
45
|
+
function jaccardSimilarity(a, b) {
|
|
46
|
+
if (a.size === 0 && b.size === 0)
|
|
47
|
+
return 1;
|
|
48
|
+
if (a.size === 0 || b.size === 0)
|
|
49
|
+
return 0;
|
|
50
|
+
const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a];
|
|
51
|
+
let intersection = 0;
|
|
52
|
+
for (const t of smaller)
|
|
53
|
+
if (larger.has(t))
|
|
54
|
+
intersection++;
|
|
55
|
+
const union = a.size + b.size - intersection;
|
|
56
|
+
return union === 0 ? 0 : intersection / union;
|
|
57
|
+
}
|
|
58
|
+
function textSimilarity(a, b) {
|
|
59
|
+
return jaccardSimilarity(tokenize(a), tokenize(b));
|
|
60
|
+
}
|
|
61
|
+
function computeMMRScore(relevance, maxSimilarity, lambda) {
|
|
62
|
+
return lambda * relevance - (1 - lambda) * maxSimilarity;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Re-rank items using MMR. Returns a new array in MMR order.
|
|
66
|
+
*/
|
|
67
|
+
function mmrRerank(items, config = {}) {
|
|
68
|
+
const enabled = config.enabled ?? DEFAULT_MMR_CONFIG.enabled;
|
|
69
|
+
const rawLambda = config.lambda ?? DEFAULT_MMR_CONFIG.lambda;
|
|
70
|
+
if (!enabled || items.length <= 1)
|
|
71
|
+
return [...items];
|
|
72
|
+
const lambda = Math.max(0, Math.min(1, rawLambda));
|
|
73
|
+
if (lambda === 1)
|
|
74
|
+
return [...items].sort((a, b) => b.score - a.score);
|
|
75
|
+
const tokenCache = new Map();
|
|
76
|
+
for (const item of items)
|
|
77
|
+
tokenCache.set(item.id, tokenize(item.content));
|
|
78
|
+
const scores = items.map((i) => i.score);
|
|
79
|
+
const maxScore = Math.max(...scores);
|
|
80
|
+
const minScore = Math.min(...scores);
|
|
81
|
+
const range = maxScore - minScore;
|
|
82
|
+
const normalize = (s) => range === 0 ? 1 : (s - minScore) / range;
|
|
83
|
+
const selected = [];
|
|
84
|
+
const remaining = new Set(items);
|
|
85
|
+
while (remaining.size > 0) {
|
|
86
|
+
let best = null;
|
|
87
|
+
let bestMMR = -Infinity;
|
|
88
|
+
for (const cand of remaining) {
|
|
89
|
+
const rel = normalize(cand.score);
|
|
90
|
+
const candTokens = tokenCache.get(cand.id);
|
|
91
|
+
let maxSim = 0;
|
|
92
|
+
for (const sel of selected) {
|
|
93
|
+
const sim = jaccardSimilarity(candTokens, tokenCache.get(sel.id));
|
|
94
|
+
if (sim > maxSim)
|
|
95
|
+
maxSim = sim;
|
|
96
|
+
}
|
|
97
|
+
const mmr = computeMMRScore(rel, maxSim, lambda);
|
|
98
|
+
if (mmr > bestMMR ||
|
|
99
|
+
(mmr === bestMMR && cand.score > (best?.score ?? -Infinity))) {
|
|
100
|
+
bestMMR = mmr;
|
|
101
|
+
best = cand;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!best)
|
|
105
|
+
break;
|
|
106
|
+
selected.push(best);
|
|
107
|
+
remaining.delete(best);
|
|
108
|
+
}
|
|
109
|
+
return selected;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Adapter: apply MMR to an array of MemoryEntry-shaped hits.
|
|
113
|
+
*
|
|
114
|
+
* Uses (path|id|index) as the stable ID so two hits from the same file at
|
|
115
|
+
* different content still get distinct MMR identities.
|
|
116
|
+
*/
|
|
117
|
+
function applyMMRToMemoryHits(results, config = {}) {
|
|
118
|
+
if (results.length <= 1)
|
|
119
|
+
return results;
|
|
120
|
+
const byId = new Map();
|
|
121
|
+
const items = results.map((r, i) => {
|
|
122
|
+
const id = `${r.path}#${r.id}#${i}`;
|
|
123
|
+
byId.set(id, r);
|
|
124
|
+
return { id, score: r.score, content: r.content };
|
|
125
|
+
});
|
|
126
|
+
return mmrRerank(items, config).map((m) => byId.get(m.id));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { DEFAULT_MMR_CONFIG, applyMMRToMemoryHits, computeMMRScore, jaccardSimilarity, mmrRerank, textSimilarity, tokenize };
|
|
130
|
+
//# sourceMappingURL=mmr.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mmr.mjs","sources":["../../../src/memory/mmr.ts"],"sourcesContent":["/**\n * Maximal Marginal Relevance (MMR) re-ranking — Phase 2.\n *\n * Ported from upstream `extensions/memory-core/src/memory/mmr.ts` with\n * minor adaptation for our `MemoryEntry` shape (content vs snippet, id vs\n * path+startLine). Behavior is identical: normalize scores, iteratively\n * pick the item that maximizes `λ * relevance - (1-λ) * max_similarity`\n * using Jaccard on tokenized content.\n *\n * @see Carbonell & Goldstein, \"The Use of MMR, Diversity-Based Reranking\" (1998)\n */\n\nexport interface MMRConfig {\n /** Opt-in. Upstream default is false. */\n enabled: boolean;\n /** 0 = max diversity, 1 = max relevance. Upstream default 0.7. */\n lambda: number;\n}\n\nexport const DEFAULT_MMR_CONFIG: MMRConfig = {\n enabled: false,\n lambda: 0.7,\n};\n\n/**\n * CJK Unified Ideographs + Extension A, Hiragana/Katakana, Hangul.\n * These lack whitespace boundaries so we must tokenize them differently.\n */\nconst CJK_RE =\n /[\\u3040-\\u309f\\u30a0-\\u30ff\\u3400-\\u4dbf\\u4e00-\\u9fff\\uac00-\\ud7af\\u1100-\\u11ff]/;\n\n/**\n * Tokenize content into a set for Jaccard similarity.\n *\n * ASCII: alphanumeric + underscore runs, lowercased.\n * CJK: each char becomes a unigram; consecutive pairs become a bigram.\n * Non-adjacent CJK chars (e.g. `我a好`) do NOT form a bigram.\n */\nexport function tokenize(text: string): Set<string> {\n const lower = (text ?? '').toLowerCase();\n const ascii = lower.match(/[a-z0-9_]+/g) ?? [];\n\n const chars = Array.from(lower);\n const cjkData: Array<{ char: string; index: number }> = [];\n for (let i = 0; i < chars.length; i++) {\n if (CJK_RE.test(chars[i])) cjkData.push({ char: chars[i], index: i });\n }\n\n const bigrams: string[] = [];\n for (let i = 0; i < cjkData.length - 1; i++) {\n if (cjkData[i + 1].index === cjkData[i].index + 1) {\n bigrams.push(cjkData[i].char + cjkData[i + 1].char);\n }\n }\n\n return new Set([...ascii, ...bigrams, ...cjkData.map((d) => d.char)]);\n}\n\nexport function jaccardSimilarity(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 && b.size === 0) return 1;\n if (a.size === 0 || b.size === 0) return 0;\n\n const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a];\n let intersection = 0;\n for (const t of smaller) if (larger.has(t)) intersection++;\n const union = a.size + b.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n\nexport function textSimilarity(a: string, b: string): number {\n return jaccardSimilarity(tokenize(a), tokenize(b));\n}\n\nexport function computeMMRScore(\n relevance: number,\n maxSimilarity: number,\n lambda: number\n): number {\n return lambda * relevance - (1 - lambda) * maxSimilarity;\n}\n\nexport interface MMRItem {\n id: string;\n score: number;\n content: string;\n}\n\n/**\n * Re-rank items using MMR. Returns a new array in MMR order.\n */\nexport function mmrRerank<T extends MMRItem>(\n items: T[],\n config: Partial<MMRConfig> = {}\n): T[] {\n const enabled = config.enabled ?? DEFAULT_MMR_CONFIG.enabled;\n const rawLambda = config.lambda ?? DEFAULT_MMR_CONFIG.lambda;\n\n if (!enabled || items.length <= 1) return [...items];\n\n const lambda = Math.max(0, Math.min(1, rawLambda));\n if (lambda === 1) return [...items].sort((a, b) => b.score - a.score);\n\n const tokenCache = new Map<string, Set<string>>();\n for (const item of items) tokenCache.set(item.id, tokenize(item.content));\n\n const scores = items.map((i) => i.score);\n const maxScore = Math.max(...scores);\n const minScore = Math.min(...scores);\n const range = maxScore - minScore;\n const normalize = (s: number): number =>\n range === 0 ? 1 : (s - minScore) / range;\n\n const selected: T[] = [];\n const remaining = new Set(items);\n\n while (remaining.size > 0) {\n let best: T | null = null;\n let bestMMR = -Infinity;\n for (const cand of remaining) {\n const rel = normalize(cand.score);\n const candTokens = tokenCache.get(cand.id)!;\n let maxSim = 0;\n for (const sel of selected) {\n const sim = jaccardSimilarity(candTokens, tokenCache.get(sel.id)!);\n if (sim > maxSim) maxSim = sim;\n }\n const mmr = computeMMRScore(rel, maxSim, lambda);\n if (\n mmr > bestMMR ||\n (mmr === bestMMR && cand.score > (best?.score ?? -Infinity))\n ) {\n bestMMR = mmr;\n best = cand;\n }\n }\n if (!best) break;\n selected.push(best);\n remaining.delete(best);\n }\n\n return selected;\n}\n\n/**\n * Adapter: apply MMR to an array of MemoryEntry-shaped hits.\n *\n * Uses (path|id|index) as the stable ID so two hits from the same file at\n * different content still get distinct MMR identities.\n */\nexport function applyMMRToMemoryHits<\n T extends { id: string; path: string; content: string; score: number },\n>(results: T[], config: Partial<MMRConfig> = {}): T[] {\n if (results.length <= 1) return results;\n const byId = new Map<string, T>();\n const items: MMRItem[] = results.map((r, i) => {\n const id = `${r.path}#${r.id}#${i}`;\n byId.set(id, r);\n return { id, score: r.score, content: r.content };\n });\n return mmrRerank(items, config).map((m) => byId.get(m.id)!);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { MEMORY_PATH_PREFIX } from './constants.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Autonomous memory — canonical path whitelist + tier utilities.
|
|
5
|
+
*
|
|
6
|
+
* Single source of truth for the 8 stable memory documents. Every write
|
|
7
|
+
* goes through {@link assertWritablePath}; every reader can ask
|
|
8
|
+
* {@link getTierForPath} what a row belongs to. Adding or removing a
|
|
9
|
+
* path means editing this file — and exactly this file.
|
|
10
|
+
*
|
|
11
|
+
* ## Why a whitelist?
|
|
12
|
+
*
|
|
13
|
+
* Earlier upstream-faithful designs used date-keyed files
|
|
14
|
+
* (`memory/YYYY-MM-DD.md`), which have three problems for a persistent
|
|
15
|
+
* multi-user agent:
|
|
16
|
+
*
|
|
17
|
+
* 1. **Unbounded growth** — one row per day per agent per user, forever.
|
|
18
|
+
* 2. **LLM routing ambiguity** — "which date file do I read?" has no
|
|
19
|
+
* good answer, so the model reads (and ranks against) all of them.
|
|
20
|
+
* 3. **No natural deduplication** — the same fact written across three
|
|
21
|
+
* days returns three near-identical hits.
|
|
22
|
+
*
|
|
23
|
+
* With 8 stable canonical documents the LLM knows *exactly* where to
|
|
24
|
+
* write ("is this a preference? → `memory/user/preferences.md`") and
|
|
25
|
+
* each row accumulates via UPSERT instead of piling up new rows.
|
|
26
|
+
*
|
|
27
|
+
* ## Two tiers
|
|
28
|
+
*
|
|
29
|
+
* - **Agent tier** (`memory/agent/*`) — operational knowledge that
|
|
30
|
+
* every caller of the agent benefits from. Stored with `user_id = NULL`.
|
|
31
|
+
* Shared across all users of a collaborative agent; the only tier that
|
|
32
|
+
* exists for isolated/autonomous agents with no invoker.
|
|
33
|
+
*
|
|
34
|
+
* - **User tier** (`memory/user/*`) — per-invoker personalization.
|
|
35
|
+
* Stored with `user_id = <caller>`. Read path filters so User A never
|
|
36
|
+
* sees User B's rows, even for the same agent. Not writable unless the
|
|
37
|
+
* flush scope carries a non-empty `userId`.
|
|
38
|
+
*
|
|
39
|
+
* The tier of a path is a function of its prefix (`memory/agent/` vs
|
|
40
|
+
* `memory/user/`), so adding a new document is one line here + the
|
|
41
|
+
* constant array entry; no store or route code has to change.
|
|
42
|
+
*/
|
|
43
|
+
/** Agent-tier documents — shared across all users of the agent. */
|
|
44
|
+
const MEMORY_AGENT_PATHS = Object.freeze([
|
|
45
|
+
Object.freeze({
|
|
46
|
+
path: 'memory/agent/playbook.md',
|
|
47
|
+
tier: 'agent',
|
|
48
|
+
label: 'Playbook',
|
|
49
|
+
description: 'Successful task patterns and workflows that have worked for this agent — ' +
|
|
50
|
+
'reusable recipes, known-good tool sequences, approaches that consistently ' +
|
|
51
|
+
'produce the right result.',
|
|
52
|
+
}),
|
|
53
|
+
Object.freeze({
|
|
54
|
+
path: 'memory/agent/pitfalls.md',
|
|
55
|
+
tier: 'agent',
|
|
56
|
+
label: 'Pitfalls',
|
|
57
|
+
description: 'Tool failures, error recoveries, schema mistakes, and invalid argument ' +
|
|
58
|
+
'shapes the agent has seen — so the same mistake is not repeated on future ' +
|
|
59
|
+
'turns. One note per distinct failure mode.',
|
|
60
|
+
}),
|
|
61
|
+
Object.freeze({
|
|
62
|
+
path: 'memory/agent/domain.md',
|
|
63
|
+
tier: 'agent',
|
|
64
|
+
label: 'Domain',
|
|
65
|
+
description: 'Durable facts about the systems, APIs, data models, and business rules ' +
|
|
66
|
+
'this agent operates on — things that are true independent of any one user ' +
|
|
67
|
+
'and that the agent repeatedly needs to know.',
|
|
68
|
+
}),
|
|
69
|
+
Object.freeze({
|
|
70
|
+
path: 'memory/agent/style.md',
|
|
71
|
+
tier: 'agent',
|
|
72
|
+
label: 'Style',
|
|
73
|
+
description: 'Tone, formatting, and response-shape conventions the agent has converged ' +
|
|
74
|
+
'on across the whole user base. Do NOT write user-specific preferences here ' +
|
|
75
|
+
'(those belong under the user tier).',
|
|
76
|
+
}),
|
|
77
|
+
]);
|
|
78
|
+
/** User-tier documents — per-invoker, never shared across users. */
|
|
79
|
+
const MEMORY_USER_PATHS = Object.freeze([
|
|
80
|
+
Object.freeze({
|
|
81
|
+
path: 'memory/user/profile.md',
|
|
82
|
+
tier: 'user',
|
|
83
|
+
label: 'Profile',
|
|
84
|
+
description: "This specific user's identity — name, role, team, sign-off name, " +
|
|
85
|
+
'responsibilities, stable facts about who they are. Only facts ' +
|
|
86
|
+
'volunteered by the user themselves.',
|
|
87
|
+
}),
|
|
88
|
+
Object.freeze({
|
|
89
|
+
path: 'memory/user/preferences.md',
|
|
90
|
+
tier: 'user',
|
|
91
|
+
label: 'Preferences',
|
|
92
|
+
description: 'How THIS user wants things done — preferred formats, verbosity, tone, ' +
|
|
93
|
+
'cadence, output length, language conventions. Explicit corrections they ' +
|
|
94
|
+
'have given count as preferences.',
|
|
95
|
+
}),
|
|
96
|
+
Object.freeze({
|
|
97
|
+
path: 'memory/user/projects.md',
|
|
98
|
+
tier: 'user',
|
|
99
|
+
label: 'Projects',
|
|
100
|
+
description: "This user's current initiatives, ongoing work, deadlines, and active " +
|
|
101
|
+
'project context. Include dates in the prose when relevant. Retire entries ' +
|
|
102
|
+
'by overwriting them on future flushes when the user indicates a project is done.',
|
|
103
|
+
}),
|
|
104
|
+
Object.freeze({
|
|
105
|
+
path: 'memory/user/references.md',
|
|
106
|
+
tier: 'user',
|
|
107
|
+
label: 'References',
|
|
108
|
+
description: 'External systems, dashboards, accounts, links, channels, and resources ' +
|
|
109
|
+
"THIS user has pointed the agent at — the 'where to look' pointers that " +
|
|
110
|
+
'make later turns more efficient.',
|
|
111
|
+
}),
|
|
112
|
+
]);
|
|
113
|
+
/** All 8 canonical documents in display order: agent tier first, user tier second. */
|
|
114
|
+
const MEMORY_ALL_PATHS = Object.freeze([
|
|
115
|
+
...MEMORY_AGENT_PATHS,
|
|
116
|
+
...MEMORY_USER_PATHS,
|
|
117
|
+
]);
|
|
118
|
+
/** Fast O(1) lookup map: path → descriptor. */
|
|
119
|
+
const PATH_INDEX = new Map(MEMORY_ALL_PATHS.map((p) => [p.path, p]));
|
|
120
|
+
/** Set of all whitelisted paths — for `has()` lookups. */
|
|
121
|
+
const MEMORY_WRITABLE_PATHS = new Set(MEMORY_ALL_PATHS.map((p) => p.path));
|
|
122
|
+
/** Returns the descriptor for a canonical path, or `undefined` if not on the whitelist. */
|
|
123
|
+
function getPathDescriptor(path) {
|
|
124
|
+
return PATH_INDEX.get(path);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Returns the tier a given path belongs to.
|
|
128
|
+
*
|
|
129
|
+
* For unknown paths (e.g. legacy date-keyed rows that predate this schema),
|
|
130
|
+
* falls back to inspecting the `memory/agent/` or `memory/user/` prefix.
|
|
131
|
+
* Anything that matches neither is classified as `agent` — it will surface
|
|
132
|
+
* in the admin UI under the agent-tier header and stay read-only there.
|
|
133
|
+
*/
|
|
134
|
+
function getTierForPath(path) {
|
|
135
|
+
const descriptor = PATH_INDEX.get(path);
|
|
136
|
+
if (descriptor)
|
|
137
|
+
return descriptor.tier;
|
|
138
|
+
if (path.startsWith('memory/user/'))
|
|
139
|
+
return 'user';
|
|
140
|
+
return 'agent';
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Paths that are legal to write **for this caller's scope**.
|
|
144
|
+
*
|
|
145
|
+
* If `scope.userId` is absent (isolated/autonomous agent), the user-tier
|
|
146
|
+
* paths are dropped. This is the list the flush prompt renders into its
|
|
147
|
+
* rubric — the LLM never sees paths it cannot write to.
|
|
148
|
+
*/
|
|
149
|
+
function getWritablePathsForScope(scope) {
|
|
150
|
+
if (scope.userId == null || scope.userId === '') {
|
|
151
|
+
return MEMORY_AGENT_PATHS;
|
|
152
|
+
}
|
|
153
|
+
return MEMORY_ALL_PATHS;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Validates a write path against the whitelist AND the caller's scope.
|
|
157
|
+
*
|
|
158
|
+
* Throws with an actionable message when:
|
|
159
|
+
* - the path is missing the `memory/` prefix
|
|
160
|
+
* - the path is not on the 8-doc whitelist
|
|
161
|
+
* - the path is user-tier but `scope.userId` is missing
|
|
162
|
+
*
|
|
163
|
+
* The store's append() is the single call site; other callers should
|
|
164
|
+
* never hit this directly. Kept as a standalone function for testability.
|
|
165
|
+
*/
|
|
166
|
+
function assertWritablePath(path, scope) {
|
|
167
|
+
if (!path || !path.startsWith(MEMORY_PATH_PREFIX)) {
|
|
168
|
+
throw new Error(`memory_append path must start with "${MEMORY_PATH_PREFIX}" (received "${path}")`);
|
|
169
|
+
}
|
|
170
|
+
const descriptor = PATH_INDEX.get(path);
|
|
171
|
+
if (!descriptor) {
|
|
172
|
+
throw new Error(`memory_append path "${path}" is not on the canonical whitelist. ` +
|
|
173
|
+
`Allowed: ${MEMORY_ALL_PATHS.map((p) => p.path).join(', ')}`);
|
|
174
|
+
}
|
|
175
|
+
if (descriptor.tier === 'user' &&
|
|
176
|
+
(scope.userId == null || scope.userId === '')) {
|
|
177
|
+
throw new Error(`memory_append to user-tier path "${path}" requires a caller userId in scope, ` +
|
|
178
|
+
`but scope.userId is missing. This path is private to a specific user and ` +
|
|
179
|
+
`cannot be written from an isolated/autonomous context.`);
|
|
180
|
+
}
|
|
181
|
+
return descriptor;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Renders the whitelist as a compact bullet list, used inside the flush
|
|
185
|
+
* prompt so the LLM sees the authoritative rubric at inference time.
|
|
186
|
+
*
|
|
187
|
+
* Output shape:
|
|
188
|
+
* - memory/agent/playbook.md [Playbook, shared] — <description>
|
|
189
|
+
* - memory/agent/pitfalls.md [Pitfalls, shared] — <description>
|
|
190
|
+
* ...
|
|
191
|
+
*
|
|
192
|
+
* The `[Label, shared|private]` tag tells the model whether the doc is
|
|
193
|
+
* visible to all users (agent tier) or only to the caller (user tier).
|
|
194
|
+
* Filtered to only the paths writable for the given scope.
|
|
195
|
+
*/
|
|
196
|
+
function renderPathsRubric(scope) {
|
|
197
|
+
const writable = getWritablePathsForScope(scope);
|
|
198
|
+
return writable
|
|
199
|
+
.map((p) => {
|
|
200
|
+
const visibility = p.tier === 'agent' ? 'shared' : 'private';
|
|
201
|
+
return `- ${p.path} [${p.label}, ${visibility}] — ${p.description}`;
|
|
202
|
+
})
|
|
203
|
+
.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export { MEMORY_AGENT_PATHS, MEMORY_ALL_PATHS, MEMORY_USER_PATHS, MEMORY_WRITABLE_PATHS, assertWritablePath, getPathDescriptor, getTierForPath, getWritablePathsForScope, renderPathsRubric };
|
|
207
|
+
//# sourceMappingURL=paths.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.mjs","sources":["../../../src/memory/paths.ts"],"sourcesContent":["/**\n * Autonomous memory — canonical path whitelist + tier utilities.\n *\n * Single source of truth for the 8 stable memory documents. Every write\n * goes through {@link assertWritablePath}; every reader can ask\n * {@link getTierForPath} what a row belongs to. Adding or removing a\n * path means editing this file — and exactly this file.\n *\n * ## Why a whitelist?\n *\n * Earlier upstream-faithful designs used date-keyed files\n * (`memory/YYYY-MM-DD.md`), which have three problems for a persistent\n * multi-user agent:\n *\n * 1. **Unbounded growth** — one row per day per agent per user, forever.\n * 2. **LLM routing ambiguity** — \"which date file do I read?\" has no\n * good answer, so the model reads (and ranks against) all of them.\n * 3. **No natural deduplication** — the same fact written across three\n * days returns three near-identical hits.\n *\n * With 8 stable canonical documents the LLM knows *exactly* where to\n * write (\"is this a preference? → `memory/user/preferences.md`\") and\n * each row accumulates via UPSERT instead of piling up new rows.\n *\n * ## Two tiers\n *\n * - **Agent tier** (`memory/agent/*`) — operational knowledge that\n * every caller of the agent benefits from. Stored with `user_id = NULL`.\n * Shared across all users of a collaborative agent; the only tier that\n * exists for isolated/autonomous agents with no invoker.\n *\n * - **User tier** (`memory/user/*`) — per-invoker personalization.\n * Stored with `user_id = <caller>`. Read path filters so User A never\n * sees User B's rows, even for the same agent. Not writable unless the\n * flush scope carries a non-empty `userId`.\n *\n * The tier of a path is a function of its prefix (`memory/agent/` vs\n * `memory/user/`), so adding a new document is one line here + the\n * constant array entry; no store or route code has to change.\n */\n\nimport { MEMORY_PATH_PREFIX } from './constants';\nimport type { MemoryScope } from './types';\n\n/** Tier discriminator — the two kinds of memory a row can belong to. */\nexport type MemoryTier = 'agent' | 'user';\n\n/** Every canonical document the flush phase is allowed to write. */\nexport interface MemoryPathDescriptor {\n /** Full path including the `memory/` prefix. */\n path: string;\n /** Which tier the path lives under. */\n tier: MemoryTier;\n /** Short label shown in UI tier badges and the flush prompt rubric. */\n label: string;\n /** One-line description — fed to the LLM to make routing unambiguous. */\n description: string;\n}\n\n/** Agent-tier documents — shared across all users of the agent. */\nexport const MEMORY_AGENT_PATHS: readonly MemoryPathDescriptor[] =\n Object.freeze([\n Object.freeze({\n path: 'memory/agent/playbook.md',\n tier: 'agent' as const,\n label: 'Playbook',\n description:\n 'Successful task patterns and workflows that have worked for this agent — ' +\n 'reusable recipes, known-good tool sequences, approaches that consistently ' +\n 'produce the right result.',\n }),\n Object.freeze({\n path: 'memory/agent/pitfalls.md',\n tier: 'agent' as const,\n label: 'Pitfalls',\n description:\n 'Tool failures, error recoveries, schema mistakes, and invalid argument ' +\n 'shapes the agent has seen — so the same mistake is not repeated on future ' +\n 'turns. One note per distinct failure mode.',\n }),\n Object.freeze({\n path: 'memory/agent/domain.md',\n tier: 'agent' as const,\n label: 'Domain',\n description:\n 'Durable facts about the systems, APIs, data models, and business rules ' +\n 'this agent operates on — things that are true independent of any one user ' +\n 'and that the agent repeatedly needs to know.',\n }),\n Object.freeze({\n path: 'memory/agent/style.md',\n tier: 'agent' as const,\n label: 'Style',\n description:\n 'Tone, formatting, and response-shape conventions the agent has converged ' +\n 'on across the whole user base. Do NOT write user-specific preferences here ' +\n '(those belong under the user tier).',\n }),\n ]);\n\n/** User-tier documents — per-invoker, never shared across users. */\nexport const MEMORY_USER_PATHS: readonly MemoryPathDescriptor[] = Object.freeze(\n [\n Object.freeze({\n path: 'memory/user/profile.md',\n tier: 'user' as const,\n label: 'Profile',\n description:\n \"This specific user's identity — name, role, team, sign-off name, \" +\n 'responsibilities, stable facts about who they are. Only facts ' +\n 'volunteered by the user themselves.',\n }),\n Object.freeze({\n path: 'memory/user/preferences.md',\n tier: 'user' as const,\n label: 'Preferences',\n description:\n 'How THIS user wants things done — preferred formats, verbosity, tone, ' +\n 'cadence, output length, language conventions. Explicit corrections they ' +\n 'have given count as preferences.',\n }),\n Object.freeze({\n path: 'memory/user/projects.md',\n tier: 'user' as const,\n label: 'Projects',\n description:\n \"This user's current initiatives, ongoing work, deadlines, and active \" +\n 'project context. Include dates in the prose when relevant. Retire entries ' +\n 'by overwriting them on future flushes when the user indicates a project is done.',\n }),\n Object.freeze({\n path: 'memory/user/references.md',\n tier: 'user' as const,\n label: 'References',\n description:\n 'External systems, dashboards, accounts, links, channels, and resources ' +\n \"THIS user has pointed the agent at — the 'where to look' pointers that \" +\n 'make later turns more efficient.',\n }),\n ]\n);\n\n/** All 8 canonical documents in display order: agent tier first, user tier second. */\nexport const MEMORY_ALL_PATHS: readonly MemoryPathDescriptor[] = Object.freeze([\n ...MEMORY_AGENT_PATHS,\n ...MEMORY_USER_PATHS,\n]);\n\n/** Fast O(1) lookup map: path → descriptor. */\nconst PATH_INDEX: ReadonlyMap<string, MemoryPathDescriptor> = new Map(\n MEMORY_ALL_PATHS.map((p) => [p.path, p])\n);\n\n/** Set of all whitelisted paths — for `has()` lookups. */\nexport const MEMORY_WRITABLE_PATHS: ReadonlySet<string> = new Set(\n MEMORY_ALL_PATHS.map((p) => p.path)\n);\n\n/** Returns the descriptor for a canonical path, or `undefined` if not on the whitelist. */\nexport function getPathDescriptor(\n path: string\n): MemoryPathDescriptor | undefined {\n return PATH_INDEX.get(path);\n}\n\n/**\n * Returns the tier a given path belongs to.\n *\n * For unknown paths (e.g. legacy date-keyed rows that predate this schema),\n * falls back to inspecting the `memory/agent/` or `memory/user/` prefix.\n * Anything that matches neither is classified as `agent` — it will surface\n * in the admin UI under the agent-tier header and stay read-only there.\n */\nexport function getTierForPath(path: string): MemoryTier {\n const descriptor = PATH_INDEX.get(path);\n if (descriptor) return descriptor.tier;\n if (path.startsWith('memory/user/')) return 'user';\n return 'agent';\n}\n\n/**\n * Paths that are legal to write **for this caller's scope**.\n *\n * If `scope.userId` is absent (isolated/autonomous agent), the user-tier\n * paths are dropped. This is the list the flush prompt renders into its\n * rubric — the LLM never sees paths it cannot write to.\n */\nexport function getWritablePathsForScope(\n scope: Pick<MemoryScope, 'userId'>\n): readonly MemoryPathDescriptor[] {\n if (scope.userId == null || scope.userId === '') {\n return MEMORY_AGENT_PATHS;\n }\n return MEMORY_ALL_PATHS;\n}\n\n/**\n * Validates a write path against the whitelist AND the caller's scope.\n *\n * Throws with an actionable message when:\n * - the path is missing the `memory/` prefix\n * - the path is not on the 8-doc whitelist\n * - the path is user-tier but `scope.userId` is missing\n *\n * The store's append() is the single call site; other callers should\n * never hit this directly. Kept as a standalone function for testability.\n */\nexport function assertWritablePath(\n path: string,\n scope: Pick<MemoryScope, 'userId'>\n): MemoryPathDescriptor {\n if (!path || !path.startsWith(MEMORY_PATH_PREFIX)) {\n throw new Error(\n `memory_append path must start with \"${MEMORY_PATH_PREFIX}\" (received \"${path}\")`\n );\n }\n const descriptor = PATH_INDEX.get(path);\n if (!descriptor) {\n throw new Error(\n `memory_append path \"${path}\" is not on the canonical whitelist. ` +\n `Allowed: ${MEMORY_ALL_PATHS.map((p) => p.path).join(', ')}`\n );\n }\n if (\n descriptor.tier === 'user' &&\n (scope.userId == null || scope.userId === '')\n ) {\n throw new Error(\n `memory_append to user-tier path \"${path}\" requires a caller userId in scope, ` +\n `but scope.userId is missing. This path is private to a specific user and ` +\n `cannot be written from an isolated/autonomous context.`\n );\n }\n return descriptor;\n}\n\n/**\n * Renders the whitelist as a compact bullet list, used inside the flush\n * prompt so the LLM sees the authoritative rubric at inference time.\n *\n * Output shape:\n * - memory/agent/playbook.md [Playbook, shared] — <description>\n * - memory/agent/pitfalls.md [Pitfalls, shared] — <description>\n * ...\n *\n * The `[Label, shared|private]` tag tells the model whether the doc is\n * visible to all users (agent tier) or only to the caller (user tier).\n * Filtered to only the paths writable for the given scope.\n */\nexport function renderPathsRubric(scope: Pick<MemoryScope, 'userId'>): string {\n const writable = getWritablePathsForScope(scope);\n return writable\n .map((p) => {\n const visibility = p.tier === 'agent' ? 'shared' : 'private';\n return `- ${p.path} [${p.label}, ${visibility}] — ${p.description}`;\n })\n .join('\\n');\n}\n"],"names":[],"mappings":"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"}
|