@illuma-ai/agents 1.1.28 → 1.3.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/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 +89 -45
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/HandoffRegistry.cjs +47 -8
- package/dist/cjs/graphs/HandoffRegistry.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +493 -267
- 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 +117 -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/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/errors.cjs +113 -0
- package/dist/cjs/utils/errors.cjs.map +1 -0
- package/dist/cjs/utils/events.cjs +36 -7
- 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.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 +89 -45
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/HandoffRegistry.mjs +47 -8
- package/dist/esm/graphs/HandoffRegistry.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +493 -267
- 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/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/errors.mjs +109 -0
- package/dist/esm/utils/errors.mjs.map +1 -0
- package/dist/esm/utils/events.mjs +36 -8
- 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 +24 -7
- package/dist/types/graphs/MultiAgentGraph.d.ts +43 -23
- 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 +10 -3
- package/dist/types/utils/childAgentContext.d.ts +99 -0
- package/dist/types/utils/errors.d.ts +37 -0
- package/dist/types/utils/events.d.ts +21 -0
- package/dist/types/utils/finishReasons.d.ts +32 -0
- package/dist/types/utils/index.d.ts +1 -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 +12 -4
- 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 +95 -61
- package/src/graphs/HandoffRegistry.ts +48 -17
- package/src/graphs/MultiAgentGraph.ts +588 -327
- package/src/graphs/__tests__/HandoffRegistry.test.ts +4 -1
- 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/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/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 +1 -1
- 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 +10 -3
- package/src/utils/__tests__/childAgentContext.test.ts +217 -0
- package/src/utils/__tests__/errors.test.ts +136 -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/errors.ts +115 -0
- package/src/utils/events.ts +37 -7
- package/src/utils/finishReasons.ts +40 -0
- package/src/utils/index.ts +1 -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,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Autonomous memory — shared constants.
|
|
3
|
+
*
|
|
4
|
+
* Single source of truth for defaults, limits, and magic strings used across
|
|
5
|
+
* the memory store, tools, flush phase, and tests. Changing a value here
|
|
6
|
+
* changes it everywhere — no hunting through files.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Default embedding provider when {@link process.env.MEMORY_EMBEDDINGS_PROVIDER} is unset. */
|
|
10
|
+
export const DEFAULT_MEMORY_PROVIDER = 'bedrock' as const;
|
|
11
|
+
|
|
12
|
+
/** Default embedding model (Titan v2 — AWS-native, 6× cheaper than Cohere v4). */
|
|
13
|
+
export const DEFAULT_MEMORY_MODEL = 'amazon.titan-embed-text-v2:0';
|
|
14
|
+
|
|
15
|
+
/** Default vector width (Titan v2 supports 256 / 512 / 1024). */
|
|
16
|
+
export const DEFAULT_MEMORY_DIMENSIONS = 1024;
|
|
17
|
+
|
|
18
|
+
/** Default Postgres table name; can be overridden via env for dev/prod sharing. */
|
|
19
|
+
export const DEFAULT_MEMORY_TABLE = 'agent_memories';
|
|
20
|
+
|
|
21
|
+
/** Default Postgres schema. */
|
|
22
|
+
export const DEFAULT_MEMORY_SCHEMA = 'public';
|
|
23
|
+
|
|
24
|
+
/** Phase values used by the flush-phase gate. */
|
|
25
|
+
export const MEMORY_PHASE_NORMAL = 'normal';
|
|
26
|
+
export const MEMORY_PHASE_FLUSHING = 'memory_flushing';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Search defaults — aligned with upstream's upstream defaults.
|
|
30
|
+
*
|
|
31
|
+
* Sources:
|
|
32
|
+
* - `upstream reference` → maxResults=6
|
|
33
|
+
* - `upstream reference` → maxInjectedChars=4000
|
|
34
|
+
*
|
|
35
|
+
* Keeping these in lockstep with upstream means the mandatory-recall tool
|
|
36
|
+
* description, budget clamps, and eval corpora line up with upstream's
|
|
37
|
+
* tuning — we inherit their calibration instead of re-tuning from scratch.
|
|
38
|
+
*/
|
|
39
|
+
export const DEFAULT_MAX_SEARCH_RESULTS = 6;
|
|
40
|
+
export const DEFAULT_MIN_SCORE = 0.1;
|
|
41
|
+
export const DEFAULT_MAX_INJECTED_CHARS = 4000;
|
|
42
|
+
|
|
43
|
+
/** Hybrid retrieval weights — 70% vector cosine, 30% BM25 / ts_rank text score. */
|
|
44
|
+
export const HYBRID_VECTOR_WEIGHT = 0.7;
|
|
45
|
+
export const HYBRID_TEXT_WEIGHT = 0.3;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Phase 2 rerank defaults — ported from upstream.
|
|
49
|
+
*
|
|
50
|
+
* Sources:
|
|
51
|
+
* - `upstream reference` → lambda=0.7
|
|
52
|
+
* - `upstream reference` → halfLifeDays=30
|
|
53
|
+
*
|
|
54
|
+
* Both features are opt-in (enabled=false by default) — the Phase 2
|
|
55
|
+
* features are layered on top of hybrid search and don't change default
|
|
56
|
+
* behavior for callers that upgrade in place.
|
|
57
|
+
*/
|
|
58
|
+
export const DEFAULT_MMR_ENABLED = false;
|
|
59
|
+
export const DEFAULT_MMR_LAMBDA = 0.7;
|
|
60
|
+
export const DEFAULT_TEMPORAL_DECAY_ENABLED = false;
|
|
61
|
+
export const DEFAULT_TEMPORAL_DECAY_HALF_LIFE_DAYS = 30;
|
|
62
|
+
export const DEFAULT_RECALL_TRACKING_ENABLED = false;
|
|
63
|
+
export const DEFAULT_CITATIONS_MODE = 'auto' as const;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Flush trigger margins (token counts) — aligned with upstream upstream.
|
|
67
|
+
*
|
|
68
|
+
* Sources:
|
|
69
|
+
* - `upstream reference` → softThreshold=4000
|
|
70
|
+
* - `upstream reference` → reserveFloor=20000
|
|
71
|
+
*/
|
|
72
|
+
export const DEFAULT_FLUSH_SOFT_THRESHOLD_TOKENS = 4000;
|
|
73
|
+
export const DEFAULT_FLUSH_RESERVE_FLOOR_TOKENS = 20000;
|
|
74
|
+
|
|
75
|
+
/** Hard cap on append calls per flush phase — prevents runaway writes. */
|
|
76
|
+
export const DEFAULT_MAX_APPENDS_PER_FLUSH = 20;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Hard cap on agentic loop iterations inside {@link runMemoryFlush}.
|
|
80
|
+
*
|
|
81
|
+
* Each iteration = one model.invoke() followed by execution of any
|
|
82
|
+
* `memory_append` tool_calls it emits. Mirrors upstream's flush-plan
|
|
83
|
+
* loop cap; 8 is enough for ~2–3 reflections of batched notes while
|
|
84
|
+
* protecting against runaway cycles if the model refuses to stop.
|
|
85
|
+
*/
|
|
86
|
+
export const DEFAULT_MAX_FLUSH_ITERATIONS = 8;
|
|
87
|
+
|
|
88
|
+
/** Path prefix enforced on every append. Paths outside this are rejected. */
|
|
89
|
+
export const MEMORY_PATH_PREFIX = 'memory/';
|
|
90
|
+
|
|
91
|
+
/** Tool names — kept as constants so server code + tests never drift. */
|
|
92
|
+
export const MEMORY_SEARCH_TOOL_NAME = 'memory_search';
|
|
93
|
+
export const MEMORY_GET_TOOL_NAME = 'memory_get';
|
|
94
|
+
export const MEMORY_APPEND_TOOL_NAME = 'memory_append';
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Mandatory-recall description — the single most load-bearing line in the
|
|
98
|
+
* whole memory system. Do not soften, shorten, or reword without an eval run.
|
|
99
|
+
*
|
|
100
|
+
* Ported VERBATIM from upstream `extensions/memory-core/src/tools.ts:186`.
|
|
101
|
+
* The wiki/corpus clause is retained even though Phase 1 doesn't ship
|
|
102
|
+
* compiled-wiki supplements — keeping the string identical means upstream's
|
|
103
|
+
* eval corpora remain drop-in valid.
|
|
104
|
+
*/
|
|
105
|
+
export const MEMORY_SEARCH_DESCRIPTION =
|
|
106
|
+
'Mandatory recall step: semantically search MEMORY.md + memory/*.md ' +
|
|
107
|
+
'(and optional session transcripts) before answering questions about ' +
|
|
108
|
+
'prior work, decisions, dates, people, preferences, or todos. Optional ' +
|
|
109
|
+
'`corpus=wiki` or `corpus=all` also searches registered compiled-wiki ' +
|
|
110
|
+
'supplements. If response has disabled=true, memory retrieval is ' +
|
|
111
|
+
'unavailable and should be surfaced to the user.';
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Ported VERBATIM from upstream `extensions/memory-core/src/tools.ts:322`.
|
|
115
|
+
*/
|
|
116
|
+
export const MEMORY_GET_DESCRIPTION =
|
|
117
|
+
'Safe snippet read from MEMORY.md or memory/*.md with optional from/lines; ' +
|
|
118
|
+
'`corpus=wiki` reads from registered compiled-wiki supplements. Use after ' +
|
|
119
|
+
'search to pull only the needed lines and keep context small.';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* `memory_append` tool description.
|
|
123
|
+
*
|
|
124
|
+
* Phase 1 historically wrote to a single date-keyed file
|
|
125
|
+
* (`memory/YYYY-MM-DD.md`), ported verbatim from upstream. That scheme
|
|
126
|
+
* is now replaced by an 8-path canonical whitelist — see
|
|
127
|
+
* {@link ./paths.MEMORY_ALL_PATHS}. The tool description no longer
|
|
128
|
+
* names a specific file; the flush-turn prompt renders the full rubric
|
|
129
|
+
* inline so the model sees every writable path at inference time.
|
|
130
|
+
*/
|
|
131
|
+
export const MEMORY_APPEND_DESCRIPTION =
|
|
132
|
+
"Append a durable note to one of the agent's canonical memory documents. " +
|
|
133
|
+
'The `path` argument MUST be one of the whitelisted paths listed in the ' +
|
|
134
|
+
'flush prompt rubric — unknown paths are rejected. Content is merged into ' +
|
|
135
|
+
'the existing row for that document via UPSERT, so the same document ' +
|
|
136
|
+
'accumulates across sessions.';
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Reply token that signals the flush turn produced no user-visible output.
|
|
140
|
+
* Ported VERBATIM from upstream `src/auto-reply/tokens.ts:4`.
|
|
141
|
+
*/
|
|
142
|
+
export const SILENT_REPLY_TOKEN = 'NO_REPLY';
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Placeholder replaced at flush time with the rendered path-rubric for
|
|
146
|
+
* the caller's scope. See `renderPathsRubric()` in `./paths.ts` and
|
|
147
|
+
* `resolveFlushPrompts()` in `../prompts/memoryFlushPrompt.ts`.
|
|
148
|
+
*
|
|
149
|
+
* Kept as a unique sentinel so `replaceAll` is safe even if the rubric
|
|
150
|
+
* content happens to contain regex metacharacters.
|
|
151
|
+
*/
|
|
152
|
+
export const FLUSH_PROMPT_RUBRIC_PLACEHOLDER = '{{MEMORY_PATHS_RUBRIC}}';
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Memory-flush prompts — canonical-document model.
|
|
156
|
+
*
|
|
157
|
+
* Every durable memory routes into one of 8 stable canonical documents
|
|
158
|
+
* (4 agent-tier + 4 user-tier). The rubric is injected at flush time so
|
|
159
|
+
* the model reads the authoritative path list with descriptions, and for
|
|
160
|
+
* isolated/autonomous agents the user-tier rows are transparently omitted
|
|
161
|
+
* from the rubric — making "user-tier writes require a scoped caller" a
|
|
162
|
+
* compile-time guarantee rather than a runtime check alone.
|
|
163
|
+
*
|
|
164
|
+
* Two-tier semantics the prompt enforces:
|
|
165
|
+
* - **agent/** — shared operational knowledge; every user of this agent
|
|
166
|
+
* benefits from rows written here. Do NOT put personal facts here.
|
|
167
|
+
* - **user/** — personalization for the specific caller only. Row is
|
|
168
|
+
* private to that user; other users never see it.
|
|
169
|
+
*/
|
|
170
|
+
const MEMORY_FLUSH_ROUTING_HINT =
|
|
171
|
+
'Route every note into exactly one of the canonical documents below by ' +
|
|
172
|
+
'picking the best match. Do NOT invent new paths; do NOT create date-keyed ' +
|
|
173
|
+
'files; unknown paths are rejected by the store.';
|
|
174
|
+
|
|
175
|
+
const MEMORY_FLUSH_TIER_HINT =
|
|
176
|
+
'Two tiers: `memory/agent/*` is SHARED operational knowledge visible to ' +
|
|
177
|
+
'every user of this agent — put successful patterns, pitfalls, domain ' +
|
|
178
|
+
'facts, and house-style there. `memory/user/*` is PRIVATE to the specific ' +
|
|
179
|
+
'caller — put their identity, preferences, projects, and references there. ' +
|
|
180
|
+
'Never put user-specific facts in an agent/* document.';
|
|
181
|
+
|
|
182
|
+
const MEMORY_FLUSH_READ_ONLY_HINT =
|
|
183
|
+
'Treat workspace bootstrap/reference files such as MEMORY.md, DREAMS.md, SOUL.md, TOOLS.md, and AGENTS.md as read-only during this flush; never overwrite, replace, or edit them.';
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Learning hint — steers the flush turn to capture the things that
|
|
187
|
+
* matter most for future turns: reusable patterns, tool failures
|
|
188
|
+
* (so the same mistake is not repeated), explicit corrections, and
|
|
189
|
+
* durable user-specific facts. Append-only via `memory_append`; one
|
|
190
|
+
* note per lesson.
|
|
191
|
+
*/
|
|
192
|
+
const MEMORY_FLUSH_LEARNING_HINT =
|
|
193
|
+
'Capture durable lessons the agent (and future turns) should retain: ' +
|
|
194
|
+
'(a) successful task patterns and workflows → memory/agent/playbook.md; ' +
|
|
195
|
+
'(b) tool failures and schema mistakes → memory/agent/pitfalls.md; ' +
|
|
196
|
+
'(c) stable domain facts about the systems/APIs → memory/agent/domain.md; ' +
|
|
197
|
+
"(d) this user's preferences, tone, and corrections → memory/user/preferences.md; " +
|
|
198
|
+
"(e) this user's identity and role → memory/user/profile.md. " +
|
|
199
|
+
'Write one note per distinct lesson. Do not log conversation summaries ' +
|
|
200
|
+
'or anything derivable from the code or recent history.';
|
|
201
|
+
|
|
202
|
+
const MEMORY_FLUSH_RUBRIC_BLOCK =
|
|
203
|
+
'Canonical documents available for this turn (path — tag — description):\n' +
|
|
204
|
+
FLUSH_PROMPT_RUBRIC_PLACEHOLDER;
|
|
205
|
+
|
|
206
|
+
export const DEFAULT_MEMORY_FLUSH_PROMPT = [
|
|
207
|
+
'Pre-compaction memory flush.',
|
|
208
|
+
MEMORY_FLUSH_ROUTING_HINT,
|
|
209
|
+
MEMORY_FLUSH_TIER_HINT,
|
|
210
|
+
MEMORY_FLUSH_READ_ONLY_HINT,
|
|
211
|
+
MEMORY_FLUSH_LEARNING_HINT,
|
|
212
|
+
'Call the `memory_append` tool for every note you want to persist, passing one of the whitelisted paths as the `path` argument. Do NOT describe what you are about to write; just call the tool.',
|
|
213
|
+
`If nothing worth storing, reply with exactly ${SILENT_REPLY_TOKEN}.`,
|
|
214
|
+
'',
|
|
215
|
+
MEMORY_FLUSH_RUBRIC_BLOCK,
|
|
216
|
+
].join('\n');
|
|
217
|
+
|
|
218
|
+
export const DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT = [
|
|
219
|
+
'Pre-compaction memory flush turn.',
|
|
220
|
+
'The session is near auto-compaction; capture durable memories to disk.',
|
|
221
|
+
MEMORY_FLUSH_ROUTING_HINT,
|
|
222
|
+
MEMORY_FLUSH_TIER_HINT,
|
|
223
|
+
MEMORY_FLUSH_READ_ONLY_HINT,
|
|
224
|
+
MEMORY_FLUSH_LEARNING_HINT,
|
|
225
|
+
'Use the `memory_append` tool for every durable note, with `path` set to one of the whitelisted canonical documents. Never claim you wrote a note without actually calling the tool.',
|
|
226
|
+
`If there is nothing worth storing, reply with exactly ${SILENT_REPLY_TOKEN}.`,
|
|
227
|
+
'',
|
|
228
|
+
MEMORY_FLUSH_RUBRIC_BLOCK,
|
|
229
|
+
].join('\n');
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-agnostic embedding client for autonomous memory.
|
|
3
|
+
*
|
|
4
|
+
* Factory pattern mirrors host's `getSharedSummaryBedrockClient()` in
|
|
5
|
+
* `api/server/controllers/agents/client.js` — lazy init, shared instance,
|
|
6
|
+
* testable reset. Same AWS credential chain as host's existing Bedrock
|
|
7
|
+
* calls, so no new IAM policy or secret.
|
|
8
|
+
*/
|
|
9
|
+
import {
|
|
10
|
+
BedrockRuntimeClient,
|
|
11
|
+
InvokeModelCommand,
|
|
12
|
+
} from '@aws-sdk/client-bedrock-runtime';
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_MEMORY_DIMENSIONS,
|
|
15
|
+
DEFAULT_MEMORY_MODEL,
|
|
16
|
+
DEFAULT_MEMORY_PROVIDER,
|
|
17
|
+
} from './constants';
|
|
18
|
+
|
|
19
|
+
export type EmbeddingProviderKind = 'bedrock' | 'openai';
|
|
20
|
+
|
|
21
|
+
export interface EmbeddingProvider {
|
|
22
|
+
readonly kind: EmbeddingProviderKind;
|
|
23
|
+
readonly model: string;
|
|
24
|
+
readonly dimensions: number;
|
|
25
|
+
embed(text: string): Promise<number[]>;
|
|
26
|
+
embedBatch(texts: string[]): Promise<number[][]>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ResolvedEmbedderConfig {
|
|
30
|
+
provider: EmbeddingProviderKind;
|
|
31
|
+
model: string;
|
|
32
|
+
dimensions: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Read embedder config from environment. Called once per factory invocation;
|
|
37
|
+
* fresh reads make testing override-friendly via {@link resetMemoryEmbedder}.
|
|
38
|
+
*/
|
|
39
|
+
function resolveConfig(): ResolvedEmbedderConfig {
|
|
40
|
+
const rawProvider = (
|
|
41
|
+
process.env.MEMORY_EMBEDDINGS_PROVIDER ?? DEFAULT_MEMORY_PROVIDER
|
|
42
|
+
).toLowerCase();
|
|
43
|
+
if (rawProvider !== 'bedrock' && rawProvider !== 'openai') {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Unsupported MEMORY_EMBEDDINGS_PROVIDER: "${rawProvider}". Supported: bedrock, openai.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const model = process.env.MEMORY_EMBEDDINGS_MODEL ?? DEFAULT_MEMORY_MODEL;
|
|
49
|
+
const dimensions =
|
|
50
|
+
Number(process.env.MEMORY_EMBEDDINGS_DIMENSIONS) ||
|
|
51
|
+
DEFAULT_MEMORY_DIMENSIONS;
|
|
52
|
+
return { provider: rawProvider, model, dimensions };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class BedrockEmbedder implements EmbeddingProvider {
|
|
56
|
+
readonly kind = 'bedrock' as const;
|
|
57
|
+
readonly model: string;
|
|
58
|
+
readonly dimensions: number;
|
|
59
|
+
private client: BedrockRuntimeClient;
|
|
60
|
+
|
|
61
|
+
constructor(params: { model: string; dimensions: number }) {
|
|
62
|
+
this.model = params.model;
|
|
63
|
+
this.dimensions = params.dimensions;
|
|
64
|
+
const region =
|
|
65
|
+
process.env.BEDROCK_AWS_DEFAULT_REGION ??
|
|
66
|
+
process.env.AWS_REGION ??
|
|
67
|
+
'us-east-1';
|
|
68
|
+
// Default credential provider chain — identical to host's existing
|
|
69
|
+
// Bedrock client. In ECS this resolves to the task role.
|
|
70
|
+
this.client = new BedrockRuntimeClient({ region });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async embed(text: string): Promise<number[]> {
|
|
74
|
+
const input = text.trim();
|
|
75
|
+
if (!input) {
|
|
76
|
+
throw new Error('Cannot embed empty text');
|
|
77
|
+
}
|
|
78
|
+
const body = {
|
|
79
|
+
inputText: input,
|
|
80
|
+
dimensions: this.dimensions,
|
|
81
|
+
normalize: true,
|
|
82
|
+
};
|
|
83
|
+
const command = new InvokeModelCommand({
|
|
84
|
+
modelId: this.model,
|
|
85
|
+
contentType: 'application/json',
|
|
86
|
+
accept: 'application/json',
|
|
87
|
+
body: new TextEncoder().encode(JSON.stringify(body)),
|
|
88
|
+
});
|
|
89
|
+
const response = await this.client.send(command);
|
|
90
|
+
const decoded = JSON.parse(new TextDecoder().decode(response.body)) as {
|
|
91
|
+
embedding: number[];
|
|
92
|
+
};
|
|
93
|
+
if (!Array.isArray(decoded.embedding)) {
|
|
94
|
+
throw new Error('Bedrock embedding response missing `embedding` field');
|
|
95
|
+
}
|
|
96
|
+
return decoded.embedding;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async embedBatch(texts: string[]): Promise<number[][]> {
|
|
100
|
+
// Titan embed v2 has no native batch API — sequential calls are the
|
|
101
|
+
// documented pattern. Parallelism here can trip per-account TPS ceilings,
|
|
102
|
+
// so we keep it serial. For Phase 4 we can introduce a bounded queue.
|
|
103
|
+
const out: number[][] = [];
|
|
104
|
+
for (const text of texts) {
|
|
105
|
+
out.push(await this.embed(text));
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class OpenAIEmbedder implements EmbeddingProvider {
|
|
112
|
+
readonly kind = 'openai' as const;
|
|
113
|
+
readonly model: string;
|
|
114
|
+
readonly dimensions: number;
|
|
115
|
+
private apiKey: string;
|
|
116
|
+
private baseUrl: string;
|
|
117
|
+
|
|
118
|
+
constructor(params: { model: string; dimensions: number }) {
|
|
119
|
+
this.model = params.model;
|
|
120
|
+
this.dimensions = params.dimensions;
|
|
121
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
122
|
+
if (!apiKey) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
'OPENAI_API_KEY is not set — required when MEMORY_EMBEDDINGS_PROVIDER=openai'
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
this.apiKey = apiKey;
|
|
128
|
+
this.baseUrl = process.env.OPENAI_BASEURL ?? 'https://api.openai.com/v1';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async call(texts: string[]): Promise<number[][]> {
|
|
132
|
+
const res = await fetch(`${this.baseUrl}/embeddings`, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({ model: this.model, input: texts }),
|
|
139
|
+
});
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
const errText = await res.text();
|
|
142
|
+
throw new Error(`OpenAI embeddings ${res.status}: ${errText}`);
|
|
143
|
+
}
|
|
144
|
+
const json = (await res.json()) as { data: Array<{ embedding: number[] }> };
|
|
145
|
+
return json.data.map((d) => d.embedding);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async embed(text: string): Promise<number[]> {
|
|
149
|
+
const [vec] = await this.call([text]);
|
|
150
|
+
return vec;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async embedBatch(texts: string[]): Promise<number[][]> {
|
|
154
|
+
if (texts.length === 0) return [];
|
|
155
|
+
return this.call(texts);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let sharedEmbedder: EmbeddingProvider | null = null;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Lazy singleton accessor. Same idiom as host's
|
|
163
|
+
* `getSharedSummaryBedrockClient()`.
|
|
164
|
+
*/
|
|
165
|
+
export function getMemoryEmbedder(): EmbeddingProvider {
|
|
166
|
+
if (sharedEmbedder) return sharedEmbedder;
|
|
167
|
+
const cfg = resolveConfig();
|
|
168
|
+
switch (cfg.provider) {
|
|
169
|
+
case 'bedrock':
|
|
170
|
+
sharedEmbedder = new BedrockEmbedder({
|
|
171
|
+
model: cfg.model,
|
|
172
|
+
dimensions: cfg.dimensions,
|
|
173
|
+
});
|
|
174
|
+
break;
|
|
175
|
+
case 'openai':
|
|
176
|
+
sharedEmbedder = new OpenAIEmbedder({
|
|
177
|
+
model: cfg.model,
|
|
178
|
+
dimensions: cfg.dimensions,
|
|
179
|
+
});
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
return sharedEmbedder;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Test hook — drops the cached embedder so the next call re-reads env. */
|
|
186
|
+
export function resetMemoryEmbedder(): void {
|
|
187
|
+
sharedEmbedder = null;
|
|
188
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory for building a memory backend from environment variables.
|
|
3
|
+
*
|
|
4
|
+
* Called once at startup by host's singleton (`api/server/services/memoryStore.js`).
|
|
5
|
+
* Returns `null` — never throws — when required env vars are missing, so
|
|
6
|
+
* agents with `memory_enabled=true` still run, just without memory. Host
|
|
7
|
+
* logs a single warning on boot instead of crashing.
|
|
8
|
+
*/
|
|
9
|
+
import { Pool } from 'pg';
|
|
10
|
+
import { DEFAULT_MEMORY_TABLE } from './constants';
|
|
11
|
+
import { PgvectorMemoryStore } from './pgvectorStore';
|
|
12
|
+
import { runMemoryMigration } from './migrate';
|
|
13
|
+
import { PgvectorRecallTracker, type RecallTracker } from './recallTracking';
|
|
14
|
+
import type { MemoryBackend } from './types';
|
|
15
|
+
|
|
16
|
+
export interface BuildMemoryBackendResult {
|
|
17
|
+
backend: MemoryBackend;
|
|
18
|
+
pool: Pool;
|
|
19
|
+
/** Phase 2 — shared recall tracker bound to the same pool/migration. */
|
|
20
|
+
recallTracker: RecallTracker;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface PgConfig {
|
|
24
|
+
host: string;
|
|
25
|
+
port: number;
|
|
26
|
+
user: string;
|
|
27
|
+
password: string;
|
|
28
|
+
database: string;
|
|
29
|
+
ssl: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readPgConfig(): PgConfig | null {
|
|
33
|
+
// Dev shortcut: single URL overrides discrete fields if present.
|
|
34
|
+
const url = process.env.MEMORY_DATABASE_URL;
|
|
35
|
+
if (url) {
|
|
36
|
+
try {
|
|
37
|
+
const u = new URL(url);
|
|
38
|
+
return {
|
|
39
|
+
host: u.hostname,
|
|
40
|
+
port: Number(u.port || 5432),
|
|
41
|
+
user: decodeURIComponent(u.username),
|
|
42
|
+
password: decodeURIComponent(u.password),
|
|
43
|
+
database: u.pathname.replace(/^\//, ''),
|
|
44
|
+
ssl: (process.env.MEMORY_POSTGRES_SSL ?? 'disable') === 'require',
|
|
45
|
+
};
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const host = process.env.MEMORY_POSTGRES_HOST;
|
|
52
|
+
const user = process.env.MEMORY_POSTGRES_USER;
|
|
53
|
+
const password = process.env.MEMORY_POSTGRES_PASSWORD;
|
|
54
|
+
const database = process.env.MEMORY_POSTGRES_DB;
|
|
55
|
+
if (!host || !user || !password || !database) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
host,
|
|
60
|
+
port: Number(process.env.MEMORY_POSTGRES_PORT ?? 5432),
|
|
61
|
+
user,
|
|
62
|
+
password,
|
|
63
|
+
database,
|
|
64
|
+
ssl: (process.env.MEMORY_POSTGRES_SSL ?? 'disable') === 'require',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Try to build a configured memory backend. Returns null when the required
|
|
70
|
+
* env vars are absent — caller should log a single "memory disabled" warning
|
|
71
|
+
* and continue. Never throws on missing config.
|
|
72
|
+
*/
|
|
73
|
+
export async function buildMemoryBackendFromEnv(): Promise<BuildMemoryBackendResult | null> {
|
|
74
|
+
const cfg = readPgConfig();
|
|
75
|
+
if (!cfg) return null;
|
|
76
|
+
|
|
77
|
+
const pool = new Pool({
|
|
78
|
+
host: cfg.host,
|
|
79
|
+
port: cfg.port,
|
|
80
|
+
user: cfg.user,
|
|
81
|
+
password: cfg.password,
|
|
82
|
+
database: cfg.database,
|
|
83
|
+
ssl: cfg.ssl ? { rejectUnauthorized: false } : undefined,
|
|
84
|
+
max: 10,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const table = process.env.MEMORY_TABLE_NAME ?? DEFAULT_MEMORY_TABLE;
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
await runMemoryMigration({ pool, table });
|
|
91
|
+
} catch (err) {
|
|
92
|
+
// Migration failures ARE fatal — they mean dimension mismatch or
|
|
93
|
+
// permissions wrong, and silently running past that would produce
|
|
94
|
+
// corrupt or missing memory reads.
|
|
95
|
+
await pool.end().catch(() => undefined);
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const backend = new PgvectorMemoryStore({ pool, table });
|
|
100
|
+
|
|
101
|
+
// Phase 2 — recall tracker migration is best-effort at boot. If it fails,
|
|
102
|
+
// tool wiring continues with a null tracker rather than crashing host.
|
|
103
|
+
const recallTracker = new PgvectorRecallTracker(pool);
|
|
104
|
+
try {
|
|
105
|
+
await recallTracker.migrate();
|
|
106
|
+
} catch {
|
|
107
|
+
// [phase2-recall-tracking] debug: migration skipped — non-fatal
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { backend, pool, recallTracker };
|
|
111
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/** Public entry point for the memory package. */
|
|
2
|
+
export * from './types';
|
|
3
|
+
export * from './constants';
|
|
4
|
+
export * from './paths';
|
|
5
|
+
export { PgvectorMemoryStore } from './pgvectorStore';
|
|
6
|
+
export type { PgvectorStoreOptions } from './pgvectorStore';
|
|
7
|
+
export { CompositeMemoryBackend } from './compositeBackend';
|
|
8
|
+
export { getMemoryEmbedder, resetMemoryEmbedder } from './embeddings';
|
|
9
|
+
export type { EmbeddingProvider, EmbeddingProviderKind } from './embeddings';
|
|
10
|
+
export { runMemoryMigration } from './migrate';
|
|
11
|
+
export type { MigrationOptions } from './migrate';
|
|
12
|
+
export { buildMemoryBackendFromEnv } from './factory';
|
|
13
|
+
export type { BuildMemoryBackendResult } from './factory';
|
|
14
|
+
|
|
15
|
+
// Phase 2
|
|
16
|
+
export {
|
|
17
|
+
mmrRerank,
|
|
18
|
+
applyMMRToMemoryHits,
|
|
19
|
+
DEFAULT_MMR_CONFIG,
|
|
20
|
+
tokenize,
|
|
21
|
+
jaccardSimilarity,
|
|
22
|
+
textSimilarity,
|
|
23
|
+
computeMMRScore,
|
|
24
|
+
} from './mmr';
|
|
25
|
+
export type { MMRConfig, MMRItem } from './mmr';
|
|
26
|
+
export {
|
|
27
|
+
applyTemporalDecayToHits,
|
|
28
|
+
applyTemporalDecayToScore,
|
|
29
|
+
calculateTemporalDecayMultiplier,
|
|
30
|
+
parseMemoryDateFromPath,
|
|
31
|
+
isEvergreenMemoryPath,
|
|
32
|
+
DEFAULT_TEMPORAL_DECAY_CONFIG,
|
|
33
|
+
} from './temporalDecay';
|
|
34
|
+
export type { TemporalDecayConfig, DecayCandidate } from './temporalDecay';
|
|
35
|
+
export {
|
|
36
|
+
decorateCitations,
|
|
37
|
+
resolveMemoryCitationsMode,
|
|
38
|
+
shouldIncludeCitations,
|
|
39
|
+
} from './citations';
|
|
40
|
+
export type { MemoryCitationsMode, CitationCandidate } from './citations';
|
|
41
|
+
export {
|
|
42
|
+
PgvectorRecallTracker,
|
|
43
|
+
NullRecallTracker,
|
|
44
|
+
RECALL_TABLE,
|
|
45
|
+
} from './recallTracking';
|
|
46
|
+
export type { RecallTracker, RecallRecordParams } from './recallTracking';
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idempotent schema install + vector-dimension safety check.
|
|
3
|
+
*
|
|
4
|
+
* Called once at agents-library startup by host's `memoryStore.js` singleton
|
|
5
|
+
* factory. Safe to call multiple times — every statement is `IF NOT EXISTS`.
|
|
6
|
+
*/
|
|
7
|
+
import type { Pool } from 'pg';
|
|
8
|
+
import { readFileSync } from 'fs';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { DEFAULT_MEMORY_DIMENSIONS, DEFAULT_MEMORY_TABLE } from './constants';
|
|
12
|
+
import { getMemoryEmbedder } from './embeddings';
|
|
13
|
+
|
|
14
|
+
export interface MigrationOptions {
|
|
15
|
+
pool: Pool;
|
|
16
|
+
table?: string;
|
|
17
|
+
/** If true, skip the live embedder probe (tests). */
|
|
18
|
+
skipEmbedderProbe?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function resolveSchemaSqlPath(): string {
|
|
22
|
+
// Works under both ESM (import.meta.url) and compiled CJS (fall back to __dirname).
|
|
23
|
+
const meta = import.meta as unknown as { url?: string };
|
|
24
|
+
const metaUrl = typeof meta.url === 'string' ? meta.url : undefined;
|
|
25
|
+
if (metaUrl) {
|
|
26
|
+
return join(dirname(fileURLToPath(metaUrl)), 'schema.sql');
|
|
27
|
+
}
|
|
28
|
+
const globalDirname = (globalThis as unknown as { __dirname?: string })
|
|
29
|
+
.__dirname;
|
|
30
|
+
return join(globalDirname ?? process.cwd(), 'schema.sql');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function runMemoryMigration(
|
|
34
|
+
opts: MigrationOptions
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
const table = opts.table ?? DEFAULT_MEMORY_TABLE;
|
|
37
|
+
const schemaPath = resolveSchemaSqlPath();
|
|
38
|
+
const schemaSql = readFileSync(schemaPath, 'utf8').replace(
|
|
39
|
+
/agent_memories/g,
|
|
40
|
+
table
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Drop the legacy `(agent_id, path)` unique constraint BEFORE running
|
|
44
|
+
// the schema SQL. The schema creates the new
|
|
45
|
+
// `(agent_id, user_id, path)` NULLS-NOT-DISTINCT constraint, which
|
|
46
|
+
// must not collide with the old one. Dropping by the legacy name is a
|
|
47
|
+
// no-op on fresh installs where the constraint never existed, and on
|
|
48
|
+
// upgrades it cleanly frees the unique-index slot so the new
|
|
49
|
+
// constraint can be installed.
|
|
50
|
+
await opts.pool
|
|
51
|
+
.query(
|
|
52
|
+
`ALTER TABLE ${table} DROP CONSTRAINT IF EXISTS ${table}_agent_path_uq`
|
|
53
|
+
)
|
|
54
|
+
.catch(() => undefined);
|
|
55
|
+
|
|
56
|
+
await opts.pool.query(schemaSql);
|
|
57
|
+
|
|
58
|
+
// Adopt legacy rows if the table pre-existed with an older shape.
|
|
59
|
+
// Idempotent — every statement is no-op-safe on a fresh schema.
|
|
60
|
+
await opts.pool
|
|
61
|
+
.query(`ALTER TABLE ${table} ALTER COLUMN user_id DROP NOT NULL`)
|
|
62
|
+
.catch(() => undefined);
|
|
63
|
+
await opts.pool.query(
|
|
64
|
+
`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()`
|
|
65
|
+
);
|
|
66
|
+
await opts.pool.query(
|
|
67
|
+
`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS last_user_id TEXT`
|
|
68
|
+
);
|
|
69
|
+
// Belt-and-suspenders: if the schema CREATE was skipped because the
|
|
70
|
+
// table already existed AND the new constraint was absent, add it
|
|
71
|
+
// explicitly. Swallow duplicate-object errors on happy-path re-runs.
|
|
72
|
+
await opts.pool
|
|
73
|
+
.query(
|
|
74
|
+
`ALTER TABLE ${table}
|
|
75
|
+
ADD CONSTRAINT ${table}_agent_user_path_uq
|
|
76
|
+
UNIQUE NULLS NOT DISTINCT (agent_id, user_id, path)`
|
|
77
|
+
)
|
|
78
|
+
.catch(() => undefined);
|
|
79
|
+
|
|
80
|
+
if (opts.skipEmbedderProbe) return;
|
|
81
|
+
|
|
82
|
+
// Vector dimension sanity check — refuses to serve if the column width
|
|
83
|
+
// disagrees with the live embedder and the table has data. See plan §3.
|
|
84
|
+
const embedder = getMemoryEmbedder();
|
|
85
|
+
const liveDim = embedder.dimensions || DEFAULT_MEMORY_DIMENSIONS;
|
|
86
|
+
|
|
87
|
+
// pgvector stores the declared width directly in atttypmod (unlike varchar
|
|
88
|
+
// which uses atttypmod = N + VARHDRSZ). Reading it raw gives the true dim.
|
|
89
|
+
const dimResult = await opts.pool.query(
|
|
90
|
+
`
|
|
91
|
+
SELECT atttypmod AS dim
|
|
92
|
+
FROM pg_attribute
|
|
93
|
+
WHERE attrelid = to_regclass($1)
|
|
94
|
+
AND attname = 'embedding'
|
|
95
|
+
`,
|
|
96
|
+
[table]
|
|
97
|
+
);
|
|
98
|
+
const columnDim = dimResult.rows[0]?.dim;
|
|
99
|
+
if (columnDim && Number(columnDim) !== liveDim) {
|
|
100
|
+
const countRes = await opts.pool.query(
|
|
101
|
+
`SELECT COUNT(*)::int AS n FROM ${table}`
|
|
102
|
+
);
|
|
103
|
+
const rowCount = Number(countRes.rows[0]?.n ?? 0);
|
|
104
|
+
if (rowCount === 0) {
|
|
105
|
+
await opts.pool.query(
|
|
106
|
+
`ALTER TABLE ${table} ALTER COLUMN embedding TYPE VECTOR(${liveDim})`
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Memory vector dimension mismatch: column is VECTOR(${columnDim}) but ` +
|
|
111
|
+
`embedder "${embedder.model}" produces ${liveDim}-d vectors, and ${rowCount} ` +
|
|
112
|
+
`rows exist. Refusing to serve memory. Admin must run a reindex job.`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|