@hazeljs/agent 0.2.0-beta.61 → 0.2.0-beta.64
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/README.md +364 -256
- package/coverage/clover.xml +192 -150
- package/coverage/lcov-report/index.html +35 -20
- package/coverage/lcov.info +293 -224
- 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 +14 -5
- package/dist/executor/agent.executor.js.map +1 -1
- package/dist/graph/agent-graph.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/supervisor/supervisor.d.ts +2 -0
- package/dist/supervisor/supervisor.d.ts.map +1 -1
- package/dist/supervisor/supervisor.js +10 -17
- package/dist/supervisor/supervisor.js.map +1 -1
- package/logs/combined.log +1 -1
- package/package.json +6 -5
- package/tsconfig.tsbuildinfo +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**Build AI agents that actually do things.**
|
|
4
4
|
|
|
5
|
-
Stateful, tool-using, memory-enabled. Define tools with `@Tool`,
|
|
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
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@hazeljs/agent)
|
|
8
8
|
[](https://www.npmjs.com/package/@hazeljs/agent)
|
|
@@ -12,12 +12,15 @@ Stateful, tool-using, memory-enabled. Define tools with `@Tool`, add approval wo
|
|
|
12
12
|
|
|
13
13
|
Unlike stateless request handlers, agents are:
|
|
14
14
|
|
|
15
|
-
- **Stateful**
|
|
16
|
-
- **Long-running**
|
|
17
|
-
- **Tool-using**
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
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
|
|
22
|
+
|
|
23
|
+
---
|
|
21
24
|
|
|
22
25
|
## Installation
|
|
23
26
|
|
|
@@ -25,9 +28,11 @@ Unlike stateless request handlers, agents are:
|
|
|
25
28
|
npm install @hazeljs/agent @hazeljs/core @hazeljs/rag
|
|
26
29
|
```
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start — Single Agent
|
|
29
34
|
|
|
30
|
-
### 1. Define an
|
|
35
|
+
### 1. Define an agent
|
|
31
36
|
|
|
32
37
|
```typescript
|
|
33
38
|
import { Agent, Tool } from '@hazeljs/agent';
|
|
@@ -42,242 +47,375 @@ import { Agent, Tool } from '@hazeljs/agent';
|
|
|
42
47
|
export class SupportAgent {
|
|
43
48
|
@Tool({
|
|
44
49
|
description: 'Look up order information by order ID',
|
|
45
|
-
parameters: [
|
|
46
|
-
{
|
|
47
|
-
name: 'orderId',
|
|
48
|
-
type: 'string',
|
|
49
|
-
description: 'The order ID to lookup',
|
|
50
|
-
required: true,
|
|
51
|
-
},
|
|
52
|
-
],
|
|
50
|
+
parameters: [{ name: 'orderId', type: 'string', description: 'The order ID', required: true }],
|
|
53
51
|
})
|
|
54
52
|
async lookupOrder(input: { orderId: string }) {
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
orderId: input.orderId,
|
|
58
|
-
status: 'shipped',
|
|
59
|
-
trackingNumber: 'TRACK123',
|
|
60
|
-
};
|
|
53
|
+
return { orderId: input.orderId, status: 'shipped', trackingNumber: 'TRACK123' };
|
|
61
54
|
}
|
|
62
55
|
|
|
63
56
|
@Tool({
|
|
64
57
|
description: 'Process a refund for an order',
|
|
65
|
-
requiresApproval: true,
|
|
58
|
+
requiresApproval: true, // requires human approval before execution
|
|
66
59
|
parameters: [
|
|
67
|
-
{
|
|
68
|
-
|
|
69
|
-
type: 'string',
|
|
70
|
-
description: 'The order ID to refund',
|
|
71
|
-
required: true,
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
name: 'amount',
|
|
75
|
-
type: 'number',
|
|
76
|
-
description: 'Refund amount',
|
|
77
|
-
required: true,
|
|
78
|
-
},
|
|
60
|
+
{ name: 'orderId', type: 'string', required: true },
|
|
61
|
+
{ name: 'amount', type: 'number', required: true },
|
|
79
62
|
],
|
|
80
63
|
})
|
|
81
64
|
async processRefund(input: { orderId: string; amount: number }) {
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
success: true,
|
|
85
|
-
refundId: 'REF123',
|
|
86
|
-
amount: input.amount,
|
|
87
|
-
};
|
|
65
|
+
return { success: true, refundId: 'REF123', amount: input.amount };
|
|
88
66
|
}
|
|
89
67
|
}
|
|
90
68
|
```
|
|
91
69
|
|
|
92
|
-
### 2. Set
|
|
70
|
+
### 2. Set up the runtime
|
|
93
71
|
|
|
94
72
|
```typescript
|
|
95
73
|
import { AgentRuntime } from '@hazeljs/agent';
|
|
96
74
|
import { MemoryManager } from '@hazeljs/rag';
|
|
97
75
|
import { AIService } from '@hazeljs/ai';
|
|
98
76
|
|
|
99
|
-
// Initialize dependencies
|
|
100
|
-
const memoryManager = new MemoryManager(/* ... */);
|
|
101
|
-
const aiService = new AIService({ provider: 'openai' });
|
|
102
|
-
|
|
103
|
-
// Create runtime
|
|
104
77
|
const runtime = new AgentRuntime({
|
|
105
|
-
memoryManager,
|
|
106
|
-
llmProvider:
|
|
78
|
+
memoryManager: new MemoryManager(/* ... */),
|
|
79
|
+
llmProvider: new AIService({ provider: 'openai' }),
|
|
107
80
|
defaultMaxSteps: 10,
|
|
108
81
|
enableObservability: true,
|
|
109
82
|
});
|
|
110
83
|
|
|
111
|
-
|
|
112
|
-
const supportAgent = new SupportAgent();
|
|
84
|
+
const agent = new SupportAgent();
|
|
113
85
|
runtime.registerAgent(SupportAgent);
|
|
114
|
-
runtime.registerAgentInstance('support-agent',
|
|
86
|
+
runtime.registerAgentInstance('support-agent', agent);
|
|
115
87
|
```
|
|
116
88
|
|
|
117
|
-
### 3. Execute
|
|
89
|
+
### 3. Execute
|
|
118
90
|
|
|
119
91
|
```typescript
|
|
120
|
-
// Execute agent
|
|
121
92
|
const result = await runtime.execute(
|
|
122
93
|
'support-agent',
|
|
123
|
-
'I need to check my order
|
|
124
|
-
{
|
|
125
|
-
sessionId: 'user-session-123',
|
|
126
|
-
userId: 'user-456',
|
|
127
|
-
enableMemory: true,
|
|
128
|
-
enableRAG: true,
|
|
129
|
-
}
|
|
94
|
+
'I need to check my order #12345',
|
|
95
|
+
{ sessionId: 'user-session-123', userId: 'user-456', enableMemory: true },
|
|
130
96
|
);
|
|
131
97
|
|
|
132
98
|
console.log(result.response);
|
|
133
99
|
console.log(`Completed in ${result.steps.length} steps`);
|
|
134
100
|
```
|
|
135
101
|
|
|
136
|
-
### 4. Handle
|
|
102
|
+
### 4. Handle human-in-the-loop
|
|
137
103
|
|
|
138
104
|
```typescript
|
|
139
|
-
// Subscribe to approval requests
|
|
140
105
|
runtime.on('tool.approval.requested', async (event) => {
|
|
141
106
|
console.log('Approval needed:', event.data);
|
|
142
|
-
|
|
143
|
-
// Approve or reject
|
|
144
107
|
runtime.approveToolExecution(event.data.requestId, 'admin-user');
|
|
145
|
-
// or
|
|
146
|
-
// runtime.rejectToolExecution(event.data.requestId);
|
|
147
108
|
});
|
|
148
109
|
|
|
149
|
-
|
|
150
|
-
const resumedResult = await runtime.resume(result.executionId);
|
|
110
|
+
const resumed = await runtime.resume(result.executionId);
|
|
151
111
|
```
|
|
152
112
|
|
|
153
|
-
|
|
113
|
+
---
|
|
154
114
|
|
|
155
|
-
|
|
115
|
+
## Multi-Agent Orchestration
|
|
156
116
|
|
|
157
|
-
|
|
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).
|
|
158
122
|
|
|
159
123
|
```
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
↓
|
|
164
|
-
waiting_for_approval
|
|
165
|
-
↓
|
|
166
|
-
failed
|
|
124
|
+
OrchestratorAgent
|
|
125
|
+
└── @Delegate → ResearchAgent
|
|
126
|
+
└── @Delegate → WriterAgent
|
|
167
127
|
```
|
|
168
128
|
|
|
169
|
-
|
|
129
|
+
```typescript
|
|
130
|
+
import { Agent, Delegate } from '@hazeljs/agent';
|
|
170
131
|
|
|
171
|
-
|
|
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
|
+
}
|
|
147
|
+
|
|
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
|
+
}
|
|
172
158
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
+
}
|
|
180
166
|
|
|
181
|
-
|
|
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
|
+
```
|
|
182
175
|
|
|
183
|
-
|
|
176
|
+
**Registration:**
|
|
184
177
|
|
|
185
178
|
```typescript
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
})
|
|
197
|
-
async sendEmail(input: { to: string; subject: string; body: string }) {
|
|
198
|
-
// Implementation
|
|
199
|
-
}
|
|
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);
|
|
200
189
|
```
|
|
201
190
|
|
|
202
|
-
**Tool
|
|
203
|
-
- Automatic parameter validation
|
|
204
|
-
- Timeout and retry logic
|
|
205
|
-
- Approval workflows
|
|
206
|
-
- Execution logging
|
|
207
|
-
- Error handling
|
|
191
|
+
> **Note:** `@Delegate` implicitly registers the method as `@Tool`. Do not add `@Tool` separately.
|
|
208
192
|
|
|
209
|
-
|
|
193
|
+
---
|
|
210
194
|
|
|
211
|
-
|
|
195
|
+
### Pattern 2 — `AgentGraph`: DAG pipelines
|
|
196
|
+
|
|
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
|
+
```
|
|
212
204
|
|
|
213
205
|
```typescript
|
|
214
|
-
|
|
215
|
-
const result = await runtime.execute('agent-name', 'Hello', {
|
|
216
|
-
sessionId: 'session-123',
|
|
217
|
-
enableMemory: true,
|
|
218
|
-
});
|
|
206
|
+
import { END } from '@hazeljs/agent';
|
|
219
207
|
|
|
220
|
-
//
|
|
221
|
-
const
|
|
222
|
-
sessionId: 'session-123', // Same session
|
|
223
|
-
enableMemory: true,
|
|
224
|
-
});
|
|
208
|
+
// Create graph via the runtime
|
|
209
|
+
const graph = runtime.createGraph('research-pipeline');
|
|
225
210
|
```
|
|
226
211
|
|
|
227
|
-
|
|
212
|
+
#### Sequential pipeline
|
|
213
|
+
|
|
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
|
+
```
|
|
228
227
|
|
|
229
|
-
|
|
228
|
+
#### Conditional routing
|
|
230
229
|
|
|
231
230
|
```typescript
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
|
|
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 } };
|
|
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') };
|
|
239
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');
|
|
240
273
|
```
|
|
241
274
|
|
|
242
|
-
|
|
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
|
+
```
|
|
243
284
|
|
|
244
|
-
|
|
285
|
+
#### `AgentGraph` API
|
|
245
286
|
|
|
246
287
|
```typescript
|
|
247
|
-
|
|
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
|
+
}
|
|
248
295
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
```
|
|
253
302
|
|
|
254
|
-
|
|
255
|
-
console.log('Agent completed:', event.data);
|
|
256
|
-
});
|
|
303
|
+
---
|
|
257
304
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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.
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
User Task
|
|
311
|
+
│
|
|
312
|
+
Supervisor ←──────────────────────────┐
|
|
313
|
+
│ │
|
|
314
|
+
┌───▼────────────────┐ Worker result
|
|
315
|
+
│ Route to worker? │ │
|
|
316
|
+
└───────────┬────────┘ │
|
|
317
|
+
│ │
|
|
318
|
+
┌──────▼──────┐ │
|
|
319
|
+
│ WorkerAgent │───────────────────┘
|
|
320
|
+
└─────────────┘
|
|
321
|
+
```
|
|
262
322
|
|
|
263
|
-
|
|
264
|
-
runtime.
|
|
265
|
-
|
|
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
|
+
},
|
|
266
335
|
});
|
|
267
336
|
|
|
268
|
-
|
|
269
|
-
|
|
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)}`);
|
|
270
345
|
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**`SupervisorConfig`:**
|
|
349
|
+
|
|
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
|
+
---
|
|
271
397
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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');
|
|
275
410
|
});
|
|
411
|
+
|
|
412
|
+
// Catch-all
|
|
413
|
+
runtime.onAny(e => console.log(e.type, e.data));
|
|
276
414
|
```
|
|
277
415
|
|
|
278
|
-
|
|
416
|
+
---
|
|
279
417
|
|
|
280
|
-
|
|
418
|
+
## HazelJS Module Integration
|
|
281
419
|
|
|
282
420
|
```typescript
|
|
283
421
|
import { HazelModule } from '@hazeljs/core';
|
|
@@ -288,146 +426,116 @@ import { RagModule } from '@hazeljs/rag';
|
|
|
288
426
|
imports: [
|
|
289
427
|
RagModule.forRoot({ /* ... */ }),
|
|
290
428
|
AgentModule.forRoot({
|
|
291
|
-
runtime: {
|
|
292
|
-
|
|
293
|
-
enableObservability: true,
|
|
294
|
-
},
|
|
295
|
-
agents: [SupportAgent, SalesAgent],
|
|
429
|
+
runtime: { defaultMaxSteps: 10, enableObservability: true },
|
|
430
|
+
agents: [SupportAgent, ResearchAgent, WriterAgent, OrchestratorAgent],
|
|
296
431
|
}),
|
|
297
432
|
],
|
|
298
433
|
})
|
|
299
434
|
export class AppModule {}
|
|
300
435
|
```
|
|
301
436
|
|
|
302
|
-
|
|
437
|
+
---
|
|
303
438
|
|
|
304
|
-
|
|
439
|
+
## Best Practices
|
|
305
440
|
|
|
306
|
-
|
|
307
|
-
// Execute agent
|
|
308
|
-
const result = await runtime.execute('agent', 'Start task');
|
|
441
|
+
### Keep tools idempotent
|
|
309
442
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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);
|
|
313
449
|
}
|
|
314
450
|
```
|
|
315
451
|
|
|
316
|
-
###
|
|
452
|
+
### Use `@Delegate` for domain specialisation
|
|
317
453
|
|
|
318
|
-
|
|
319
|
-
const result = await runtime.execute('agent', 'Process order', {
|
|
320
|
-
initialContext: {
|
|
321
|
-
userId: '123',
|
|
322
|
-
orderData: { /* ... */ },
|
|
323
|
-
},
|
|
324
|
-
});
|
|
325
|
-
```
|
|
454
|
+
Keep each agent focused on one domain. `@Delegate` lets the orchestrator combine specialists without any agent becoming a monolith.
|
|
326
455
|
|
|
327
|
-
###
|
|
456
|
+
### Choose the right multi-agent pattern
|
|
328
457
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
})
|
|
335
|
-
async deleteUserData(input: { userId: string }) {
|
|
336
|
-
// Implementation
|
|
337
|
-
}
|
|
338
|
-
```
|
|
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 |
|
|
339
463
|
|
|
340
|
-
|
|
464
|
+
### Require approval for destructive actions
|
|
341
465
|
|
|
466
|
+
```typescript
|
|
467
|
+
@Tool({ requiresApproval: true, description: 'Delete user account' })
|
|
468
|
+
async deleteAccount(input: { userId: string }) { /* ... */ }
|
|
342
469
|
```
|
|
343
|
-
┌─────────────────────────────────────────────────┐
|
|
344
|
-
│ Agent Runtime │
|
|
345
|
-
├─────────────────────────────────────────────────┤
|
|
346
|
-
│ ┌──────────────┐ ┌──────────────┐ │
|
|
347
|
-
│ │ Registry │ │ State Mgr │ │
|
|
348
|
-
│ └──────────────┘ └──────────────┘ │
|
|
349
|
-
│ ┌──────────────┐ ┌──────────────┐ │
|
|
350
|
-
│ │ Executor │ │ Tool Executor│ │
|
|
351
|
-
│ └──────────────┘ └──────────────┘ │
|
|
352
|
-
│ ┌──────────────┐ ┌──────────────┐ │
|
|
353
|
-
│ │ Events │ │ Context │ │
|
|
354
|
-
│ └──────────────┘ └──────────────┘ │
|
|
355
|
-
├─────────────────────────────────────────────────┤
|
|
356
|
-
│ Memory Module │ RAG Module │
|
|
357
|
-
└─────────────────────────────────────────────────┘
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
## Best Practices
|
|
361
470
|
|
|
362
|
-
###
|
|
471
|
+
### Handle errors in tools
|
|
363
472
|
|
|
364
473
|
```typescript
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
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 };
|
|
371
480
|
}
|
|
372
481
|
}
|
|
373
|
-
|
|
374
|
-
// ❌ Bad - Business logic in decorator
|
|
375
|
-
@Agent({
|
|
376
|
-
name: 'support-agent',
|
|
377
|
-
onExecute: async () => { /* complex logic */ }
|
|
378
|
-
})
|
|
379
482
|
```
|
|
380
483
|
|
|
381
|
-
|
|
484
|
+
---
|
|
382
485
|
|
|
383
|
-
|
|
384
|
-
@Tool({ requiresApproval: true })
|
|
385
|
-
async deleteAccount(input: { userId: string }) {
|
|
386
|
-
// Destructive action
|
|
387
|
-
}
|
|
388
|
-
```
|
|
486
|
+
## API Reference
|
|
389
487
|
|
|
390
|
-
###
|
|
488
|
+
### `AgentRuntime`
|
|
391
489
|
|
|
392
490
|
```typescript
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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;
|
|
400
502
|
}
|
|
401
503
|
```
|
|
402
504
|
|
|
403
|
-
###
|
|
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
|
|
404
514
|
|
|
405
515
|
```typescript
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
return await this.api.call(input);
|
|
410
|
-
} catch (error) {
|
|
411
|
-
// Return structured error
|
|
412
|
-
return {
|
|
413
|
-
success: false,
|
|
414
|
-
error: error.message,
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
```
|
|
516
|
+
// Agent node — runs a registered agent
|
|
517
|
+
{ type: 'agent', agentName: string }
|
|
419
518
|
|
|
420
|
-
|
|
519
|
+
// Function node — runs a custom function
|
|
520
|
+
{ type: 'function', fn: (state: GraphState) => Promise<GraphState> }
|
|
421
521
|
|
|
422
|
-
|
|
522
|
+
// Parallel node — fans out to multiple branches simultaneously
|
|
523
|
+
{ type: 'parallel', branches: string[] }
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
423
527
|
|
|
424
528
|
## Examples
|
|
425
529
|
|
|
426
|
-
- [
|
|
427
|
-
- [
|
|
428
|
-
|
|
429
|
-
|
|
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
|
+
---
|
|
430
534
|
|
|
431
535
|
## License
|
|
432
536
|
|
|
433
537
|
Apache 2.0
|
|
538
|
+
|
|
539
|
+
## Contributing
|
|
540
|
+
|
|
541
|
+
Contributions are welcome! See [CONTRIBUTING.md](../../CONTRIBUTING.md) for details.
|