@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,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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 ??
|
|
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({
|
|
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({
|
|
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> & {
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
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> & {
|
|
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,
|