@hazeljs/agent 0.2.0-beta.8 → 0.2.0-beta.80
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/LICENSE +192 -21
- package/README.md +372 -258
- package/coverage/clover.xml +853 -576
- package/coverage/lcov-report/index.html +107 -62
- package/coverage/lcov.info +1428 -945
- package/dist/agent.module.d.ts +16 -1
- package/dist/agent.module.d.ts.map +1 -1
- package/dist/agent.module.js +14 -5
- package/dist/agent.module.js.map +1 -1
- package/dist/decorators/delegate.decorator.d.ts +66 -0
- package/dist/decorators/delegate.decorator.d.ts.map +1 -0
- package/dist/decorators/delegate.decorator.js +108 -0
- package/dist/decorators/delegate.decorator.js.map +1 -0
- package/dist/executor/agent.executor.d.ts +1 -0
- package/dist/executor/agent.executor.d.ts.map +1 -1
- package/dist/executor/agent.executor.js +19 -10
- package/dist/executor/agent.executor.js.map +1 -1
- package/dist/executor/tool.executor.d.ts +3 -1
- package/dist/executor/tool.executor.d.ts.map +1 -1
- package/dist/executor/tool.executor.js +33 -2
- package/dist/executor/tool.executor.js.map +1 -1
- package/dist/graph/agent-graph.d.ts +131 -0
- package/dist/graph/agent-graph.d.ts.map +1 -0
- package/dist/graph/agent-graph.js +462 -0
- package/dist/graph/agent-graph.js.map +1 -0
- package/dist/graph/agent-graph.types.d.ts +210 -0
- package/dist/graph/agent-graph.types.d.ts.map +1 -0
- package/dist/graph/agent-graph.types.js +12 -0
- package/dist/graph/agent-graph.types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/prompts/agent-system.prompt.d.ts +10 -0
- package/dist/prompts/agent-system.prompt.d.ts.map +1 -0
- package/dist/prompts/agent-system.prompt.js +18 -0
- package/dist/prompts/agent-system.prompt.js.map +1 -0
- package/dist/prompts/index.d.ts +4 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +20 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/supervisor-routing.prompt.d.ts +9 -0
- package/dist/prompts/supervisor-routing.prompt.d.ts.map +1 -0
- package/dist/prompts/supervisor-routing.prompt.js +22 -0
- package/dist/prompts/supervisor-routing.prompt.js.map +1 -0
- package/dist/prompts/supervisor-system.prompt.d.ts +9 -0
- package/dist/prompts/supervisor-system.prompt.d.ts.map +1 -0
- package/dist/prompts/supervisor-system.prompt.js +21 -0
- package/dist/prompts/supervisor-system.prompt.js.map +1 -0
- package/dist/runtime/agent.runtime.d.ts +65 -2
- package/dist/runtime/agent.runtime.d.ts.map +1 -1
- package/dist/runtime/agent.runtime.js +105 -4
- package/dist/runtime/agent.runtime.js.map +1 -1
- package/dist/supervisor/supervisor.d.ts +81 -0
- package/dist/supervisor/supervisor.d.ts.map +1 -0
- package/dist/supervisor/supervisor.js +220 -0
- package/dist/supervisor/supervisor.js.map +1 -0
- package/dist/types/agent.types.d.ts +15 -0
- package/dist/types/agent.types.d.ts.map +1 -1
- package/dist/types/agent.types.js.map +1 -1
- package/dist/types/event.types.d.ts +76 -1
- package/dist/types/event.types.d.ts.map +1 -1
- package/dist/types/event.types.js +16 -0
- package/dist/types/event.types.js.map +1 -1
- package/dist/utils/circuit-breaker.d.ts +5 -65
- package/dist/utils/circuit-breaker.d.ts.map +1 -1
- package/dist/utils/circuit-breaker.js +10 -150
- package/dist/utils/circuit-breaker.js.map +1 -1
- package/logs/combined.log +1 -1
- package/package.json +23 -8
- package/tsconfig.tsbuildinfo +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
# @hazeljs/agent
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**Build AI agents that actually do things.**
|
|
4
|
+
|
|
5
|
+
Stateful, tool-using, memory-enabled. Define tools with `@Tool`, delegate between agents with `@Delegate`, orchestrate multi-agent pipelines with `AgentGraph`, and route tasks automatically with `SupervisorAgent`. Production-grade agent infrastructure without the complexity.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@hazeljs/agent)
|
|
8
|
+
[](https://www.npmjs.com/package/@hazeljs/agent)
|
|
9
|
+
[](https://www.apache.org/licenses/LICENSE-2.0)
|
|
4
10
|
|
|
5
11
|
## Overview
|
|
6
12
|
|
|
7
|
-
|
|
13
|
+
Unlike stateless request handlers, agents are:
|
|
14
|
+
|
|
15
|
+
- **Stateful** — Maintain context across multiple steps and sessions
|
|
16
|
+
- **Long-running** — Execute complex, multi-hop workflows over time
|
|
17
|
+
- **Tool-using** — Call functions safely with timeout, retry, and approval workflows
|
|
18
|
+
- **Multi-agent** — Orchestrate teams of specialised agents with `AgentGraph`, `SupervisorAgent`, and `@Delegate`
|
|
19
|
+
- **Memory-enabled** — Integrate with persistent memory systems
|
|
20
|
+
- **Observable** — Full event system for monitoring and debugging
|
|
21
|
+
- **Resumable** — Support pause/resume and human-in-the-loop
|
|
8
22
|
|
|
9
|
-
|
|
10
|
-
- **Long-running** - Execute complex workflows over time
|
|
11
|
-
- **Tool-using** - Call functions safely with approval workflows
|
|
12
|
-
- **Memory-enabled** - Integrate with persistent memory systems
|
|
13
|
-
- **Observable** - Full event system for monitoring and debugging
|
|
14
|
-
- **Resumable** - Support pause/resume and human-in-the-loop
|
|
23
|
+
---
|
|
15
24
|
|
|
16
25
|
## Installation
|
|
17
26
|
|
|
@@ -19,9 +28,11 @@ The Agent Runtime is a core primitive in HazelJS designed for building productio
|
|
|
19
28
|
npm install @hazeljs/agent @hazeljs/core @hazeljs/rag
|
|
20
29
|
```
|
|
21
30
|
|
|
22
|
-
|
|
31
|
+
---
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
## Quick Start — Single Agent
|
|
34
|
+
|
|
35
|
+
### 1. Define an agent
|
|
25
36
|
|
|
26
37
|
```typescript
|
|
27
38
|
import { Agent, Tool } from '@hazeljs/agent';
|
|
@@ -36,242 +47,375 @@ import { Agent, Tool } from '@hazeljs/agent';
|
|
|
36
47
|
export class SupportAgent {
|
|
37
48
|
@Tool({
|
|
38
49
|
description: 'Look up order information by order ID',
|
|
39
|
-
parameters: [
|
|
40
|
-
{
|
|
41
|
-
name: 'orderId',
|
|
42
|
-
type: 'string',
|
|
43
|
-
description: 'The order ID to lookup',
|
|
44
|
-
required: true,
|
|
45
|
-
},
|
|
46
|
-
],
|
|
50
|
+
parameters: [{ name: 'orderId', type: 'string', description: 'The order ID', required: true }],
|
|
47
51
|
})
|
|
48
52
|
async lookupOrder(input: { orderId: string }) {
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
orderId: input.orderId,
|
|
52
|
-
status: 'shipped',
|
|
53
|
-
trackingNumber: 'TRACK123',
|
|
54
|
-
};
|
|
53
|
+
return { orderId: input.orderId, status: 'shipped', trackingNumber: 'TRACK123' };
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
@Tool({
|
|
58
57
|
description: 'Process a refund for an order',
|
|
59
|
-
requiresApproval: true,
|
|
58
|
+
requiresApproval: true, // requires human approval before execution
|
|
60
59
|
parameters: [
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
type: 'string',
|
|
64
|
-
description: 'The order ID to refund',
|
|
65
|
-
required: true,
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
name: 'amount',
|
|
69
|
-
type: 'number',
|
|
70
|
-
description: 'Refund amount',
|
|
71
|
-
required: true,
|
|
72
|
-
},
|
|
60
|
+
{ name: 'orderId', type: 'string', required: true },
|
|
61
|
+
{ name: 'amount', type: 'number', required: true },
|
|
73
62
|
],
|
|
74
63
|
})
|
|
75
64
|
async processRefund(input: { orderId: string; amount: number }) {
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
success: true,
|
|
79
|
-
refundId: 'REF123',
|
|
80
|
-
amount: input.amount,
|
|
81
|
-
};
|
|
65
|
+
return { success: true, refundId: 'REF123', amount: input.amount };
|
|
82
66
|
}
|
|
83
67
|
}
|
|
84
68
|
```
|
|
85
69
|
|
|
86
|
-
### 2. Set
|
|
70
|
+
### 2. Set up the runtime
|
|
87
71
|
|
|
88
72
|
```typescript
|
|
89
73
|
import { AgentRuntime } from '@hazeljs/agent';
|
|
90
74
|
import { MemoryManager } from '@hazeljs/rag';
|
|
91
75
|
import { AIService } from '@hazeljs/ai';
|
|
92
76
|
|
|
93
|
-
// Initialize dependencies
|
|
94
|
-
const memoryManager = new MemoryManager(/* ... */);
|
|
95
|
-
const aiService = new AIService({ provider: 'openai' });
|
|
96
|
-
|
|
97
|
-
// Create runtime
|
|
98
77
|
const runtime = new AgentRuntime({
|
|
99
|
-
memoryManager,
|
|
100
|
-
llmProvider:
|
|
78
|
+
memoryManager: new MemoryManager(/* ... */),
|
|
79
|
+
llmProvider: new AIService({ provider: 'openai' }),
|
|
101
80
|
defaultMaxSteps: 10,
|
|
102
81
|
enableObservability: true,
|
|
103
82
|
});
|
|
104
83
|
|
|
105
|
-
|
|
106
|
-
const supportAgent = new SupportAgent();
|
|
84
|
+
const agent = new SupportAgent();
|
|
107
85
|
runtime.registerAgent(SupportAgent);
|
|
108
|
-
runtime.registerAgentInstance('support-agent',
|
|
86
|
+
runtime.registerAgentInstance('support-agent', agent);
|
|
109
87
|
```
|
|
110
88
|
|
|
111
|
-
### 3. Execute
|
|
89
|
+
### 3. Execute
|
|
112
90
|
|
|
113
91
|
```typescript
|
|
114
|
-
// Execute agent
|
|
115
92
|
const result = await runtime.execute(
|
|
116
93
|
'support-agent',
|
|
117
|
-
'I need to check my order
|
|
118
|
-
{
|
|
119
|
-
sessionId: 'user-session-123',
|
|
120
|
-
userId: 'user-456',
|
|
121
|
-
enableMemory: true,
|
|
122
|
-
enableRAG: true,
|
|
123
|
-
}
|
|
94
|
+
'I need to check my order #12345',
|
|
95
|
+
{ sessionId: 'user-session-123', userId: 'user-456', enableMemory: true },
|
|
124
96
|
);
|
|
125
97
|
|
|
126
98
|
console.log(result.response);
|
|
127
99
|
console.log(`Completed in ${result.steps.length} steps`);
|
|
128
100
|
```
|
|
129
101
|
|
|
130
|
-
### 4. Handle
|
|
102
|
+
### 4. Handle human-in-the-loop
|
|
131
103
|
|
|
132
104
|
```typescript
|
|
133
|
-
// Subscribe to approval requests
|
|
134
105
|
runtime.on('tool.approval.requested', async (event) => {
|
|
135
106
|
console.log('Approval needed:', event.data);
|
|
136
|
-
|
|
137
|
-
// Approve or reject
|
|
138
107
|
runtime.approveToolExecution(event.data.requestId, 'admin-user');
|
|
139
|
-
// or
|
|
140
|
-
// runtime.rejectToolExecution(event.data.requestId);
|
|
141
108
|
});
|
|
142
109
|
|
|
143
|
-
|
|
144
|
-
const resumedResult = await runtime.resume(result.executionId);
|
|
110
|
+
const resumed = await runtime.resume(result.executionId);
|
|
145
111
|
```
|
|
146
112
|
|
|
147
|
-
|
|
113
|
+
---
|
|
148
114
|
|
|
149
|
-
|
|
115
|
+
## Multi-Agent Orchestration
|
|
150
116
|
|
|
151
|
-
|
|
117
|
+
`@hazeljs/agent` ships three complementary patterns for coordinating multiple agents. Use them individually or combine them.
|
|
118
|
+
|
|
119
|
+
### Pattern 1 — `@Delegate`: peer-to-peer agent calls
|
|
120
|
+
|
|
121
|
+
`@Delegate` marks a method on an agent as a delegation point to another agent. The method body is replaced at runtime with an actual `runtime.execute(targetAgent, input)` call — making agent-to-agent communication completely transparent to the LLM (it sees delegation targets as ordinary tools).
|
|
152
122
|
|
|
153
123
|
```
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
↓
|
|
158
|
-
waiting_for_approval
|
|
159
|
-
↓
|
|
160
|
-
failed
|
|
124
|
+
OrchestratorAgent
|
|
125
|
+
└── @Delegate → ResearchAgent
|
|
126
|
+
└── @Delegate → WriterAgent
|
|
161
127
|
```
|
|
162
128
|
|
|
163
|
-
|
|
129
|
+
```typescript
|
|
130
|
+
import { Agent, Delegate } from '@hazeljs/agent';
|
|
164
131
|
|
|
165
|
-
|
|
132
|
+
@Agent({
|
|
133
|
+
name: 'OrchestratorAgent',
|
|
134
|
+
description: 'Plans and delegates research and writing tasks',
|
|
135
|
+
systemPrompt: 'You orchestrate research and writing. Use the available tools to complete tasks.',
|
|
136
|
+
})
|
|
137
|
+
export class OrchestratorAgent {
|
|
138
|
+
// The LLM sees this as a tool. At runtime it calls ResearchAgent.
|
|
139
|
+
@Delegate({
|
|
140
|
+
agent: 'ResearchAgent',
|
|
141
|
+
description: 'Research a topic thoroughly and return key findings',
|
|
142
|
+
inputField: 'query',
|
|
143
|
+
})
|
|
144
|
+
async researchTopic(query: string): Promise<string> {
|
|
145
|
+
return ''; // body replaced at runtime by AgentRuntime
|
|
146
|
+
}
|
|
166
147
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
148
|
+
// The LLM sees this as a tool. At runtime it calls WriterAgent.
|
|
149
|
+
@Delegate({
|
|
150
|
+
agent: 'WriterAgent',
|
|
151
|
+
description: 'Write a polished article from the provided research notes',
|
|
152
|
+
inputField: 'content',
|
|
153
|
+
})
|
|
154
|
+
async writeArticle(content: string): Promise<string> {
|
|
155
|
+
return ''; // body replaced at runtime by AgentRuntime
|
|
156
|
+
}
|
|
157
|
+
}
|
|
174
158
|
|
|
175
|
-
|
|
159
|
+
@Agent({ name: 'ResearchAgent', systemPrompt: 'You are an expert researcher.' })
|
|
160
|
+
export class ResearchAgent {
|
|
161
|
+
@Tool({ description: 'Search the web', parameters: [{ name: 'query', type: 'string', required: true }] })
|
|
162
|
+
async searchWeb(input: { query: string }) {
|
|
163
|
+
return `Research findings for: ${input.query}`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
176
166
|
|
|
177
|
-
|
|
167
|
+
@Agent({ name: 'WriterAgent', systemPrompt: 'You are a professional technical writer.' })
|
|
168
|
+
export class WriterAgent {
|
|
169
|
+
@Tool({ description: 'Format content as Markdown', parameters: [{ name: 'raw', type: 'string', required: true }] })
|
|
170
|
+
async formatMarkdown(input: { raw: string }) {
|
|
171
|
+
return `## Article\n\n${input.raw}`;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Registration:**
|
|
178
177
|
|
|
179
178
|
```typescript
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
})
|
|
191
|
-
async sendEmail(input: { to: string; subject: string; body: string }) {
|
|
192
|
-
// Implementation
|
|
193
|
-
}
|
|
179
|
+
const orchestrator = new OrchestratorAgent();
|
|
180
|
+
const researcher = new ResearchAgent();
|
|
181
|
+
const writer = new WriterAgent();
|
|
182
|
+
|
|
183
|
+
[ResearchAgent, WriterAgent, OrchestratorAgent].forEach(A => runtime.registerAgent(A));
|
|
184
|
+
[['OrchestratorAgent', orchestrator], ['ResearchAgent', researcher], ['WriterAgent', writer]]
|
|
185
|
+
.forEach(([name, inst]) => runtime.registerAgentInstance(name as string, inst));
|
|
186
|
+
|
|
187
|
+
const result = await runtime.execute('OrchestratorAgent', 'Write a blog post about LLMs');
|
|
188
|
+
console.log(result.response);
|
|
194
189
|
```
|
|
195
190
|
|
|
196
|
-
**Tool
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
- Approval workflows
|
|
200
|
-
- Execution logging
|
|
201
|
-
- Error handling
|
|
191
|
+
> **Note:** `@Delegate` implicitly registers the method as `@Tool`. Do not add `@Tool` separately.
|
|
192
|
+
|
|
193
|
+
---
|
|
202
194
|
|
|
203
|
-
###
|
|
195
|
+
### Pattern 2 — `AgentGraph`: DAG pipelines
|
|
204
196
|
|
|
205
|
-
|
|
197
|
+
`AgentGraph` lets you wire agents and functions into a directed acyclic graph with sequential edges, conditional routing, and parallel fan-out/fan-in. Think LangGraph but TypeScript-native and integrated with `AgentRuntime`.
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
Entry → NodeA → NodeB → END (sequential)
|
|
201
|
+
Entry → RouterNode → NodeA | NodeB → END (conditional)
|
|
202
|
+
Entry → Splitter → [NodeA ‖ NodeB] → Combiner → END (parallel)
|
|
203
|
+
```
|
|
206
204
|
|
|
207
205
|
```typescript
|
|
208
|
-
|
|
209
|
-
const result = await runtime.execute('agent-name', 'Hello', {
|
|
210
|
-
sessionId: 'session-123',
|
|
211
|
-
enableMemory: true,
|
|
212
|
-
});
|
|
206
|
+
import { END } from '@hazeljs/agent';
|
|
213
207
|
|
|
214
|
-
//
|
|
215
|
-
const
|
|
216
|
-
sessionId: 'session-123', // Same session
|
|
217
|
-
enableMemory: true,
|
|
218
|
-
});
|
|
208
|
+
// Create graph via the runtime
|
|
209
|
+
const graph = runtime.createGraph('research-pipeline');
|
|
219
210
|
```
|
|
220
211
|
|
|
221
|
-
|
|
212
|
+
#### Sequential pipeline
|
|
222
213
|
|
|
223
|
-
|
|
214
|
+
```typescript
|
|
215
|
+
const pipeline = runtime
|
|
216
|
+
.createGraph('blog-pipeline')
|
|
217
|
+
.addNode('researcher', { type: 'agent', agentName: 'ResearchAgent' })
|
|
218
|
+
.addNode('writer', { type: 'agent', agentName: 'WriterAgent' })
|
|
219
|
+
.addEdge('researcher', 'writer')
|
|
220
|
+
.addEdge('writer', END)
|
|
221
|
+
.setEntryPoint('researcher')
|
|
222
|
+
.compile();
|
|
223
|
+
|
|
224
|
+
const result = await pipeline.execute('Write a blog post about TypeScript generics');
|
|
225
|
+
console.log(result.output);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### Conditional routing
|
|
224
229
|
|
|
225
230
|
```typescript
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
const router = runtime
|
|
232
|
+
.createGraph('router')
|
|
233
|
+
.addNode('classifier', { type: 'agent', agentName: 'ClassifierAgent' })
|
|
234
|
+
.addNode('coder', { type: 'agent', agentName: 'CoderAgent' })
|
|
235
|
+
.addNode('writer', { type: 'agent', agentName: 'WriterAgent' })
|
|
236
|
+
.setEntryPoint('classifier')
|
|
237
|
+
.addConditionalEdge('classifier', (state) =>
|
|
238
|
+
state.data?.type === 'code' ? 'coder' : 'writer',
|
|
239
|
+
)
|
|
240
|
+
.addEdge('coder', END)
|
|
241
|
+
.addEdge('writer', END)
|
|
242
|
+
.compile();
|
|
243
|
+
|
|
244
|
+
const result = await router.execute('Write a sorting algorithm in TypeScript');
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
#### Parallel fan-out / fan-in
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
async function splitTask(state: GraphState) {
|
|
251
|
+
return { ...state, data: { ...state.data, split: true } };
|
|
233
252
|
}
|
|
253
|
+
|
|
254
|
+
async function mergeResults(state: GraphState) {
|
|
255
|
+
const results = state.data?.branchResults as ParallelBranchResult[];
|
|
256
|
+
return { ...state, output: results.map(r => r.output).join('\n---\n') };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const parallel = runtime
|
|
260
|
+
.createGraph('parallel-research')
|
|
261
|
+
.addNode('splitter', { type: 'function', fn: splitTask })
|
|
262
|
+
.addNode('parallel-1', { type: 'parallel', branches: ['tech-researcher', 'market-researcher'] })
|
|
263
|
+
.addNode('tech-researcher', { type: 'agent', agentName: 'TechResearchAgent' })
|
|
264
|
+
.addNode('market-researcher', { type: 'agent', agentName: 'MarketResearchAgent' })
|
|
265
|
+
.addNode('combiner', { type: 'function', fn: mergeResults })
|
|
266
|
+
.addEdge('splitter', 'parallel-1')
|
|
267
|
+
.addEdge('parallel-1', 'combiner')
|
|
268
|
+
.addEdge('combiner', END)
|
|
269
|
+
.setEntryPoint('splitter')
|
|
270
|
+
.compile();
|
|
271
|
+
|
|
272
|
+
const result = await parallel.execute('Analyse the AI framework market');
|
|
234
273
|
```
|
|
235
274
|
|
|
236
|
-
|
|
275
|
+
#### Streaming execution
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
for await (const chunk of pipeline.stream('Tell me about GraphRAG')) {
|
|
279
|
+
if (chunk.type === 'node_complete') {
|
|
280
|
+
console.log(`✓ ${chunk.nodeId}: ${chunk.output?.slice(0, 80)}...`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
237
284
|
|
|
238
|
-
|
|
285
|
+
#### `AgentGraph` API
|
|
239
286
|
|
|
240
287
|
```typescript
|
|
241
|
-
|
|
288
|
+
interface AgentGraph {
|
|
289
|
+
addNode(id: string, config: GraphNodeConfig): this;
|
|
290
|
+
addEdge(from: string, to: string): this;
|
|
291
|
+
addConditionalEdge(from: string, router: RouterFunction): this;
|
|
292
|
+
setEntryPoint(nodeId: string): this;
|
|
293
|
+
compile(): CompiledGraph;
|
|
294
|
+
}
|
|
242
295
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
296
|
+
interface CompiledGraph {
|
|
297
|
+
execute(input: string, options?: GraphExecutionOptions): Promise<GraphExecutionResult>;
|
|
298
|
+
stream(input: string, options?: GraphExecutionOptions): AsyncIterable<GraphStreamChunk>;
|
|
299
|
+
visualize(): string; // returns a Mermaid diagram string
|
|
300
|
+
}
|
|
301
|
+
```
|
|
247
302
|
|
|
248
|
-
|
|
249
|
-
console.log('Agent completed:', event.data);
|
|
250
|
-
});
|
|
303
|
+
---
|
|
251
304
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
});
|
|
305
|
+
### Pattern 3 — `SupervisorAgent`: LLM-driven routing
|
|
306
|
+
|
|
307
|
+
`SupervisorAgent` uses an LLM to decompose tasks into subtasks, route each subtask to the best worker agent, and accumulate results — continuing until the task is complete or `maxRounds` is reached.
|
|
256
308
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
309
|
+
```
|
|
310
|
+
User Task
|
|
311
|
+
│
|
|
312
|
+
Supervisor ←──────────────────────────┐
|
|
313
|
+
│ │
|
|
314
|
+
┌───▼────────────────┐ Worker result
|
|
315
|
+
│ Route to worker? │ │
|
|
316
|
+
└───────────┬────────┘ │
|
|
317
|
+
│ │
|
|
318
|
+
┌──────▼──────┐ │
|
|
319
|
+
│ WorkerAgent │───────────────────┘
|
|
320
|
+
└─────────────┘
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
const supervisor = runtime.createSupervisor({
|
|
325
|
+
name: 'project-manager',
|
|
326
|
+
workers: ['ResearchAgent', 'CoderAgent', 'WriterAgent'],
|
|
327
|
+
maxRounds: 6,
|
|
328
|
+
llm: async (prompt) => {
|
|
329
|
+
const res = await openai.chat.completions.create({
|
|
330
|
+
model: 'gpt-4o-mini',
|
|
331
|
+
messages: [{ role: 'user', content: prompt }],
|
|
332
|
+
});
|
|
333
|
+
return res.choices[0].message.content ?? '';
|
|
334
|
+
},
|
|
260
335
|
});
|
|
261
336
|
|
|
262
|
-
|
|
263
|
-
|
|
337
|
+
const result = await supervisor.run(
|
|
338
|
+
'Build and document a REST API for a todo app',
|
|
339
|
+
{ sessionId: 'proj-001' },
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
console.log(result.response);
|
|
343
|
+
result.rounds.forEach((round, i) => {
|
|
344
|
+
console.log(`Round ${i + 1}: routed to ${round.worker} — ${round.workerResult.response.slice(0, 80)}`);
|
|
264
345
|
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**`SupervisorConfig`:**
|
|
265
349
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
350
|
+
| Field | Type | Default | Description |
|
|
351
|
+
|-------|------|---------|-------------|
|
|
352
|
+
| `name` | string | — | Supervisor instance name |
|
|
353
|
+
| `workers` | string[] | — | Registered agent names available to the supervisor |
|
|
354
|
+
| `maxRounds` | number | 5 | Maximum routing iterations |
|
|
355
|
+
| `llm` | `(prompt: string) => Promise<string>` | — | LLM function for routing decisions |
|
|
356
|
+
| `sessionId` | string | auto | Session for memory continuity across rounds |
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## Architecture
|
|
361
|
+
|
|
362
|
+
```
|
|
363
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
364
|
+
│ AgentRuntime │
|
|
365
|
+
├──────────────┬───────────────┬───────────────┬───────────────────┤
|
|
366
|
+
│ Registry │ State Mgr │ Executor │ Tool Executor │
|
|
367
|
+
│ (agents, │ (in-mem / │ (step loop, │ (timeout, │
|
|
368
|
+
│ tools) │ Redis / DB) │ approval) │ retry, audit) │
|
|
369
|
+
├──────────────┴───────────────┴───────────────┴───────────────────┤
|
|
370
|
+
│ Multi-Agent Layer │
|
|
371
|
+
│ ┌──────────────┐ ┌───────────────────┐ ┌────────────────────┐ │
|
|
372
|
+
│ │ AgentGraph │ │ SupervisorAgent │ │ @Delegate │ │
|
|
373
|
+
│ │ (DAG pipes) │ │ (LLM routing) │ │ (peer-to-peer) │ │
|
|
374
|
+
│ └──────────────┘ └───────────────────┘ └────────────────────┘ │
|
|
375
|
+
├────────────────────────────────────────────────────────────────── ┤
|
|
376
|
+
│ Memory Module (@hazeljs/rag) │ RAG Module │
|
|
377
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## State Machine
|
|
383
|
+
|
|
384
|
+
Every agent execution follows a deterministic state machine:
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
idle → thinking → using_tool → thinking → ... → completed
|
|
388
|
+
↓
|
|
389
|
+
waiting_for_input
|
|
390
|
+
↓
|
|
391
|
+
waiting_for_approval
|
|
392
|
+
↓
|
|
393
|
+
failed
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## Event System
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { AgentEventType } from '@hazeljs/agent';
|
|
402
|
+
|
|
403
|
+
runtime.on(AgentEventType.EXECUTION_STARTED, e => console.log('started:', e.data));
|
|
404
|
+
runtime.on(AgentEventType.EXECUTION_COMPLETED, e => console.log('completed:', e.data));
|
|
405
|
+
runtime.on(AgentEventType.STEP_STARTED, e => console.log('step:', e.data));
|
|
406
|
+
runtime.on(AgentEventType.TOOL_EXECUTION_STARTED, e => console.log('tool:', e.data));
|
|
407
|
+
runtime.on(AgentEventType.TOOL_APPROVAL_REQUESTED, e => {
|
|
408
|
+
console.log('approval needed:', e.data);
|
|
409
|
+
runtime.approveToolExecution(e.data.requestId, 'admin');
|
|
269
410
|
});
|
|
411
|
+
|
|
412
|
+
// Catch-all
|
|
413
|
+
runtime.onAny(e => console.log(e.type, e.data));
|
|
270
414
|
```
|
|
271
415
|
|
|
272
|
-
|
|
416
|
+
---
|
|
273
417
|
|
|
274
|
-
|
|
418
|
+
## HazelJS Module Integration
|
|
275
419
|
|
|
276
420
|
```typescript
|
|
277
421
|
import { HazelModule } from '@hazeljs/core';
|
|
@@ -282,146 +426,116 @@ import { RagModule } from '@hazeljs/rag';
|
|
|
282
426
|
imports: [
|
|
283
427
|
RagModule.forRoot({ /* ... */ }),
|
|
284
428
|
AgentModule.forRoot({
|
|
285
|
-
runtime: {
|
|
286
|
-
|
|
287
|
-
enableObservability: true,
|
|
288
|
-
},
|
|
289
|
-
agents: [SupportAgent, SalesAgent],
|
|
429
|
+
runtime: { defaultMaxSteps: 10, enableObservability: true },
|
|
430
|
+
agents: [SupportAgent, ResearchAgent, WriterAgent, OrchestratorAgent],
|
|
290
431
|
}),
|
|
291
432
|
],
|
|
292
433
|
})
|
|
293
434
|
export class AppModule {}
|
|
294
435
|
```
|
|
295
436
|
|
|
296
|
-
|
|
437
|
+
---
|
|
297
438
|
|
|
298
|
-
|
|
439
|
+
## Best Practices
|
|
299
440
|
|
|
300
|
-
|
|
301
|
-
// Execute agent
|
|
302
|
-
const result = await runtime.execute('agent', 'Start task');
|
|
441
|
+
### Keep tools idempotent
|
|
303
442
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
443
|
+
```typescript
|
|
444
|
+
@Tool({ description: 'Create an order' })
|
|
445
|
+
async createOrder(input: { orderId: string; items: Item[] }) {
|
|
446
|
+
const existing = await this.findOrder(input.orderId);
|
|
447
|
+
if (existing) return existing; // safe to retry
|
|
448
|
+
return this.createNewOrder(input);
|
|
307
449
|
}
|
|
308
450
|
```
|
|
309
451
|
|
|
310
|
-
###
|
|
452
|
+
### Use `@Delegate` for domain specialisation
|
|
311
453
|
|
|
312
|
-
|
|
313
|
-
const result = await runtime.execute('agent', 'Process order', {
|
|
314
|
-
initialContext: {
|
|
315
|
-
userId: '123',
|
|
316
|
-
orderData: { /* ... */ },
|
|
317
|
-
},
|
|
318
|
-
});
|
|
319
|
-
```
|
|
454
|
+
Keep each agent focused on one domain. `@Delegate` lets the orchestrator combine specialists without any agent becoming a monolith.
|
|
320
455
|
|
|
321
|
-
###
|
|
456
|
+
### Choose the right multi-agent pattern
|
|
322
457
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
})
|
|
329
|
-
async deleteUserData(input: { userId: string }) {
|
|
330
|
-
// Implementation
|
|
331
|
-
}
|
|
332
|
-
```
|
|
458
|
+
| Pattern | Use when |
|
|
459
|
+
|---------|----------|
|
|
460
|
+
| `@Delegate` | Two or three agents with a clear orchestrator / worker split |
|
|
461
|
+
| `AgentGraph` | Workflow is known at design time; conditional routing matters |
|
|
462
|
+
| `SupervisorAgent` | Task decomposition is dynamic; you want LLM-driven routing |
|
|
333
463
|
|
|
334
|
-
|
|
464
|
+
### Require approval for destructive actions
|
|
335
465
|
|
|
466
|
+
```typescript
|
|
467
|
+
@Tool({ requiresApproval: true, description: 'Delete user account' })
|
|
468
|
+
async deleteAccount(input: { userId: string }) { /* ... */ }
|
|
336
469
|
```
|
|
337
|
-
┌─────────────────────────────────────────────────┐
|
|
338
|
-
│ Agent Runtime │
|
|
339
|
-
├─────────────────────────────────────────────────┤
|
|
340
|
-
│ ┌──────────────┐ ┌──────────────┐ │
|
|
341
|
-
│ │ Registry │ │ State Mgr │ │
|
|
342
|
-
│ └──────────────┘ └──────────────┘ │
|
|
343
|
-
│ ┌──────────────┐ ┌──────────────┐ │
|
|
344
|
-
│ │ Executor │ │ Tool Executor│ │
|
|
345
|
-
│ └──────────────┘ └──────────────┘ │
|
|
346
|
-
│ ┌──────────────┐ ┌──────────────┐ │
|
|
347
|
-
│ │ Events │ │ Context │ │
|
|
348
|
-
│ └──────────────┘ └──────────────┘ │
|
|
349
|
-
├─────────────────────────────────────────────────┤
|
|
350
|
-
│ Memory Module │ RAG Module │
|
|
351
|
-
└─────────────────────────────────────────────────┘
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## Best Practices
|
|
355
470
|
|
|
356
|
-
###
|
|
471
|
+
### Handle errors in tools
|
|
357
472
|
|
|
358
473
|
```typescript
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return
|
|
474
|
+
@Tool({ description: 'Call external API' })
|
|
475
|
+
async callExternalAPI(input: { endpoint: string }) {
|
|
476
|
+
try {
|
|
477
|
+
return await this.api.call(input.endpoint);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
return { success: false, error: (error as Error).message };
|
|
365
480
|
}
|
|
366
481
|
}
|
|
367
|
-
|
|
368
|
-
// ❌ Bad - Business logic in decorator
|
|
369
|
-
@Agent({
|
|
370
|
-
name: 'support-agent',
|
|
371
|
-
onExecute: async () => { /* complex logic */ }
|
|
372
|
-
})
|
|
373
482
|
```
|
|
374
483
|
|
|
375
|
-
|
|
484
|
+
---
|
|
376
485
|
|
|
377
|
-
|
|
378
|
-
@Tool({ requiresApproval: true })
|
|
379
|
-
async deleteAccount(input: { userId: string }) {
|
|
380
|
-
// Destructive action
|
|
381
|
-
}
|
|
382
|
-
```
|
|
486
|
+
## API Reference
|
|
383
487
|
|
|
384
|
-
###
|
|
488
|
+
### `AgentRuntime`
|
|
385
489
|
|
|
386
490
|
```typescript
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
491
|
+
class AgentRuntime {
|
|
492
|
+
execute(agentName: string, input: string, options?: ExecuteOptions): Promise<AgentExecutionResult>;
|
|
493
|
+
resume(executionId: string, input?: string): Promise<AgentExecutionResult>;
|
|
494
|
+
registerAgent(agentClass: new (...args: unknown[]) => unknown): void;
|
|
495
|
+
registerAgentInstance(name: string, instance: unknown): void;
|
|
496
|
+
createGraph(name: string): AgentGraph;
|
|
497
|
+
createSupervisor(config: SupervisorConfig): SupervisorAgent;
|
|
498
|
+
approveToolExecution(requestId: string, approvedBy: string): void;
|
|
499
|
+
rejectToolExecution(requestId: string, reason?: string): void;
|
|
500
|
+
on(event: string, handler: (e: AgentEvent) => void): void;
|
|
501
|
+
onAny(handler: (e: AgentEvent) => void): void;
|
|
394
502
|
}
|
|
395
503
|
```
|
|
396
504
|
|
|
397
|
-
###
|
|
505
|
+
### Decorators
|
|
506
|
+
|
|
507
|
+
| Decorator | Target | Description |
|
|
508
|
+
|-----------|--------|-------------|
|
|
509
|
+
| `@Agent(config)` | Class | Declares a class as an agent |
|
|
510
|
+
| `@Tool(config)` | Method | Exposes a method as an LLM-callable tool |
|
|
511
|
+
| `@Delegate(config)` | Method | Delegates a method to another agent (registers as `@Tool` automatically) |
|
|
512
|
+
|
|
513
|
+
### `GraphNodeConfig` types
|
|
398
514
|
|
|
399
515
|
```typescript
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
try {
|
|
403
|
-
return await this.api.call(input);
|
|
404
|
-
} catch (error) {
|
|
405
|
-
// Return structured error
|
|
406
|
-
return {
|
|
407
|
-
success: false,
|
|
408
|
-
error: error.message,
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
```
|
|
516
|
+
// Agent node — runs a registered agent
|
|
517
|
+
{ type: 'agent', agentName: string }
|
|
413
518
|
|
|
414
|
-
|
|
519
|
+
// Function node — runs a custom function
|
|
520
|
+
{ type: 'function', fn: (state: GraphState) => Promise<GraphState> }
|
|
415
521
|
|
|
416
|
-
|
|
522
|
+
// Parallel node — fans out to multiple branches simultaneously
|
|
523
|
+
{ type: 'parallel', branches: string[] }
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
417
527
|
|
|
418
528
|
## Examples
|
|
419
529
|
|
|
420
|
-
- [
|
|
421
|
-
- [
|
|
422
|
-
|
|
423
|
-
|
|
530
|
+
- [hazeljs-ai-multiagent-starter](../../hazeljs-ai-multiagent-starter) — Full multi-agent REST API with `AgentGraph`, `SupervisorAgent`, and `@Delegate`
|
|
531
|
+
- [hazeljs-rag-documents-starter](../../hazeljs-rag-documents-starter) — RAG + GraphRAG knowledge base API
|
|
532
|
+
|
|
533
|
+
---
|
|
424
534
|
|
|
425
535
|
## License
|
|
426
536
|
|
|
427
|
-
|
|
537
|
+
Apache 2.0
|
|
538
|
+
|
|
539
|
+
## Contributing
|
|
540
|
+
|
|
541
|
+
Contributions are welcome! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
|