@hazeljs/agent 0.2.0-beta.61 → 0.2.0-beta.65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +364 -256
  2. package/coverage/clover.xml +192 -150
  3. package/coverage/lcov-report/index.html +35 -20
  4. package/coverage/lcov.info +293 -224
  5. package/dist/executor/agent.executor.d.ts +1 -0
  6. package/dist/executor/agent.executor.d.ts.map +1 -1
  7. package/dist/executor/agent.executor.js +14 -5
  8. package/dist/executor/agent.executor.js.map +1 -1
  9. package/dist/graph/agent-graph.js.map +1 -1
  10. package/dist/prompts/agent-system.prompt.d.ts +10 -0
  11. package/dist/prompts/agent-system.prompt.d.ts.map +1 -0
  12. package/dist/prompts/agent-system.prompt.js +18 -0
  13. package/dist/prompts/agent-system.prompt.js.map +1 -0
  14. package/dist/prompts/index.d.ts +4 -0
  15. package/dist/prompts/index.d.ts.map +1 -0
  16. package/dist/prompts/index.js +20 -0
  17. package/dist/prompts/index.js.map +1 -0
  18. package/dist/prompts/supervisor-routing.prompt.d.ts +9 -0
  19. package/dist/prompts/supervisor-routing.prompt.d.ts.map +1 -0
  20. package/dist/prompts/supervisor-routing.prompt.js +22 -0
  21. package/dist/prompts/supervisor-routing.prompt.js.map +1 -0
  22. package/dist/prompts/supervisor-system.prompt.d.ts +9 -0
  23. package/dist/prompts/supervisor-system.prompt.d.ts.map +1 -0
  24. package/dist/prompts/supervisor-system.prompt.js +21 -0
  25. package/dist/prompts/supervisor-system.prompt.js.map +1 -0
  26. package/dist/supervisor/supervisor.d.ts +2 -0
  27. package/dist/supervisor/supervisor.d.ts.map +1 -1
  28. package/dist/supervisor/supervisor.js +10 -17
  29. package/dist/supervisor/supervisor.js.map +1 -1
  30. package/logs/combined.log +1 -1
  31. package/package.json +6 -5
  32. 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`, add approval workflows for sensitive actions, integrate RAG. Production-grade agents without the complexity.
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
  [![npm version](https://img.shields.io/npm/v/@hazeljs/agent.svg)](https://www.npmjs.com/package/@hazeljs/agent)
8
8
  [![npm downloads](https://img.shields.io/npm/dm/@hazeljs/agent)](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** - Maintain context across multiple steps
16
- - **Long-running** - Execute complex workflows over time
17
- - **Tool-using** - Call functions safely with approval workflows
18
- - **Memory-enabled** - Integrate with persistent memory systems
19
- - **Observable** - Full event system for monitoring and debugging
20
- - **Resumable** - Support pause/resume and human-in-the-loop
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
- ## Quick Start
31
+ ---
32
+
33
+ ## Quick Start — Single Agent
29
34
 
30
- ### 1. Define an Agent
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
- // Your implementation
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, // Requires human approval
58
+ requiresApproval: true, // requires human approval before execution
66
59
  parameters: [
67
- {
68
- name: 'orderId',
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
- // Your implementation
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 Up the Runtime
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: aiService,
78
+ memoryManager: new MemoryManager(/* ... */),
79
+ llmProvider: new AIService({ provider: 'openai' }),
107
80
  defaultMaxSteps: 10,
108
81
  enableObservability: true,
109
82
  });
110
83
 
111
- // Register agent
112
- const supportAgent = new SupportAgent();
84
+ const agent = new SupportAgent();
113
85
  runtime.registerAgent(SupportAgent);
114
- runtime.registerAgentInstance('support-agent', supportAgent);
86
+ runtime.registerAgentInstance('support-agent', agent);
115
87
  ```
116
88
 
117
- ### 3. Execute the Agent
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 status for order #12345',
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 Human-in-the-Loop
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
- // Resume after approval
150
- const resumedResult = await runtime.resume(result.executionId);
110
+ const resumed = await runtime.resume(result.executionId);
151
111
  ```
152
112
 
153
- ## Core Concepts
113
+ ---
154
114
 
155
- ### Agent State Machine
115
+ ## Multi-Agent Orchestration
156
116
 
157
- Every agent execution follows a deterministic state machine:
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
- idle → thinking → using_tool → thinking → ... → completed
161
-
162
- waiting_for_input
163
-
164
- waiting_for_approval
165
-
166
- failed
124
+ OrchestratorAgent
125
+ └── @Delegate → ResearchAgent
126
+ └── @Delegate → WriterAgent
167
127
  ```
168
128
 
169
- ### Execution Loop
129
+ ```typescript
130
+ import { Agent, Delegate } from '@hazeljs/agent';
170
131
 
171
- The agent runtime implements a controlled execution loop:
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
- 1. **Load State** - Restore agent context and memory
174
- 2. **Load Memory** - Retrieve conversation history
175
- 3. **Retrieve RAG** - Get relevant context (optional)
176
- 4. **Ask LLM** - Decide next action
177
- 5. **Execute Action** - Call tool, ask user, or respond
178
- 6. **Persist State** - Save state and memory
179
- 7. **Repeat or Finish** - Continue or complete
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
- ### Tools
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
- Tools are explicit, auditable capabilities:
176
+ **Registration:**
184
177
 
185
178
  ```typescript
186
- @Tool({
187
- description: 'Send an email',
188
- requiresApproval: true,
189
- timeout: 30000,
190
- retries: 2,
191
- parameters: [
192
- { name: 'to', type: 'string', required: true },
193
- { name: 'subject', type: 'string', required: true },
194
- { name: 'body', type: 'string', required: true },
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 Features:**
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
- ### Memory Integration
193
+ ---
210
194
 
211
- Agents automatically integrate with HazelJS Memory:
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
- // Memory is automatically persisted
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
- // Conversation history is maintained
221
- const result2 = await runtime.execute('agent-name', 'What did I just say?', {
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
- ### RAG Integration
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
- Agents can query RAG before reasoning:
228
+ #### Conditional routing
230
229
 
231
230
  ```typescript
232
- @Agent({
233
- name: 'docs-agent',
234
- enableRAG: true,
235
- ragTopK: 5,
236
- })
237
- export class DocsAgent {
238
- // Agent automatically retrieves relevant docs
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
- ## Event System
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
- Subscribe to agent events for observability:
285
+ #### `AgentGraph` API
245
286
 
246
287
  ```typescript
247
- import { AgentEventType } from '@hazeljs/agent';
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
- // Execution events
250
- runtime.on(AgentEventType.EXECUTION_STARTED, (event) => {
251
- console.log('Agent started:', event.data);
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
- runtime.on(AgentEventType.EXECUTION_COMPLETED, (event) => {
255
- console.log('Agent completed:', event.data);
256
- });
303
+ ---
257
304
 
258
- // Step events
259
- runtime.on(AgentEventType.STEP_STARTED, (event) => {
260
- console.log('Step started:', event.data);
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
- // Tool events
264
- runtime.on(AgentEventType.TOOL_EXECUTION_STARTED, (event) => {
265
- console.log('Tool executing:', event.data);
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
- runtime.on(AgentEventType.TOOL_APPROVAL_REQUESTED, (event) => {
269
- console.log('Approval needed:', event.data);
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
- // Subscribe to all events
273
- runtime.onAny((event) => {
274
- console.log('Event:', event.type, event.data);
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
- ## HazelJS Module Integration
416
+ ---
279
417
 
280
- Use with HazelJS modules:
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
- defaultMaxSteps: 10,
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
- ## Advanced Usage
437
+ ---
303
438
 
304
- ### Pause and Resume
439
+ ## Best Practices
305
440
 
306
- ```typescript
307
- // Execute agent
308
- const result = await runtime.execute('agent', 'Start task');
441
+ ### Keep tools idempotent
309
442
 
310
- if (result.state === 'waiting_for_input') {
311
- // Agent is waiting for user input
312
- const resumed = await runtime.resume(result.executionId, 'User response');
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
- ### Custom Context
452
+ ### Use `@Delegate` for domain specialisation
317
453
 
318
- ```typescript
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
- ### Tool Policies
456
+ ### Choose the right multi-agent pattern
328
457
 
329
- ```typescript
330
- @Tool({
331
- description: 'Delete user data',
332
- requiresApproval: true,
333
- policy: 'admin-only', // Custom policy
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
- ## Architecture
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
- ### 1. Keep Agents Declarative
471
+ ### Handle errors in tools
363
472
 
364
473
  ```typescript
365
- // Good - Declarative
366
- @Agent({ name: 'support-agent' })
367
- export class SupportAgent {
368
- @Tool()
369
- async lookupOrder(input: { orderId: string }) {
370
- return this.orderService.find(input.orderId);
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
- ### 2. Use Approval for Destructive Actions
484
+ ---
382
485
 
383
- ```typescript
384
- @Tool({ requiresApproval: true })
385
- async deleteAccount(input: { userId: string }) {
386
- // Destructive action
387
- }
388
- ```
486
+ ## API Reference
389
487
 
390
- ### 3. Design Idempotent Tools
488
+ ### `AgentRuntime`
391
489
 
392
490
  ```typescript
393
- @Tool()
394
- async createOrder(input: { orderId: string; items: any[] }) {
395
- // Check if order exists first
396
- const existing = await this.findOrder(input.orderId);
397
- if (existing) return existing;
398
-
399
- return this.createNewOrder(input);
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
- ### 4. Handle Errors Gracefully
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
- @Tool()
407
- async externalAPICall(input: any) {
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
- ## API Reference
519
+ // Function node — runs a custom function
520
+ { type: 'function', fn: (state: GraphState) => Promise<GraphState> }
421
521
 
422
- See [API Documentation](./docs/api.md) for complete API reference.
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
- - [Customer Support Agent](./examples/support-agent.ts)
427
- - [Sales Agent with Approval](./examples/sales-agent.ts)
428
- - [Multi-Agent System](./examples/multi-agent.ts)
429
- - [RAG-Powered Agent](./examples/rag-agent.ts)
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.