Loading...
The @hazeljs/agent package provides a production-grade AI-native agent runtime for building stateful, long-running agents with tools, memory, and human-in-the-loop workflows.
Unlike traditional frameworks that treat AI interactions as stateless request/response cycles, HazelJS Agent Runtime is a dedicated execution engine designed specifically for stateful AI agents. It provides:
Traditional Approach (Stateless):
// ❌ Stateless - loses context between requests
app.post('/chat', async (req, res) => {
const response = await llm.chat(req.body.message);
res.json({ response });
// Context is lost after response
});
HazelJS Agent Runtime (Stateful):
// ✅ Stateful - maintains context across steps
const runtime = new AgentRuntime({ /* ... */ });
const result = await runtime.execute('agent-name', 'query', {
sessionId: 'user-123',
enableMemory: true,
});
// Context persists, can resume later
await runtime.resume(result.executionId, 'follow-up');
| Feature | Traditional Frameworks | HazelJS Agent Runtime |
|---|---|---|
| State Management | Manual, stateless | Automatic, persistent |
| Tool Execution | Direct function calls | Orchestrated with approval workflows |
| Lifecycle Control | None | Full pause/resume support |
| Memory Integration | Manual implementation | Automatic conversation tracking |
| Observability | Logging only | Comprehensive event system |
| Error Handling | Try/catch blocks | Built-in retries, circuit breakers |
| Long-Running Tasks | Not supported | Native support with state persistence |
Building production AI agents requires managing state, executing tools safely, integrating with memory systems, and handling complex workflows. The @hazeljs/agent package solves these challenges by providing:
The Agent Runtime is a multi-layered execution engine that orchestrates all aspects of agent execution:
1. Orchestration Layer (AgentRuntime)
2. Execution Layer (AgentExecutor)
3. Tool Layer (ToolExecutor)
4. State Layer (StateManager)
5. Integration Layer (ContextBuilder)
The DatabaseStateManager provides production-grade, database-backed state persistence for agent executions. Unlike in-memory state managers, it offers durable storage, queryable history, and multi-instance support.
import { AgentRuntime } from '@hazeljs/agent';
import { DatabaseStateManager } from '@hazeljs/agent/state';
import { PrismaClient } from '@prisma/client';
// Initialize Prisma client
const prisma = new PrismaClient();
// Create database state manager
const stateManager = new DatabaseStateManager({
client: prisma,
softDelete: true, // Keep deleted contexts for audit
autoArchive: true, // Automatically archive old contexts
archiveThresholdDays: 30, // Archive after 30 days
});
// Use with runtime
const runtime = new AgentRuntime({
stateManager,
// ... other config
});
1. Automatic State Persistence
All agent execution state is automatically persisted to the database:
const result = await runtime.execute('agent-name', 'query', {
sessionId: 'user-123',
userId: 'user-abc',
enableMemory: true,
});
// State is persisted to database:
// - Execution context
// - Agent steps
// - Conversation history
// - Working memory
// - Entities and facts
// - RAG context
2. Session Management
Track and query all conversations for a user or session:
// Get all contexts for a session
const sessionContexts = await stateManager.getSessionContexts('user-123');
console.log(`Found ${sessionContexts.length} conversations`);
sessionContexts.forEach(ctx => {
console.log(`Execution ${ctx.executionId}: ${ctx.state}`);
console.log(`Steps: ${ctx.steps.length}`);
console.log(`Messages: ${ctx.memory.conversationHistory.length}`);
});
3. Pause and Resume
Long-running agent workflows can be paused and resumed:
// Start execution
const result = await runtime.execute('research-agent', 'complex query', {
sessionId: 'user-123',
});
// State is persisted - can resume later (even after server restart)
const resumed = await runtime.resume(result.executionId, 'follow-up question');
// Or resume from any execution ID
const context = await stateManager.getContext(executionId);
if (context) {
await runtime.resume(executionId);
}
4. Working Memory
Store temporary context and variables during execution:
// Set working memory (automatically persisted)
await stateManager.setWorkingMemory(executionId, 'userPreferences', {
language: 'en',
timezone: 'UTC',
});
// Get working memory
const preferences = await stateManager.getWorkingMemory(executionId, 'userPreferences');
5. Soft Deletes and Audit Trail
Keep complete audit trail of all agent executions:
// Soft delete (marks as deleted but keeps in database)
await stateManager.deleteContext(executionId);
// Context is still queryable for audit purposes
const deletedContext = await stateManager.getContext(executionId);
// deletedContext.deletedAt will be set
6. Auto-Archiving
Automatically archive old conversations:
const stateManager = new DatabaseStateManager({
client: prisma,
autoArchive: true,
archiveThresholdDays: 30, // Archive contexts older than 30 days
});
// Old contexts are automatically archived
// Can still be queried but marked as archived
The DatabaseStateManager requires a Prisma schema with the following model:
model AgentContext {
id String @id @default(uuid())
executionId String @unique
agentId String
sessionId String
userId String?
input String
state String
steps Json // AgentStep[]
conversationHistory Json // ConversationMessage[]
workingMemory Json // Record<string, unknown>
facts Json // string[]
entities Json // Entity[]
ragContext Json? // string[]
metadata Json // Record<string, unknown>
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
archivedAt DateTime?
@@index([sessionId])
@@index([userId])
@@index([agentId])
@@index([state])
@@index([createdAt])
}
DatabaseStateManager Methods:
// Create new execution context
await stateManager.createContext(
agentId: string,
sessionId: string,
input: string,
userId?: string,
metadata?: Record<string, unknown>
): Promise<AgentContext>
// Get execution context
await stateManager.getContext(executionId: string): Promise<AgentContext | undefined>
// Update agent state
await stateManager.updateState(executionId: string, newState: AgentState): Promise<void>
// Add execution step
await stateManager.addStep(executionId: string, step: AgentStep): Promise<void>
// Update last step
await stateManager.updateLastStep(executionId: string, updates: Partial<AgentStep>): Promise<void>
// Add conversation message
await stateManager.addMessage(
executionId: string,
role: 'user' | 'assistant' | 'system' | 'tool',
content: string
): Promise<void>
// Working memory operations
await stateManager.setWorkingMemory(executionId: string, key: string, value: unknown): Promise<void>
await stateManager.getWorkingMemory(executionId: string, key: string): Promise<unknown>
// RAG context
await stateManager.addRAGContext(executionId: string, contexts: string[]): Promise<void>
// Check if can continue
await stateManager.canContinue(executionId: string, maxSteps: number): Promise<boolean>
// Delete context (soft or hard delete)
await stateManager.deleteContext(executionId: string): Promise<void>
// Clear all contexts
await stateManager.clear(): Promise<void>
// Get all contexts for a session
await stateManager.getSessionContexts(sessionId: string): Promise<AgentContext[]>
Complete production setup with database persistence:
import { AgentRuntime } from '@hazeljs/agent';
import { DatabaseStateManager } from '@hazeljs/agent/state';
import { PrismaClient } from '@prisma/client';
import { RAGPipeline } from '@hazeljs/rag';
import { AIService } from '@hazeljs/ai';
// Initialize dependencies
const prisma = new PrismaClient();
const stateManager = new DatabaseStateManager({
client: prisma,
softDelete: true,
autoArchive: true,
archiveThresholdDays: 90,
});
const ragPipeline = new RAGPipeline({ /* ... */ });
const aiService = new AIService({ provider: 'openai' });
// Create runtime with database persistence
const runtime = new AgentRuntime({
stateManager,
ragService: ragPipeline,
llmProvider: aiService,
defaultMaxSteps: 20,
enableMetrics: true,
enableCircuitBreaker: true,
});
// Execute agent with persistent state
const result = await runtime.execute('support-agent', 'Help me with order #12345', {
sessionId: 'user-session-123',
userId: 'user-abc',
enableMemory: true,
enableRAG: true,
metadata: {
channel: 'web-chat',
priority: 'high',
},
});
// Query session history
const allSessions = await stateManager.getSessionContexts('user-session-123');
console.log(`User has ${allSessions.length} conversations`);
// Analytics query (using Prisma directly)
const stats = await prisma.agentContext.groupBy({
by: ['agentId', 'state'],
_count: true,
where: {
createdAt: {
gte: new Date(Date.now() - 24 * 60 * 60 * 1000), // Last 24 hours
},
},
});
1. Durability
2. Scalability
3. Queryability
4. Compliance
5. Debugging
6. Recovery
The RedisStateManager provides high-performance, distributed state management using Redis. Perfect for applications requiring fast state access, automatic expiration, and multi-instance deployments.
import { AgentRuntime } from '@hazeljs/agent';
import { RedisStateManager } from '@hazeljs/agent/state';
import Redis from 'ioredis';
// Initialize Redis client
const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD,
db: 0,
retryStrategy: (times) => {
const delay = Math.min(times * 50, 2000);
return delay;
},
});
// Create Redis state manager
const stateManager = new RedisStateManager({
client: redis,
keyPrefix: 'agent:state:', // Namespace for keys
defaultTTL: 3600, // 1 hour for active contexts
completedTTL: 86400, // 24 hours for completed
failedTTL: 604800, // 7 days for failed
});
// Use with runtime
const runtime = new AgentRuntime({
stateManager,
// ... other config
});
1. High-Performance State Access
Redis provides sub-millisecond read/write performance:
// State operations are extremely fast
const result = await runtime.execute('agent-name', 'query', {
sessionId: 'user-123',
});
// Resume is instant (state loaded from Redis)
const resumed = await runtime.resume(result.executionId, 'follow-up');
2. Automatic TTL Management
Different TTLs based on execution state:
const stateManager = new RedisStateManager({
client: redis,
defaultTTL: 3600, // Active contexts: 1 hour
completedTTL: 86400, // Completed: 24 hours
failedTTL: 604800, // Failed: 7 days (for debugging)
});
// TTL automatically adjusted based on state
// - IDLE, THINKING, USING_TOOL: defaultTTL (1 hour)
// - COMPLETED: completedTTL (24 hours)
// - FAILED: failedTTL (7 days)
3. Distributed State
Share state across multiple server instances:
// Server Instance 1
const result = await runtime1.execute('agent', 'query', {
sessionId: 'user-123',
});
// Server Instance 2 (can access same state)
const resumed = await runtime2.resume(result.executionId);
// State is shared via Redis
4. Session Indexing
Fast session-based queries using Redis Sets:
// Get all contexts for a session
const sessionContexts = await stateManager.getSessionContexts('user-123');
console.log(`Found ${sessionContexts.length} conversations`);
sessionContexts.forEach(ctx => {
console.log(`- ${ctx.executionId}: ${ctx.state}`);
});
// Redis uses Sets for efficient session indexing
// Key: agent:state:session:user-123
// Value: Set of execution IDs
5. Automatic Cleanup
Redis automatically removes expired contexts:
// No manual cleanup needed
// Redis TTL handles expiration automatically
// Contexts expire based on state:
// - Active contexts expire after 1 hour of inactivity
// - Completed contexts expire after 24 hours
// - Failed contexts kept for 7 days for debugging
| Feature | Redis State Manager | Database State Manager |
|---|---|---|
| Performance | ⚡ Sub-millisecond | 🐢 10-50ms |
| Scalability | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐ Good |
| Persistence | ⚠️ Memory-based | ✅ Durable |
| Automatic Cleanup | ✅ TTL-based | ❌ Manual archiving |
| Query Capabilities | ⚠️ Limited | ✅ Full SQL |
| Audit Trail | ❌ Expires | ✅ Permanent |
| Cost | 💰 Memory-intensive | 💰💰 Storage-based |
| Best For | High-performance, temporary state | Long-term storage, compliance |
✅ Use Redis When:
❌ Use Database When:
Complete production setup with Redis clustering:
import { AgentRuntime } from '@hazeljs/agent';
import { RedisStateManager } from '@hazeljs/agent/state';
import Redis from 'ioredis';
// Redis Cluster for high availability
const redis = new Redis.Cluster([
{ host: 'redis-node-1', port: 6379 },
{ host: 'redis-node-2', port: 6379 },
{ host: 'redis-node-3', port: 6379 },
], {
redisOptions: {
password: process.env.REDIS_PASSWORD,
},
clusterRetryStrategy: (times) => {
return Math.min(100 * times, 2000);
},
});
// State manager with optimized TTLs
const stateManager = new RedisStateManager({
client: redis,
keyPrefix: 'prod:agent:',
defaultTTL: 1800, // 30 minutes for active
completedTTL: 43200, // 12 hours for completed
failedTTL: 259200, // 3 days for failed
});
// Runtime with Redis state
const runtime = new AgentRuntime({
stateManager,
// ... other config
});
// Monitor Redis health
redis.on('connect', () => {
console.log('Redis connected');
});
redis.on('error', (err) => {
console.error('Redis error:', err);
});
// Graceful shutdown
process.on('SIGTERM', async () => {
await redis.quit();
console.log('Redis disconnected');
});
Use both for optimal performance and durability:
import { RedisStateManager } from '@hazeljs/agent/state';
import { DatabaseStateManager } from '@hazeljs/agent/state';
class HybridStateManager implements IAgentStateManager {
constructor(
private redis: RedisStateManager,
private database: DatabaseStateManager
) {}
async createContext(...args): Promise<AgentContext> {
// Create in both
const context = await this.redis.createContext(...args);
await this.database.createContext(...args);
return context;
}
async getContext(executionId: string): Promise<AgentContext | undefined> {
// Try Redis first (fast)
let context = await this.redis.getContext(executionId);
if (!context) {
// Fallback to database
context = await this.database.getContext(executionId);
if (context) {
// Restore to Redis
await this.redis.createContext(
context.agentId,
context.sessionId,
context.input,
context.userId,
context.metadata
);
}
}
return context;
}
async updateState(executionId: string, newState: AgentState): Promise<void> {
// Update both (fire and forget for database)
await this.redis.updateState(executionId, newState);
this.database.updateState(executionId, newState).catch(console.error);
}
// ... implement other methods
}
// Usage
const hybridState = new HybridStateManager(
new RedisStateManager({ client: redis }),
new DatabaseStateManager({ client: prisma })
);
const runtime = new AgentRuntime({
stateManager: hybridState,
});
// Benefits:
// - Fast reads from Redis
// - Durable writes to database
// - Automatic fallback if Redis fails
// - Full audit trail in database
1. Connection Pooling
const redis = new Redis({
host: 'localhost',
port: 6379,
maxRetriesPerRequest: 3,
enableReadyCheck: true,
lazyConnect: false,
});
2. Key Namespacing
// Use prefixes to organize keys
const stateManager = new RedisStateManager({
client: redis,
keyPrefix: 'myapp:agent:state:', // Namespace your keys
});
// Results in keys like:
// myapp:agent:state:execution-id-123
// myapp:agent:state:session:user-456
3. Memory Management
// Set maxmemory policy in Redis config
// redis.conf:
// maxmemory 2gb
// maxmemory-policy allkeys-lru
// Or via command:
await redis.config('SET', 'maxmemory', '2gb');
await redis.config('SET', 'maxmemory-policy', 'allkeys-lru');
4. Monitoring
// Monitor Redis stats
setInterval(async () => {
const info = await redis.info('stats');
console.log('Redis stats:', info);
// Check key count
const keyCount = await redis.dbsize();
console.log('Total keys:', keyCount);
}, 60000); // Every minute
The Agent Runtime is not just a library—it's a full execution engine that manages the entire agent lifecycle:
Example:
// Runtime manages the entire execution lifecycle
const runtime = new AgentRuntime({
defaultMaxSteps: 10, // Prevents infinite loops
enableCircuitBreaker: true, // Protects against failures
enableRateLimiting: true, // Controls resource usage
});
// Runtime handles state, memory, and tool execution automatically
const result = await runtime.execute('agent', 'query');
Unlike stateless request handlers, the runtime maintains context across multiple steps:
Comparison:
// ❌ Stateless - loses context
app.post('/chat', async (req, res) => {
// No memory of previous interactions
const response = await llm.chat(req.body.message);
});
// ✅ Stateful Runtime - maintains context
const runtime = new AgentRuntime({ memoryManager });
await runtime.execute('agent', 'Hello', { sessionId: 'user-123' });
await runtime.execute('agent', 'What did I say?', { sessionId: 'user-123' });
// Agent remembers the conversation
The runtime orchestrates tool execution with enterprise-grade features:
Example:
@Tool({ requiresApproval: true, timeout: 30000 })
async deleteUser(userId: string) {
// Runtime handles approval workflow automatically
}
// Runtime manages the approval process
runtime.on('tool.approval.requested', (event) => {
// Handle approval
runtime.approveToolExecution(event.requestId, 'admin');
});
The runtime automatically integrates with memory systems:
Example:
// Runtime automatically loads and persists memory
const runtime = new AgentRuntime({ memoryManager });
// First interaction
await runtime.execute('agent', 'My name is John', {
sessionId: 'user-123',
enableMemory: true
});
// Second interaction - agent remembers
await runtime.execute('agent', 'What is my name?', {
sessionId: 'user-123',
enableMemory: true
});
// Agent responds: "Your name is John"
Built-in observability without external tools:
Example:
// Runtime provides comprehensive observability
runtime.on('execution.started', (event) => {
// Track execution start
});
runtime.on('tool.execution.completed', (event) => {
// Monitor tool usage
});
const metrics = runtime.getMetrics();
console.log(metrics.executions.successRate);
Enterprise-grade reliability features:
Example:
const runtime = new AgentRuntime({
enableCircuitBreaker: true,
circuitBreakerConfig: {
failureThreshold: 5,
resetTimeout: 30000,
},
enableRateLimiting: true,
rateLimitConfig: {
tokensPerMinute: 60,
burstSize: 10,
},
});
Seamlessly integrates with HazelJS ecosystem:
| Feature | LangChain/LlamaIndex | HazelJS Agent Runtime |
|---|---|---|
| State Management | Manual chains | Automatic runtime management |
| Tool Execution | Direct calls | Orchestrated with approval |
| Memory | Manual implementation | Automatic integration |
| Observability | Limited | Comprehensive event system |
| Production Features | Basic | Circuit breakers, rate limiting |
| Framework Integration | Standalone | Native HazelJS integration |
Without Runtime:
// ❌ Manual state management
let conversationHistory = [];
let currentState = 'idle';
let executionId = null;
// Manual tool execution
try {
const result = await tool.execute();
} catch (error) {
// Manual error handling
}
// Manual memory persistence
await saveToDatabase(conversationHistory);
With HazelJS Agent Runtime:
// ✅ Runtime handles everything
const runtime = new AgentRuntime({ memoryManager });
const result = await runtime.execute('agent', 'query');
// State, memory, tools, errors - all handled automatically
| Feature | Serverless Functions | Agent Runtime |
|---|---|---|
| State Persistence | External storage required | Built-in |
| Long-Running Tasks | Timeout limits | Native support |
| Context Continuity | Stateless | Stateful |
| Tool Orchestration | Manual | Automatic |
| Observability | Cloud provider logs | Event system |
npm install @hazeljs/agent @hazeljs/core @hazeljs/rag @hazeljs/ai
Peer Dependencies:
# For LLM integration
npm install @hazeljs/ai
# For memory integration
npm install @hazeljs/rag
Use the CLI to quickly scaffold a new agent:
npx @hazeljs/cli generate agent support-agent
# or
hazel g agent support-agent
This generates a complete agent file with @Agent and @Tool decorators ready to customize.
Alternatively, you can create an agent manually:
import { Agent, Tool } from '@hazeljs/agent';
@Agent({
name: 'support-agent',
description: 'Customer support agent',
systemPrompt: 'You are a helpful customer support agent.',
enableMemory: true,
enableRAG: true,
})
export class SupportAgent {
@Tool({
description: 'Look up order information by order ID',
parameters: [
{
name: 'orderId',
type: 'string',
description: 'The order ID to lookup',
required: true,
},
],
})
async lookupOrder(input: { orderId: string }) {
// Your implementation
return {
orderId: input.orderId,
status: 'shipped',
trackingNumber: 'TRACK123',
};
}
@Tool({
description: 'Process a refund for an order',
requiresApproval: true, // Requires human approval
parameters: [
{
name: 'orderId',
type: 'string',
description: 'The order ID to refund',
required: true,
},
{
name: 'amount',
type: 'number',
description: 'Refund amount',
required: true,
},
],
})
async processRefund(input: { orderId: string; amount: number }) {
// Your implementation
return {
success: true,
refundId: 'REF123',
amount: input.amount,
};
}
}
import { AgentRuntime } from '@hazeljs/agent';
import { MemoryManager } from '@hazeljs/rag';
import { AIService } from '@hazeljs/ai';
// Initialize dependencies
const memoryManager = new MemoryManager(/* ... */);
const aiService = new AIService({ provider: 'openai' });
// Create runtime
const runtime = new AgentRuntime({
memoryManager,
llmProvider: aiService,
defaultMaxSteps: 10,
enableMetrics: true,
enableCircuitBreaker: true,
enableRateLimiting: true,
});
// Register agent
const supportAgent = new SupportAgent();
runtime.registerAgent(SupportAgent);
runtime.registerAgentInstance('support-agent', supportAgent);
// Execute agent
const result = await runtime.execute(
'support-agent',
'I need to check my order status for order #12345',
{
sessionId: 'user-session-123',
userId: 'user-456',
enableMemory: true,
enableRAG: true,
}
);
console.log(result.response);
console.log(`Completed in ${result.steps.length} steps`);
import { AgentEventType } from '@hazeljs/agent';
// Subscribe to approval requests
runtime.on(AgentEventType.TOOL_APPROVAL_REQUESTED, async (event) => {
console.log('Approval needed:', event.data);
// Approve or reject
runtime.approveToolExecution(event.data.requestId, 'admin-user');
// or
// runtime.rejectToolExecution(event.data.requestId);
});
// Resume after approval
const resumedResult = await runtime.resume(result.executionId);
Every agent execution follows a deterministic state machine:
States:
idle - Agent is ready but not executingthinking - Agent is reasoning about next actionusing_tool - Agent is executing a toolwaiting_for_input - Agent is waiting for user inputwaiting_for_approval - Agent is waiting for tool approvalcompleted - Agent execution completed successfullyfailed - Agent execution failedThe agent runtime implements a controlled execution loop:
Tools are explicit, auditable capabilities:
@Tool({
description: 'Send an email',
requiresApproval: true,
timeout: 30000,
retries: 2,
parameters: [
{ name: 'to', type: 'string', required: true },
{ name: 'subject', type: 'string', required: true },
{ name: 'body', type: 'string', required: true },
],
})
async sendEmail(input: { to: string; subject: string; body: string }) {
// Implementation
}
Tool Features:
Agents automatically integrate with HazelJS Memory:
// Memory is automatically persisted
const result = await runtime.execute('agent-name', 'Hello', {
sessionId: 'session-123',
enableMemory: true,
});
// Conversation history is maintained
const result2 = await runtime.execute('agent-name', 'What did I just say?', {
sessionId: 'session-123', // Same session
enableMemory: true,
});
Memory Features:
Agents can query RAG before reasoning:
@Agent({
name: 'docs-agent',
enableRAG: true,
ragTopK: 5,
})
export class DocsAgent {
// Agent automatically retrieves relevant docs
}
Subscribe to agent events for observability:
import { AgentEventType } from '@hazeljs/agent';
// Execution events
runtime.on(AgentEventType.EXECUTION_STARTED, (event) => {
console.log('Agent started:', event.data);
});
runtime.on(AgentEventType.EXECUTION_COMPLETED, (event) => {
console.log('Agent completed:', event.data);
});
// Step events
runtime.on(AgentEventType.STEP_STARTED, (event) => {
console.log('Step started:', event.data);
});
// Tool events
runtime.on(AgentEventType.TOOL_EXECUTION_STARTED, (event) => {
console.log('Tool executing:', event.data);
});
runtime.on(AgentEventType.TOOL_APPROVAL_REQUESTED, (event) => {
console.log('Approval needed:', event.data);
});
// Subscribe to all events
runtime.onAny((event) => {
console.log('Event:', event.type, event.data);
});
Available Events:
EXECUTION_STARTED - Agent execution startedEXECUTION_COMPLETED - Agent execution completedEXECUTION_FAILED - Agent execution failedSTEP_STARTED - Execution step startedSTEP_COMPLETED - Execution step completedSTEP_FAILED - Execution step failedSTATE_CHANGED - Agent state changedTOOL_EXECUTION_STARTED - Tool execution startedTOOL_EXECUTION_COMPLETED - Tool execution completedTOOL_EXECUTION_FAILED - Tool execution failedTOOL_APPROVAL_REQUESTED - Tool approval requestedTOOL_APPROVAL_GRANTED - Tool approval grantedTOOL_APPROVAL_DENIED - Tool approval deniedUSER_INPUT_REQUESTED - User input requestedUSER_INPUT_RECEIVED - User input receivedMEMORY_UPDATED - Memory updatedRAG_QUERY_EXECUTED - RAG query executedUse with HazelJS modules:
import { HazelModule } from '@hazeljs/core';
import { AgentModule } from '@hazeljs/agent';
import { RagModule } from '@hazeljs/rag';
@HazelModule({
imports: [
RagModule.forRoot({ /* ... */ }),
AgentModule.forRoot({
runtime: {
defaultMaxSteps: 10,
enableMetrics: true,
},
agents: [SupportAgent, SalesAgent],
}),
],
})
export class AppModule {}
// Execute agent
const result = await runtime.execute('agent', 'Start task');
if (result.state === 'waiting_for_input') {
// Agent is waiting for user input
const resumed = await runtime.resume(result.executionId, 'User response');
}
const result = await runtime.execute('agent', 'Process order', {
initialContext: {
userId: '123',
orderData: { /* ... */ },
},
});
@Tool({
description: 'Delete user data',
requiresApproval: true,
policy: 'admin-only', // Custom policy
})
async deleteUserData(input: { userId: string }) {
// Implementation
}
const runtime = new AgentRuntime({
enableRateLimiting: true,
rateLimitConfig: {
tokensPerMinute: 60,
burstSize: 10,
},
});
const runtime = new AgentRuntime({
enableCircuitBreaker: true,
circuitBreakerConfig: {
failureThreshold: 5,
successThreshold: 2,
resetTimeout: 30000,
},
});
const runtime = new AgentRuntime({
enableMetrics: true,
});
// Get metrics
const metrics = runtime.getMetrics();
console.log(metrics.executions.total);
console.log(metrics.executions.successRate);
console.log(metrics.performance.averageDuration);
// ✅ Good - Declarative
@Agent({ name: 'support-agent' })
export class SupportAgent {
@Tool()
async lookupOrder(input: { orderId: string }) {
return this.orderService.find(input.orderId);
}
}
// ❌ Bad - Business logic in decorator
@Agent({
name: 'support-agent',
onExecute: async () => { /* complex logic */ }
})
@Tool({ requiresApproval: true })
async deleteAccount(input: { userId: string }) {
// Destructive action
}
@Tool()
async createOrder(input: { orderId: string; items: any[] }) {
// Check if order exists first
const existing = await this.findOrder(input.orderId);
if (existing) return existing;
return this.createNewOrder(input);
}
@Tool()
async externalAPICall(input: any) {
try {
return await this.api.call(input);
} catch (error) {
// Return structured error
return {
success: false,
error: error.message,
};
}
}
// Always provide sessionId for memory
const result = await runtime.execute('agent', 'query', {
sessionId: 'user-session-123',
enableMemory: true,
});
runtime.onAny((event) => {
// Log to monitoring system
logger.info('Agent event', {
type: event.type,
executionId: event.executionId,
data: event.data,
});
});
The Agent Runtime is ideal for scenarios that require:
Use Runtime When:
Example:
// Customer support agent that remembers conversation
const result = await runtime.execute('support-agent',
'I ordered product #12345',
{ sessionId: 'user-123' }
);
// Later, agent remembers the order
await runtime.execute('support-agent',
'What was the status?',
{ sessionId: 'user-123' }
);
Use Runtime When:
Example:
@Tool({ requiresApproval: true })
async deleteAccount(userId: string) {
// Runtime handles approval workflow
}
// Runtime manages the entire approval process
runtime.on('tool.approval.requested', handleApproval);
Use Runtime When:
Example:
const runtime = new AgentRuntime({
enableCircuitBreaker: true,
enableRateLimiting: true,
enableMetrics: true,
});
// All production features built-in
Use Runtime When:
Example:
// Runtime automatically manages memory
const runtime = new AgentRuntime({
memoryManager,
ragService
});
// Memory and RAG integrated automatically
await runtime.execute('agent', 'query', {
enableMemory: true,
enableRAG: true,
});
Handle support tickets with tool access, conversation memory, and approval workflows for sensitive operations.
@Agent({ name: 'support-agent' })
class SupportAgent {
@Tool({ requiresApproval: true })
async processRefund(orderId: string) {
// Requires approval before execution
}
}
Qualify leads, schedule meetings, and maintain conversation context across multiple interactions.
Research topics with RAG integration, multi-hop reasoning, and source verification.
Analyze data, generate reports, and maintain analysis context across sessions.
Multi-step business processes with pause/resume capabilities and state persistence.
Generate content with approval workflows, memory of previous content, and iterative refinement.
When NOT to use Runtime:
When to use Runtime:
Main runtime class for agent execution.
Methods:
registerAgent(agentClass) - Register an agent classregisterAgentInstance(name, instance) - Register an agent instanceexecute(agentName, input, options) - Execute an agentresume(executionId, input?) - Resume a paused executiongetContext(executionId) - Get execution contexton(type, handler) - Subscribe to eventsapproveToolExecution(requestId, approvedBy) - Approve a tool executionrejectToolExecution(requestId) - Reject a tool executiongetPendingApprovals() - Get pending approval requestsMarks a class as an agent.
@Agent({
name: string;
description?: string;
systemPrompt?: string;
enableMemory?: boolean;
enableRAG?: boolean;
ragTopK?: number;
})
Marks a method as a tool.
@Tool({
name?: string;
description?: string;
parameters?: ToolParameter[];
requiresApproval?: boolean;
timeout?: number;
retries?: number;
policy?: string;
})
Generate a new agent in seconds:
npx @hazeljs/cli generate agent support-agent
This creates a complete agent file with @Agent and @Tool decorators ready to customize.
See the examples directory for complete examples:
MIT