@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.
Files changed (272) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +20 -3
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/spawnPath.cjs +104 -0
  4. package/dist/cjs/common/spawnPath.cjs.map +1 -0
  5. package/dist/cjs/graphs/Graph.cjs +87 -31
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/graphs/HandoffRegistry.cjs +143 -0
  8. package/dist/cjs/graphs/HandoffRegistry.cjs.map +1 -0
  9. package/dist/cjs/graphs/MultiAgentGraph.cjs +587 -184
  10. package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
  11. package/dist/cjs/graphs/phases/flushLoop.cjs +214 -0
  12. package/dist/cjs/graphs/phases/flushLoop.cjs.map +1 -0
  13. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs +102 -0
  14. package/dist/cjs/graphs/phases/memoryFlushPhase.cjs.map +1 -0
  15. package/dist/cjs/llm/bedrock/index.cjs +4 -3
  16. package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
  17. package/dist/cjs/main.cjs +115 -0
  18. package/dist/cjs/main.cjs.map +1 -1
  19. package/dist/cjs/memory/citations.cjs +69 -0
  20. package/dist/cjs/memory/citations.cjs.map +1 -0
  21. package/dist/cjs/memory/compositeBackend.cjs +60 -0
  22. package/dist/cjs/memory/compositeBackend.cjs.map +1 -0
  23. package/dist/cjs/memory/constants.cjs +232 -0
  24. package/dist/cjs/memory/constants.cjs.map +1 -0
  25. package/dist/cjs/memory/embeddings.cjs +151 -0
  26. package/dist/cjs/memory/embeddings.cjs.map +1 -0
  27. package/dist/cjs/memory/factory.cjs +95 -0
  28. package/dist/cjs/memory/factory.cjs.map +1 -0
  29. package/dist/cjs/memory/migrate.cjs +81 -0
  30. package/dist/cjs/memory/migrate.cjs.map +1 -0
  31. package/dist/cjs/memory/mmr.cjs +138 -0
  32. package/dist/cjs/memory/mmr.cjs.map +1 -0
  33. package/dist/cjs/memory/paths.cjs +217 -0
  34. package/dist/cjs/memory/paths.cjs.map +1 -0
  35. package/dist/cjs/memory/pgvectorStore.cjs +225 -0
  36. package/dist/cjs/memory/pgvectorStore.cjs.map +1 -0
  37. package/dist/cjs/memory/recallTracking.cjs +98 -0
  38. package/dist/cjs/memory/recallTracking.cjs.map +1 -0
  39. package/dist/cjs/memory/schema.sql +51 -0
  40. package/dist/cjs/memory/temporalDecay.cjs +118 -0
  41. package/dist/cjs/memory/temporalDecay.cjs.map +1 -0
  42. package/dist/cjs/nodes/ApprovalGateNode.cjs +1 -1
  43. package/dist/cjs/nodes/ApprovalGateNode.cjs.map +1 -1
  44. package/dist/cjs/prompts/memoryFlushPrompt.cjs +49 -0
  45. package/dist/cjs/prompts/memoryFlushPrompt.cjs.map +1 -0
  46. package/dist/cjs/run.cjs +16 -3
  47. package/dist/cjs/run.cjs.map +1 -1
  48. package/dist/cjs/stream.cjs +4 -4
  49. package/dist/cjs/stream.cjs.map +1 -1
  50. package/dist/cjs/tools/AskUser.cjs +6 -1
  51. package/dist/cjs/tools/AskUser.cjs.map +1 -1
  52. package/dist/cjs/tools/BrowserTools.cjs +1 -1
  53. package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
  54. package/dist/cjs/tools/ToolNode.cjs +127 -10
  55. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  56. package/dist/cjs/tools/approval/constants.cjs +2 -2
  57. package/dist/cjs/tools/approval/constants.cjs.map +1 -1
  58. package/dist/cjs/tools/memory/index.cjs +58 -0
  59. package/dist/cjs/tools/memory/index.cjs.map +1 -0
  60. package/dist/cjs/tools/memory/memoryAppendTool.cjs +69 -0
  61. package/dist/cjs/tools/memory/memoryAppendTool.cjs.map +1 -0
  62. package/dist/cjs/tools/memory/memoryGetTool.cjs +49 -0
  63. package/dist/cjs/tools/memory/memoryGetTool.cjs.map +1 -0
  64. package/dist/cjs/tools/memory/memorySearchTool.cjs +65 -0
  65. package/dist/cjs/tools/memory/memorySearchTool.cjs.map +1 -0
  66. package/dist/cjs/tools/memory/shared.cjs +106 -0
  67. package/dist/cjs/tools/memory/shared.cjs.map +1 -0
  68. package/dist/cjs/types/graph.cjs.map +1 -1
  69. package/dist/cjs/utils/childAgentContext.cjs +242 -0
  70. package/dist/cjs/utils/childAgentContext.cjs.map +1 -0
  71. package/dist/cjs/utils/events.cjs +36 -4
  72. package/dist/cjs/utils/events.cjs.map +1 -1
  73. package/dist/cjs/utils/finishReasons.cjs +44 -0
  74. package/dist/cjs/utils/finishReasons.cjs.map +1 -0
  75. package/dist/cjs/utils/llm.cjs.map +1 -1
  76. package/dist/cjs/utils/logging.cjs +34 -0
  77. package/dist/cjs/utils/logging.cjs.map +1 -0
  78. package/dist/cjs/utils/toolCallNormalization.cjs +250 -0
  79. package/dist/cjs/utils/toolCallNormalization.cjs.map +1 -0
  80. package/dist/esm/agents/AgentContext.mjs +20 -3
  81. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  82. package/dist/esm/common/spawnPath.mjs +95 -0
  83. package/dist/esm/common/spawnPath.mjs.map +1 -0
  84. package/dist/esm/graphs/Graph.mjs +87 -31
  85. package/dist/esm/graphs/Graph.mjs.map +1 -1
  86. package/dist/esm/graphs/HandoffRegistry.mjs +141 -0
  87. package/dist/esm/graphs/HandoffRegistry.mjs.map +1 -0
  88. package/dist/esm/graphs/MultiAgentGraph.mjs +587 -184
  89. package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
  90. package/dist/esm/graphs/phases/flushLoop.mjs +209 -0
  91. package/dist/esm/graphs/phases/flushLoop.mjs.map +1 -0
  92. package/dist/esm/graphs/phases/memoryFlushPhase.mjs +99 -0
  93. package/dist/esm/graphs/phases/memoryFlushPhase.mjs.map +1 -0
  94. package/dist/esm/llm/bedrock/index.mjs +4 -3
  95. package/dist/esm/llm/bedrock/index.mjs.map +1 -1
  96. package/dist/esm/main.mjs +21 -0
  97. package/dist/esm/main.mjs.map +1 -1
  98. package/dist/esm/memory/citations.mjs +64 -0
  99. package/dist/esm/memory/citations.mjs.map +1 -0
  100. package/dist/esm/memory/compositeBackend.mjs +58 -0
  101. package/dist/esm/memory/compositeBackend.mjs.map +1 -0
  102. package/dist/esm/memory/constants.mjs +198 -0
  103. package/dist/esm/memory/constants.mjs.map +1 -0
  104. package/dist/esm/memory/embeddings.mjs +148 -0
  105. package/dist/esm/memory/embeddings.mjs.map +1 -0
  106. package/dist/esm/memory/factory.mjs +93 -0
  107. package/dist/esm/memory/factory.mjs.map +1 -0
  108. package/dist/esm/memory/migrate.mjs +78 -0
  109. package/dist/esm/memory/migrate.mjs.map +1 -0
  110. package/dist/esm/memory/mmr.mjs +130 -0
  111. package/dist/esm/memory/mmr.mjs.map +1 -0
  112. package/dist/esm/memory/paths.mjs +207 -0
  113. package/dist/esm/memory/paths.mjs.map +1 -0
  114. package/dist/esm/memory/pgvectorStore.mjs +223 -0
  115. package/dist/esm/memory/pgvectorStore.mjs.map +1 -0
  116. package/dist/esm/memory/recallTracking.mjs +94 -0
  117. package/dist/esm/memory/recallTracking.mjs.map +1 -0
  118. package/dist/esm/memory/schema.sql +51 -0
  119. package/dist/esm/memory/temporalDecay.mjs +110 -0
  120. package/dist/esm/memory/temporalDecay.mjs.map +1 -0
  121. package/dist/esm/nodes/ApprovalGateNode.mjs +1 -1
  122. package/dist/esm/nodes/ApprovalGateNode.mjs.map +1 -1
  123. package/dist/esm/prompts/memoryFlushPrompt.mjs +44 -0
  124. package/dist/esm/prompts/memoryFlushPrompt.mjs.map +1 -0
  125. package/dist/esm/run.mjs +16 -3
  126. package/dist/esm/run.mjs.map +1 -1
  127. package/dist/esm/stream.mjs +4 -4
  128. package/dist/esm/stream.mjs.map +1 -1
  129. package/dist/esm/tools/AskUser.mjs +6 -1
  130. package/dist/esm/tools/AskUser.mjs.map +1 -1
  131. package/dist/esm/tools/BrowserTools.mjs +1 -1
  132. package/dist/esm/tools/BrowserTools.mjs.map +1 -1
  133. package/dist/esm/tools/ToolNode.mjs +128 -11
  134. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  135. package/dist/esm/tools/approval/constants.mjs +2 -2
  136. package/dist/esm/tools/approval/constants.mjs.map +1 -1
  137. package/dist/esm/tools/memory/index.mjs +46 -0
  138. package/dist/esm/tools/memory/index.mjs.map +1 -0
  139. package/dist/esm/tools/memory/memoryAppendTool.mjs +67 -0
  140. package/dist/esm/tools/memory/memoryAppendTool.mjs.map +1 -0
  141. package/dist/esm/tools/memory/memoryGetTool.mjs +47 -0
  142. package/dist/esm/tools/memory/memoryGetTool.mjs.map +1 -0
  143. package/dist/esm/tools/memory/memorySearchTool.mjs +63 -0
  144. package/dist/esm/tools/memory/memorySearchTool.mjs.map +1 -0
  145. package/dist/esm/tools/memory/shared.mjs +98 -0
  146. package/dist/esm/tools/memory/shared.mjs.map +1 -0
  147. package/dist/esm/types/graph.mjs.map +1 -1
  148. package/dist/esm/utils/childAgentContext.mjs +237 -0
  149. package/dist/esm/utils/childAgentContext.mjs.map +1 -0
  150. package/dist/esm/utils/events.mjs +36 -5
  151. package/dist/esm/utils/events.mjs.map +1 -1
  152. package/dist/esm/utils/finishReasons.mjs +41 -0
  153. package/dist/esm/utils/finishReasons.mjs.map +1 -0
  154. package/dist/esm/utils/llm.mjs.map +1 -1
  155. package/dist/esm/utils/logging.mjs +31 -0
  156. package/dist/esm/utils/logging.mjs.map +1 -0
  157. package/dist/esm/utils/toolCallNormalization.mjs +247 -0
  158. package/dist/esm/utils/toolCallNormalization.mjs.map +1 -0
  159. package/dist/types/common/index.d.ts +1 -0
  160. package/dist/types/common/spawnPath.d.ts +59 -0
  161. package/dist/types/graphs/HandoffRegistry.d.ts +97 -0
  162. package/dist/types/graphs/MultiAgentGraph.d.ts +58 -18
  163. package/dist/types/graphs/index.d.ts +1 -0
  164. package/dist/types/graphs/phases/flushLoop.d.ts +106 -0
  165. package/dist/types/graphs/phases/memoryFlushPhase.d.ts +100 -0
  166. package/dist/types/index.d.ts +7 -0
  167. package/dist/types/memory/__tests__/mockBackend.d.ts +40 -0
  168. package/dist/types/memory/citations.d.ts +39 -0
  169. package/dist/types/memory/compositeBackend.d.ts +30 -0
  170. package/dist/types/memory/constants.d.ts +121 -0
  171. package/dist/types/memory/embeddings.d.ts +15 -0
  172. package/dist/types/memory/factory.d.ts +23 -0
  173. package/dist/types/memory/index.d.ts +21 -0
  174. package/dist/types/memory/migrate.d.ts +14 -0
  175. package/dist/types/memory/mmr.d.ts +50 -0
  176. package/dist/types/memory/paths.d.ts +107 -0
  177. package/dist/types/memory/pgvectorStore.d.ts +56 -0
  178. package/dist/types/memory/recallTracking.d.ts +30 -0
  179. package/dist/types/memory/temporalDecay.d.ts +53 -0
  180. package/dist/types/memory/types.d.ts +182 -0
  181. package/dist/types/prompts/memoryFlushPrompt.d.ts +54 -0
  182. package/dist/types/run.d.ts +1 -0
  183. package/dist/types/tools/AskUser.d.ts +1 -1
  184. package/dist/types/tools/BrowserTools.d.ts +2 -2
  185. package/dist/types/tools/approval/constants.d.ts +2 -2
  186. package/dist/types/tools/memory/index.d.ts +39 -0
  187. package/dist/types/tools/memory/memoryAppendTool.d.ts +27 -0
  188. package/dist/types/tools/memory/memoryGetTool.d.ts +22 -0
  189. package/dist/types/tools/memory/memorySearchTool.d.ts +22 -0
  190. package/dist/types/tools/memory/shared.d.ts +106 -0
  191. package/dist/types/types/graph.d.ts +16 -3
  192. package/dist/types/utils/childAgentContext.d.ts +99 -0
  193. package/dist/types/utils/events.d.ts +21 -0
  194. package/dist/types/utils/finishReasons.d.ts +32 -0
  195. package/dist/types/utils/logging.d.ts +2 -0
  196. package/dist/types/utils/toolCallNormalization.d.ts +44 -0
  197. package/package.json +6 -4
  198. package/src/agents/AgentContext.ts +26 -3
  199. package/src/common/__tests__/enum.test.ts +4 -2
  200. package/src/common/__tests__/spawnPath.test.ts +110 -0
  201. package/src/common/index.ts +1 -0
  202. package/src/common/spawnPath.ts +101 -0
  203. package/src/graphs/Graph.ts +94 -43
  204. package/src/graphs/HandoffRegistry.ts +199 -0
  205. package/src/graphs/MultiAgentGraph.ts +694 -226
  206. package/src/graphs/__tests__/HandoffRegistry.test.ts +410 -0
  207. package/src/graphs/__tests__/multi-agent-delegate.test.ts +61 -16
  208. package/src/graphs/__tests__/multi-agent-edges.test.ts +4 -2
  209. package/src/graphs/__tests__/multi-agent-nested-subgraph.test.ts +221 -0
  210. package/src/graphs/__tests__/structured-output.integration.test.ts +212 -118
  211. package/src/graphs/contextManagement.e2e.test.ts +1 -1
  212. package/src/graphs/index.ts +1 -0
  213. package/src/graphs/phases/__tests__/flushLoop.test.ts +264 -0
  214. package/src/graphs/phases/__tests__/memoryFlushPhase.test.ts +37 -0
  215. package/src/graphs/phases/__tests__/runMemoryFlush.test.ts +150 -0
  216. package/src/graphs/phases/flushLoop.ts +303 -0
  217. package/src/graphs/phases/memoryFlushPhase.ts +209 -0
  218. package/src/index.ts +30 -1
  219. package/src/llm/bedrock/index.ts +4 -5
  220. package/src/memory/__tests__/citations.test.ts +61 -0
  221. package/src/memory/__tests__/compositeBackend.test.ts +79 -0
  222. package/src/memory/__tests__/isolation.test.ts +206 -0
  223. package/src/memory/__tests__/mmr.test.ts +148 -0
  224. package/src/memory/__tests__/mockBackend.ts +161 -0
  225. package/src/memory/__tests__/paths.test.ts +168 -0
  226. package/src/memory/__tests__/recallTracking.test.ts +96 -0
  227. package/src/memory/__tests__/temporalDecay.test.ts +151 -0
  228. package/src/memory/citations.ts +80 -0
  229. package/src/memory/compositeBackend.ts +99 -0
  230. package/src/memory/constants.ts +229 -0
  231. package/src/memory/embeddings.ts +188 -0
  232. package/src/memory/factory.ts +111 -0
  233. package/src/memory/index.ts +46 -0
  234. package/src/memory/migrate.ts +116 -0
  235. package/src/memory/mmr.ts +161 -0
  236. package/src/memory/paths.ts +258 -0
  237. package/src/memory/pgvectorStore.ts +324 -0
  238. package/src/memory/recallTracking.ts +127 -0
  239. package/src/memory/schema.sql +51 -0
  240. package/src/memory/temporalDecay.ts +134 -0
  241. package/src/memory/types.ts +185 -0
  242. package/src/nodes/ApprovalGateNode.ts +4 -10
  243. package/src/nodes/__tests__/ApprovalGateNode.test.ts +11 -20
  244. package/src/prompts/memoryFlushPrompt.ts +78 -0
  245. package/src/run.ts +17 -6
  246. package/src/scripts/test-bedrock-handoff-autonomous.ts +56 -20
  247. package/src/specs/agent-handoffs-bedrock.integration.test.ts +8 -5
  248. package/src/specs/agent-handoffs.test.ts +8 -2
  249. package/src/stream.ts +4 -6
  250. package/src/tools/AskUser.ts +7 -2
  251. package/src/tools/BrowserTools.ts +3 -5
  252. package/src/tools/ToolNode.ts +150 -13
  253. package/src/tools/__tests__/ToolApproval.test.ts +22 -9
  254. package/src/tools/approval/__tests__/constants.test.ts +4 -4
  255. package/src/tools/approval/constants.ts +2 -2
  256. package/src/tools/memory/__tests__/memoryTools.test.ts +205 -0
  257. package/src/tools/memory/index.ts +96 -0
  258. package/src/tools/memory/memoryAppendTool.ts +101 -0
  259. package/src/tools/memory/memoryGetTool.ts +53 -0
  260. package/src/tools/memory/memorySearchTool.ts +80 -0
  261. package/src/tools/memory/shared.ts +169 -0
  262. package/src/tools/search/search.test.ts +6 -1
  263. package/src/types/graph.ts +16 -3
  264. package/src/utils/__tests__/childAgentContext.test.ts +217 -0
  265. package/src/utils/__tests__/finishReasons.test.ts +55 -0
  266. package/src/utils/__tests__/toolCallNormalization.test.ts +181 -0
  267. package/src/utils/childAgentContext.ts +259 -0
  268. package/src/utils/events.ts +37 -4
  269. package/src/utils/finishReasons.ts +40 -0
  270. package/src/utils/llm.ts +0 -1
  271. package/src/utils/logging.ts +45 -8
  272. package/src/utils/toolCallNormalization.ts +271 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Autonomous memory — core types.
3
+ *
4
+ * Ported from upstream's memory-core pattern, adapted for Postgres + pgvector
5
+ * and shaped so a future graph-backend layer (Graphiti, Neo4j agent-memory, etc.)
6
+ * can be added alongside the vector store without changing the tool contracts.
7
+ *
8
+ * Architectural boundary: the LLM tools ({@link MemoryBackend}) never talk to
9
+ * Postgres directly — they go through an interface that a vector store,
10
+ * a graph store, or a composite of both can satisfy.
11
+ */
12
+
13
+ /**
14
+ * Scope key for memory isolation.
15
+ *
16
+ * Two-tier model:
17
+ * - Agent-tier rows (`memory/agent/*`) are stored with `user_id = NULL`
18
+ * and are shared across every user of the agent.
19
+ * - User-tier rows (`memory/user/*`) are stored with the caller's
20
+ * `userId` and are private to that caller; other users of the same
21
+ * agent never see them.
22
+ *
23
+ * Every read query applies `WHERE agent_id = $1 AND (user_id IS NULL OR
24
+ * user_id = $caller)`, so the caller sees shared rows plus their own.
25
+ * `userId` is therefore both the write target (for user-tier paths) AND
26
+ * the read filter. An absent `userId` is legal — it just means this
27
+ * scope is isolated/autonomous and the user tier is inert.
28
+ *
29
+ * Captured at tool construction time in a closure and NEVER exposed as
30
+ * tool parameters. The LLM cannot override either field.
31
+ */
32
+ export interface MemoryScope {
33
+ agentId: string;
34
+ /**
35
+ * The caller's identity, used for user-tier writes AND the layered
36
+ * read filter. Undefined/null for isolated/autonomous agents — in
37
+ * that case only agent-tier rows are visible and writable.
38
+ */
39
+ userId?: string | null;
40
+ }
41
+
42
+ /**
43
+ * One retrieved memory record.
44
+ *
45
+ * `score` is a unit-interval hybrid score (0..1): 0.7 * cosine + 0.3 * text
46
+ * rank in the default pgvector backend. A graph backend is free to produce
47
+ * its own score as long as it stays within that range.
48
+ */
49
+ export interface MemoryEntry {
50
+ id: string;
51
+ path: string;
52
+ content: string;
53
+ score: number;
54
+ createdAt: Date;
55
+ /**
56
+ * Which backend surfaced this entry. Lets a composite store tag results so
57
+ * downstream consumers (tests, telemetry, UI) can distinguish vector hits
58
+ * from graph hits without inspecting the ID format.
59
+ */
60
+ source?: MemoryBackendKind;
61
+ /**
62
+ * Which tier the row belongs to — derived from the path at read time.
63
+ * See `./paths.ts` for the whitelist. UI groups rows by this field
64
+ * so users can see which memories are shared (agent tier) vs private
65
+ * (user tier).
66
+ */
67
+ tier?: 'agent' | 'user';
68
+ /** Citation marker (Phase 2) — e.g. `memory/agent/playbook.md#L1-L8`. */
69
+ citation?: string;
70
+ /** 1-indexed start/end line range used to build the citation. */
71
+ startLine?: number;
72
+ endLine?: number;
73
+ }
74
+
75
+ export type MemoryBackendKind = 'vector' | 'graph' | 'composite';
76
+
77
+ export interface MemorySearchOptions {
78
+ maxResults?: number;
79
+ minScore?: number;
80
+ /**
81
+ * Phase 2 toggles — when the backend supports them. Each is independently
82
+ * opt-in; all false = upstream Phase 1 behavior.
83
+ */
84
+ mmr?: { enabled?: boolean; lambda?: number };
85
+ temporalDecay?: { enabled?: boolean; halfLifeDays?: number };
86
+ /** Citation mode: 'on' decorates; 'off' strips; 'auto' = on for direct chat. */
87
+ citations?: 'on' | 'off' | 'auto';
88
+ }
89
+
90
+ export interface MemoryGetOptions {
91
+ path: string;
92
+ from?: number;
93
+ lines?: number;
94
+ }
95
+
96
+ export interface MemoryReadResult {
97
+ path: string;
98
+ text: string;
99
+ }
100
+
101
+ export interface MemoryAppendInput {
102
+ /** Must begin with "memory/". Enforced by the append tool wrapper. */
103
+ path: string;
104
+ content: string;
105
+ }
106
+
107
+ export interface MemoryHealth {
108
+ ok: boolean;
109
+ backend: MemoryBackendKind;
110
+ error?: string;
111
+ }
112
+
113
+ /**
114
+ * The contract every memory backend implements.
115
+ *
116
+ * A Phase 1 pgvector store satisfies this directly. A Phase 4 graph store
117
+ * (Graphiti / Neo4j agent-memory) will implement the same interface and plug
118
+ * into a {@link CompositeMemoryBackend} without touching the tools.
119
+ */
120
+ export interface MemoryBackend {
121
+ readonly kind: MemoryBackendKind;
122
+ search(
123
+ scope: MemoryScope,
124
+ query: string,
125
+ opts?: MemorySearchOptions
126
+ ): Promise<MemoryEntry[]>;
127
+ get(
128
+ scope: MemoryScope,
129
+ opts: MemoryGetOptions
130
+ ): Promise<MemoryReadResult | null>;
131
+ append(scope: MemoryScope, input: MemoryAppendInput): Promise<void>;
132
+ health(): Promise<MemoryHealth>;
133
+ }
134
+
135
+ /**
136
+ * Historical alias kept for one release so external consumers can still import
137
+ * the name used in the plan doc. New code should prefer {@link MemoryBackend}.
138
+ *
139
+ * @deprecated use {@link MemoryBackend}
140
+ */
141
+ export type MemoryStore = MemoryBackend;
142
+
143
+ /**
144
+ * Optional configuration bundle plumbed through `createAgentNode`.
145
+ * Missing config = memory fully disabled, no tools attached.
146
+ */
147
+ export interface MemoryConfig {
148
+ backend: MemoryBackend;
149
+ scope: MemoryScope;
150
+ flush?: {
151
+ softThresholdTokens?: number;
152
+ reserveFloorTokens?: number;
153
+ windowTokens?: number;
154
+ enabled?: boolean;
155
+ };
156
+ search?: {
157
+ maxResults?: number;
158
+ maxInjectedChars?: number;
159
+ /** Phase 2 — enable MMR reranking (upstream-aligned defaults when true). */
160
+ mmr?: { enabled?: boolean; lambda?: number };
161
+ /** Phase 2 — enable temporal decay on dated memory files. */
162
+ temporalDecay?: { enabled?: boolean; halfLifeDays?: number };
163
+ /** Phase 2 — citation mode. Defaults to 'auto'. */
164
+ citations?: 'on' | 'off' | 'auto';
165
+ };
166
+ /**
167
+ * Phase 2 — optional recall tracker. When set, memory_search fires a
168
+ * best-effort recall record after each successful call. Failures are
169
+ * swallowed so recall tracking never blocks the tool result.
170
+ */
171
+ recallTracker?: {
172
+ record(params: {
173
+ agentId: string;
174
+ query: string;
175
+ hits: Array<{ id: string; path: string; score: number }>;
176
+ }): Promise<void>;
177
+ };
178
+ /**
179
+ * Phase state accessor. Returns the current phase so the append-tool wrapper
180
+ * can gate writes to the reflection phase. Supplied by the graph runtime.
181
+ */
182
+ getPhase?: () => MemoryPhase;
183
+ }
184
+
185
+ export type MemoryPhase = 'normal' | 'memory_flushing';
@@ -50,15 +50,9 @@ export interface ApprovalGateInterrupt {
50
50
  export function createApprovalGateNode(
51
51
  config: ApprovalGateConfig,
52
52
  sourceAgentId: string,
53
- destinationAgentId: string,
53
+ destinationAgentId: string
54
54
  ) {
55
- const {
56
- gateId,
57
- channel = 'chat',
58
- prompt,
59
- approver,
60
- timeoutMs,
61
- } = config;
55
+ const { gateId, channel = 'chat', prompt, approver, timeoutMs } = config;
62
56
 
63
57
  /**
64
58
  * The gate node function. Receives the current graph state,
@@ -67,7 +61,7 @@ export function createApprovalGateNode(
67
61
  */
68
62
  return async function approvalGateNode(
69
63
  state: BaseGraphState,
70
- runnableConfig?: RunnableConfig,
64
+ runnableConfig?: RunnableConfig
71
65
  ): Promise<Partial<BaseGraphState>> {
72
66
  const interruptPayload: ApprovalGateInterrupt = {
73
67
  type: 'approval_gate',
@@ -87,7 +81,7 @@ export function createApprovalGateNode(
87
81
  safeDispatchCustomEvent(
88
82
  GraphEvents.ON_APPROVAL_GATE,
89
83
  interruptPayload,
90
- runnableConfig,
84
+ runnableConfig
91
85
  );
92
86
 
93
87
  // Pause the graph — state is checkpointed by the MongoDBSaver.
@@ -81,7 +81,7 @@ describe('ApprovalGateNode', () => {
81
81
  // First invoke: Agent A runs, then gate interrupts
82
82
  const result = await compiled.invoke(
83
83
  { messages: [new HumanMessage('start')] },
84
- config,
84
+ config
85
85
  );
86
86
 
87
87
  // Check that the graph was interrupted (Agent B should NOT have run)
@@ -92,13 +92,13 @@ describe('ApprovalGateNode', () => {
92
92
  // Agent A should have run
93
93
  const messages = result.messages as BaseMessage[];
94
94
  const hasAgentA = messages.some(
95
- (m: any) => m._getType?.() === 'ai' && m.content === 'Agent A done',
95
+ (m: any) => m._getType?.() === 'ai' && m.content === 'Agent A done'
96
96
  );
97
97
  expect(hasAgentA).toBe(true);
98
98
 
99
99
  // Agent B should NOT have run
100
100
  const hasAgentB = messages.some(
101
- (m: any) => m._getType?.() === 'ai' && m.content === 'Agent B done',
101
+ (m: any) => m._getType?.() === 'ai' && m.content === 'Agent B done'
102
102
  );
103
103
  expect(hasAgentB).toBe(false);
104
104
  });
@@ -114,21 +114,18 @@ describe('ApprovalGateNode', () => {
114
114
  };
115
115
 
116
116
  // First invoke: hits gate interrupt
117
- await compiled.invoke(
118
- { messages: [new HumanMessage('start')] },
119
- config,
120
- );
117
+ await compiled.invoke({ messages: [new HumanMessage('start')] }, config);
121
118
 
122
119
  // Resume with approval
123
120
  const result = await compiled.invoke(
124
121
  new Command({ resume: { approved: true } }),
125
- config,
122
+ config
126
123
  );
127
124
 
128
125
  // Agent B should have run after approval
129
126
  const messages = result.messages as BaseMessage[];
130
127
  const hasAgentB = messages.some(
131
- (m: any) => m._getType?.() === 'ai' && m.content === 'Agent B done',
128
+ (m: any) => m._getType?.() === 'ai' && m.content === 'Agent B done'
132
129
  );
133
130
  expect(hasAgentB).toBe(true);
134
131
  });
@@ -145,10 +142,7 @@ describe('ApprovalGateNode', () => {
145
142
  configurable: { thread_id: 'test-thread-3' },
146
143
  };
147
144
 
148
- await compiled.invoke(
149
- { messages: [new HumanMessage('start')] },
150
- config,
151
- );
145
+ await compiled.invoke({ messages: [new HumanMessage('start')] }, config);
152
146
 
153
147
  // safeDispatchCustomEvent should have been called with gate data
154
148
  expect(safeDispatchCustomEvent).toHaveBeenCalledWith(
@@ -162,7 +156,7 @@ describe('ApprovalGateNode', () => {
162
156
  sourceAgentId: 'agent_a',
163
157
  destinationAgentId: 'agent_b',
164
158
  }),
165
- expect.anything(),
159
+ expect.anything()
166
160
  );
167
161
  });
168
162
 
@@ -176,15 +170,12 @@ describe('ApprovalGateNode', () => {
176
170
  };
177
171
 
178
172
  // First invoke: hits gate interrupt
179
- await compiled.invoke(
180
- { messages: [new HumanMessage('start')] },
181
- config,
182
- );
173
+ await compiled.invoke({ messages: [new HumanMessage('start')] }, config);
183
174
 
184
175
  // Resume with denial
185
176
  const result = await compiled.invoke(
186
177
  new Command({ resume: { approved: false, feedback: 'Not ready' } }),
187
- config,
178
+ config
188
179
  );
189
180
 
190
181
  // Agent B still runs (the gate doesn't block — it's the host's
@@ -192,7 +183,7 @@ describe('ApprovalGateNode', () => {
192
183
  // In the current implementation, the gate node returns {} regardless.
193
184
  const messages = result.messages as BaseMessage[];
194
185
  const hasAgentB = messages.some(
195
- (m: any) => m._getType?.() === 'ai' && m.content === 'Agent B done',
186
+ (m: any) => m._getType?.() === 'ai' && m.content === 'Agent B done'
196
187
  );
197
188
  expect(hasAgentB).toBe(true);
198
189
  });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Memory-flush prompt resolver.
3
+ *
4
+ * The raw prompt strings live in `src/memory/constants.ts` and contain a
5
+ * single `{{MEMORY_PATHS_RUBRIC}}` placeholder. This module substitutes
6
+ * the caller-scoped rubric (produced by `renderPathsRubric()` in
7
+ * `src/memory/paths.ts`) into that placeholder at flush time, so:
8
+ *
9
+ * - a collaborative agent with a real caller sees all 8 paths
10
+ * - an isolated/autonomous agent sees only the 4 agent-tier paths
11
+ * (user-tier rows are physically omitted from the rubric the LLM
12
+ * sees, so it cannot route writes there even by mistake)
13
+ *
14
+ * ## Why this lives in prompts/ and not memory/constants.ts
15
+ *
16
+ * The constants file is pure strings — no imports, no runtime work.
17
+ * Injecting the rubric needs access to `renderPathsRubric()` which
18
+ * needs `MemoryScope`, so the substitution has to happen one layer up.
19
+ * Keeping the placeholder in constants and the substitution here means
20
+ * tests of the raw prompt (no scope) and tests of the rendered prompt
21
+ * (scope-aware) stay independent.
22
+ */
23
+ import {
24
+ DEFAULT_MEMORY_FLUSH_PROMPT,
25
+ DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT,
26
+ FLUSH_PROMPT_RUBRIC_PLACEHOLDER,
27
+ } from '@/memory/constants';
28
+ import { renderPathsRubric } from '@/memory/paths';
29
+ import type { MemoryScope } from '@/memory/types';
30
+
31
+ export { DEFAULT_MEMORY_FLUSH_PROMPT, DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT };
32
+
33
+ /** Back-compat alias so existing callers keep working. */
34
+ export const MEMORY_FLUSH_SYSTEM_PROMPT = DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT;
35
+
36
+ /** Minimum scope shape needed to render the rubric. */
37
+ export interface ResolveFlushPromptsOptions {
38
+ /**
39
+ * The scope the memory tools were built with. Only `userId` matters
40
+ * for rubric rendering — if absent, the user-tier paths are filtered
41
+ * out of the rubric the LLM sees.
42
+ */
43
+ scope: Pick<MemoryScope, 'userId'>;
44
+ }
45
+
46
+ /**
47
+ * Result shape returned to `runMemoryFlush`. `prompt` goes in the
48
+ * HumanMessage, `systemPrompt` goes in the SystemMessage. Both already
49
+ * have the rubric substituted — the caller should pass them through
50
+ * verbatim.
51
+ */
52
+ export interface ResolvedFlushPrompts {
53
+ prompt: string;
54
+ systemPrompt: string;
55
+ }
56
+
57
+ /**
58
+ * Substitutes the paths rubric into the raw prompts for a given scope.
59
+ *
60
+ * Idempotent (calling it twice on already-resolved strings is a no-op
61
+ * because the placeholder will have been replaced already). Safe to
62
+ * call per-flush; no caching needed — the string concat is microseconds.
63
+ */
64
+ export function resolveFlushPrompts(
65
+ options: ResolveFlushPromptsOptions
66
+ ): ResolvedFlushPrompts {
67
+ const rubric = renderPathsRubric(options.scope);
68
+ return {
69
+ prompt: DEFAULT_MEMORY_FLUSH_PROMPT.replaceAll(
70
+ FLUSH_PROMPT_RUBRIC_PLACEHOLDER,
71
+ rubric
72
+ ),
73
+ systemPrompt: DEFAULT_MEMORY_FLUSH_SYSTEM_PROMPT.replaceAll(
74
+ FLUSH_PROMPT_RUBRIC_PLACEHOLDER,
75
+ rubric
76
+ ),
77
+ };
78
+ }
package/src/run.ts CHANGED
@@ -47,6 +47,7 @@ export class Run<_T extends t.BaseGraphState> {
47
47
  Graph: StandardGraph | MultiAgentGraph | undefined;
48
48
  returnContent: boolean = false;
49
49
  private skipCleanup: boolean = false;
50
+ private _originalCallbacksSnapshot?: t.ProvidedCallbacks;
50
51
 
51
52
  private constructor(config: Partial<t.RunConfig>) {
52
53
  const runId = config.runId ?? '';
@@ -280,7 +281,19 @@ export class Run<_T extends t.BaseGraphState> {
280
281
  /** Custom event callback to intercept and handle custom events */
281
282
  const customEventCallback = this.createCustomEventCallback();
282
283
 
283
- const baseCallbacks = (config.callbacks as t.ProvidedCallbacks) ?? [];
284
+ // IMPORTANT: snapshot the ORIGINAL caller-provided callbacks once and
285
+ // reuse that snapshot on every invocation. Previously we read
286
+ // `config.callbacks` on each call and concat'd onto it — but the same
287
+ // `config` object is passed back to processStream() on HITL resume, so
288
+ // the previous run's appended callbacks became the new "base" and we
289
+ // ended up stacking duplicate stream + custom handlers on every cycle.
290
+ // That produced N-times duplicated message_delta events on the SSE
291
+ // stream (character-interleaved text in the UI after tool approval).
292
+ if (!this._originalCallbacksSnapshot) {
293
+ this._originalCallbacksSnapshot =
294
+ (config.callbacks as t.ProvidedCallbacks) ?? [];
295
+ }
296
+ const baseCallbacks = this._originalCallbacksSnapshot;
284
297
  const streamCallbacks = streamOptions?.callbacks
285
298
  ? this.getCallbacks(streamOptions.callbacks)
286
299
  : [];
@@ -435,15 +448,13 @@ export class Run<_T extends t.BaseGraphState> {
435
448
  * Requires a checkpointer to be configured — without one, interrupt state
436
449
  * is not persisted and this always returns false.
437
450
  */
438
- async hasInterrupts(
439
- config: Partial<RunnableConfig>
440
- ): Promise<boolean> {
451
+ async hasInterrupts(config: Partial<RunnableConfig>): Promise<boolean> {
441
452
  if (!this.graphRunnable) {
442
453
  return false;
443
454
  }
444
455
  try {
445
456
  const state = await this.graphRunnable.getState(config);
446
- return state.tasks?.some((task) => task.interrupts?.length > 0) ?? false;
457
+ return state.tasks.some((task) => task.interrupts.length > 0) ?? false;
447
458
  } catch {
448
459
  return false;
449
460
  }
@@ -463,7 +474,7 @@ export class Run<_T extends t.BaseGraphState> {
463
474
  try {
464
475
  const state = await this.graphRunnable.getState(config);
465
476
  return (
466
- state.tasks?.flatMap((task) =>
477
+ state.tasks.flatMap((task) =>
467
478
  (task.interrupts ?? []).map((i) => i.value)
468
479
  ) ?? []
469
480
  );
@@ -18,7 +18,12 @@
18
18
  import { config } from 'dotenv';
19
19
  config();
20
20
  import { resolve } from 'path';
21
- config({ path: resolve(process.cwd(), '..', 'ranger', '.env'), override: false });
21
+ if (process.env.AGENTS_TEST_ENV_PATH) {
22
+ config({
23
+ path: resolve(process.env.AGENTS_TEST_ENV_PATH),
24
+ override: false,
25
+ });
26
+ }
22
27
 
23
28
  // Disable observability for this test (requires @illuma-ai/observability-node)
24
29
  delete process.env.ILLUMA_SECRET_KEY;
@@ -34,7 +39,9 @@ import type * as t from '@/types';
34
39
  import type { RunnableConfig } from '@langchain/core/runnables';
35
40
 
36
41
  const bedrockRegion =
37
- process.env.BEDROCK_AWS_REGION ?? process.env.BEDROCK_AWS_DEFAULT_REGION ?? 'us-east-1';
42
+ process.env.BEDROCK_AWS_REGION ??
43
+ process.env.BEDROCK_AWS_DEFAULT_REGION ??
44
+ 'us-east-1';
38
45
 
39
46
  const bedrockOptions = {
40
47
  model: 'us.anthropic.claude-3-5-haiku-20241022-v1:0',
@@ -116,7 +123,10 @@ Do NOT delegate or transfer. Just write the content and respond.`
116
123
  [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
117
124
  [GraphEvents.ON_RUN_STEP]: {
118
125
  handle: (event: string, data: t.StreamEventData): void => {
119
- aggregateContent({ event: event as GraphEvents, data: data as t.RunStep });
126
+ aggregateContent({
127
+ event: event as GraphEvents,
128
+ data: data as t.RunStep,
129
+ });
120
130
  },
121
131
  },
122
132
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
@@ -129,7 +139,10 @@ Do NOT delegate or transfer. Just write the content and respond.`
129
139
  },
130
140
  [GraphEvents.ON_MESSAGE_DELTA]: {
131
141
  handle: (event: string, data: t.StreamEventData): void => {
132
- aggregateContent({ event: event as GraphEvents, data: data as t.MessageDeltaEvent });
142
+ aggregateContent({
143
+ event: event as GraphEvents,
144
+ data: data as t.MessageDeltaEvent,
145
+ });
133
146
  },
134
147
  },
135
148
  };
@@ -145,7 +158,10 @@ Do NOT delegate or transfer. Just write the content and respond.`
145
158
  returnContent: true,
146
159
  });
147
160
 
148
- const streamConfig: Partial<RunnableConfig> & { version: 'v1' | 'v2'; streamMode: string } = {
161
+ const streamConfig: Partial<RunnableConfig> & {
162
+ version: 'v1' | 'v2';
163
+ streamMode: string;
164
+ } = {
149
165
  configurable: { thread_id: 'bedrock-autonomous-test' },
150
166
  streamMode: 'values',
151
167
  version: 'v2',
@@ -153,25 +169,34 @@ Do NOT delegate or transfer. Just write the content and respond.`
153
169
 
154
170
  // ── Test 1: Single handoff (Orchestrator → Researcher → back) ──
155
171
  console.log('\n--- Test 1: Single Handoff (Research) ---');
156
- console.log('Query: "What are the top 3 trends in enterprise AI adoption in 2025?"');
172
+ console.log(
173
+ 'Query: "What are the top 3 trends in enterprise AI adoption in 2025?"'
174
+ );
157
175
 
158
176
  const messages1 = [
159
- new HumanMessage('What are the top 3 trends in enterprise AI adoption in 2025?'),
177
+ new HumanMessage(
178
+ 'What are the top 3 trends in enterprise AI adoption in 2025?'
179
+ ),
160
180
  ];
161
181
 
162
182
  try {
163
183
  await run.processStream({ messages: messages1 }, streamConfig);
164
184
 
165
185
  const finalMessages = run.getRunMessages();
166
- console.log(`\n✓ Graph completed. Total messages: ${finalMessages?.length ?? 0}`);
186
+ console.log(
187
+ `\n✓ Graph completed. Total messages: ${finalMessages?.length ?? 0}`
188
+ );
167
189
 
168
190
  // Check that a handoff happened
169
191
  const lastMsg = finalMessages?.[finalMessages.length - 1];
170
192
  if (lastMsg) {
171
- const content = typeof lastMsg.content === 'string'
172
- ? lastMsg.content
173
- : JSON.stringify(lastMsg.content);
174
- console.log(`\n✓ Final response (first 500 chars):\n${content.slice(0, 500)}`);
193
+ const content =
194
+ typeof lastMsg.content === 'string'
195
+ ? lastMsg.content
196
+ : JSON.stringify(lastMsg.content);
197
+ console.log(
198
+ `\n✓ Final response (first 500 chars):\n${content.slice(0, 500)}`
199
+ );
175
200
  }
176
201
 
177
202
  // Verify last active agent tracking
@@ -186,7 +211,9 @@ Do NOT delegate or transfer. Just write the content and respond.`
186
211
 
187
212
  // ── Test 2: Multi-hop (Orchestrator → Researcher → back → Writer → back) ──
188
213
  console.log('\n\n--- Test 2: Multi-Hop Handoff (Research → Write) ---');
189
- console.log('Query: "Research AI agent frameworks and write a brief summary email"');
214
+ console.log(
215
+ 'Query: "Research AI agent frameworks and write a brief summary email"'
216
+ );
190
217
 
191
218
  const run2 = await Run.create({
192
219
  runId: `bedrock-autonomous-handoff-2-${Date.now()}`,
@@ -199,27 +226,36 @@ Do NOT delegate or transfer. Just write the content and respond.`
199
226
  });
200
227
 
201
228
  const messages2 = [
202
- new HumanMessage('Research the latest AI agent frameworks and write a brief 3-sentence summary.'),
229
+ new HumanMessage(
230
+ 'Research the latest AI agent frameworks and write a brief 3-sentence summary.'
231
+ ),
203
232
  ];
204
233
 
205
234
  try {
206
235
  await run2.processStream({ messages: messages2 }, streamConfig);
207
236
 
208
237
  const finalMessages2 = run2.getRunMessages();
209
- console.log(`\n✓ Graph completed. Total messages: ${finalMessages2?.length ?? 0}`);
238
+ console.log(
239
+ `\n✓ Graph completed. Total messages: ${finalMessages2?.length ?? 0}`
240
+ );
210
241
 
211
242
  const lastMsg2 = finalMessages2?.[finalMessages2.length - 1];
212
243
  if (lastMsg2) {
213
- const content = typeof lastMsg2.content === 'string'
214
- ? lastMsg2.content
215
- : JSON.stringify(lastMsg2.content);
216
- console.log(`\n✓ Final response (first 500 chars):\n${content.slice(0, 500)}`);
244
+ const content =
245
+ typeof lastMsg2.content === 'string'
246
+ ? lastMsg2.content
247
+ : JSON.stringify(lastMsg2.content);
248
+ console.log(
249
+ `\n✓ Final response (first 500 chars):\n${content.slice(0, 500)}`
250
+ );
217
251
  }
218
252
 
219
253
  const lastActiveAgentId2 = run2.getLastActiveAgentId?.();
220
254
  console.log(`\n✓ lastActiveAgentId: ${lastActiveAgentId2}`);
221
255
 
222
- console.log('\n✅ Test 2 PASSED — Multi-hop handoff completed successfully');
256
+ console.log(
257
+ '\n✅ Test 2 PASSED — Multi-hop handoff completed successfully'
258
+ );
223
259
  } catch (err) {
224
260
  console.error('\n❌ Test 2 FAILED:', (err as Error).message);
225
261
  console.error((err as Error).stack);
@@ -10,12 +10,15 @@
10
10
  */
11
11
  import { config } from 'dotenv';
12
12
  import { resolve } from 'path';
13
- // Load from local .env first, then fall back to ranger's .env for Bedrock credentials
13
+ // Load local .env first; optionally fall back to a host-provided env file
14
+ // pointed at by AGENTS_TEST_ENV_PATH.
14
15
  config();
15
- config({
16
- path: resolve(process.cwd(), '..', 'ranger', '.env'),
17
- override: false,
18
- });
16
+ if (process.env.AGENTS_TEST_ENV_PATH) {
17
+ config({
18
+ path: resolve(process.env.AGENTS_TEST_ENV_PATH),
19
+ override: false,
20
+ });
21
+ }
19
22
 
20
23
  import { HumanMessage, ToolMessage } from '@langchain/core/messages';
21
24
  import type { RunnableConfig } from '@langchain/core/runnables';
@@ -1397,10 +1397,16 @@ describe('Agent Handoffs Tests', () => {
1397
1397
  });
1398
1398
 
1399
1399
  // Override test model to respond directly (no transfer)
1400
- run.Graph?.overrideTestModel(['I am the writer, continuing your work.'], 10);
1400
+ run.Graph?.overrideTestModel(
1401
+ ['I am the writer, continuing your work.'],
1402
+ 10
1403
+ );
1401
1404
 
1402
1405
  const messages = [new HumanMessage('Make the intro shorter')];
1403
- const config: Partial<RunnableConfig> & { version: 'v1' | 'v2'; streamMode: string } = {
1406
+ const config: Partial<RunnableConfig> & {
1407
+ version: 'v1' | 'v2';
1408
+ streamMode: string;
1409
+ } = {
1404
1410
  configurable: { thread_id: 'resume-exec-test' },
1405
1411
  streamMode: 'values',
1406
1412
  version: 'v2' as const,