npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hazeljs/agent

v1.0.5

Published

AI-native Agent Runtime for HazelJS - stateful, long-running agents with tools, memory, and human-in-the-loop

Readme

@hazeljs/agent

Build AI agents that actually do things.

Part of the HazelJS AI-Native Backend Framework. 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.

🚀 Trusted by 200K+ monthly downloads • 37+ GitHub stars • 15+ daily active developers

npm version npm downloads License: Apache-2.0

Why @hazeljs/agent?

Built for AI-native applications - not just another agent framework. When you combine @hazeljs/agent with @hazeljs/core, @hazeljs/ai, and @hazeljs/rag, you get a complete stack for intelligent backends.

Perfect for:

  • AI startups building production agent systems
  • Teams creating customer support or automation agents
  • Developers who want stateful, long-running workflows
  • Projects needing multi-agent orchestration

Overview

Unlike stateless request handlers, agents are:

  • Stateful — Maintain context across multiple steps and sessions
  • Long-running — Execute complex, multi-hop workflows over time
  • Tool-using — Call functions safely with timeout, retry, and approval workflows
  • Multi-agent — Orchestrate teams of specialised agents with AgentGraph, SupervisorAgent, and @Delegate
  • Memory-enabled — Integrate with persistent memory systems
  • Observable — Full event system for monitoring and debugging
  • Resumable — Support pause/resume and human-in-the-loop

Installation

npm install @hazeljs/agent @hazeljs/core @hazeljs/rag

Quick Start — Single Agent

1. Define an agent

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', required: true }],
  })
  async lookupOrder(input: { orderId: string }) {
    return { orderId: input.orderId, status: 'shipped', trackingNumber: 'TRACK123' };
  }

  @Tool({
    description: 'Process a refund for an order',
    requiresApproval: true, // requires human approval before execution
    parameters: [
      { name: 'orderId', type: 'string', required: true },
      { name: 'amount', type: 'number', required: true },
    ],
  })
  async processRefund(input: { orderId: string; amount: number }) {
    return { success: true, refundId: 'REF123', amount: input.amount };
  }
}

2. Set up the runtime

import { AgentRuntime, AgentService } from '@hazeljs/agent';
import { MemoryManager } from '@hazeljs/rag';
import { AIEnhancedService } from '@hazeljs/ai';

const runtime = new AgentRuntime({
  memoryManager: new MemoryManager(/* ... */),
  llmProvider: AgentService.createLLMProviderFromAI(new AIEnhancedService()),
  defaultMaxSteps: 10,
  // Optional: pass observabilityProvider from @hazeljs/observability for OTel spans
});

const agent = new SupportAgent();
runtime.registerAgent(SupportAgent);
runtime.registerAgentInstance('support-agent', agent);

3. Execute

const result = await runtime.execute('support-agent', 'I need to check my order #12345', {
  sessionId: 'user-session-123',
  userId: 'user-456',
  enableMemory: true,
});

console.log(result.response);
console.log(`Completed in ${result.steps.length} steps`);

4. Handle human-in-the-loop

runtime.on('tool.approval.requested', async (event) => {
  console.log('Approval needed:', event.data);
  runtime.approveToolExecution(event.data.requestId, 'admin-user');
});

const resumed = await runtime.resume(result.executionId);

Multi-Agent Orchestration

@hazeljs/agent ships three complementary patterns for coordinating multiple agents. Use them individually or combine them.

Pattern 1 — @Delegate: peer-to-peer agent calls

@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).

OrchestratorAgent
   └── @Delegate → ResearchAgent
   └── @Delegate → WriterAgent
import { Agent, Delegate } from '@hazeljs/agent';

@Agent({
  name: 'OrchestratorAgent',
  description: 'Plans and delegates research and writing tasks',
  systemPrompt: 'You orchestrate research and writing. Use the available tools to complete tasks.',
})
export class OrchestratorAgent {
  // The LLM sees this as a tool. At runtime it calls ResearchAgent.
  @Delegate({
    agent: 'ResearchAgent',
    description: 'Research a topic thoroughly and return key findings',
    inputField: 'query',
  })
  async researchTopic(query: string): Promise<string> {
    return ''; // body replaced at runtime by AgentRuntime
  }

  // The LLM sees this as a tool. At runtime it calls WriterAgent.
  @Delegate({
    agent: 'WriterAgent',
    description: 'Write a polished article from the provided research notes',
    inputField: 'content',
  })
  async writeArticle(content: string): Promise<string> {
    return ''; // body replaced at runtime by AgentRuntime
  }
}

@Agent({ name: 'ResearchAgent', systemPrompt: 'You are an expert researcher.' })
export class ResearchAgent {
  @Tool({
    description: 'Search the web',
    parameters: [{ name: 'query', type: 'string', required: true }],
  })
  async searchWeb(input: { query: string }) {
    return `Research findings for: ${input.query}`;
  }
}

@Agent({ name: 'WriterAgent', systemPrompt: 'You are a professional technical writer.' })
export class WriterAgent {
  @Tool({
    description: 'Format content as Markdown',
    parameters: [{ name: 'raw', type: 'string', required: true }],
  })
  async formatMarkdown(input: { raw: string }) {
    return `## Article\n\n${input.raw}`;
  }
}

Registration:

const orchestrator = new OrchestratorAgent();
const researcher = new ResearchAgent();
const writer = new WriterAgent();

[ResearchAgent, WriterAgent, OrchestratorAgent].forEach((A) => runtime.registerAgent(A));
[
  ['OrchestratorAgent', orchestrator],
  ['ResearchAgent', researcher],
  ['WriterAgent', writer],
].forEach(([name, inst]) => runtime.registerAgentInstance(name as string, inst));

const result = await runtime.execute('OrchestratorAgent', 'Write a blog post about LLMs');
console.log(result.response);

Note: @Delegate implicitly registers the method as @Tool. Do not add @Tool separately.


Pattern 2 — AgentGraph: DAG pipelines

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.

Entry → NodeA → NodeB → END              (sequential)
Entry → RouterNode → NodeA | NodeB → END (conditional)
Entry → Splitter → [NodeA ‖ NodeB] → Combiner → END (parallel)
import { END } from '@hazeljs/agent';

// Create graph via the runtime
const graph = runtime.createGraph('research-pipeline');

Sequential pipeline

const pipeline = runtime
  .createGraph('blog-pipeline')
  .addNode('researcher', { type: 'agent', agentName: 'ResearchAgent' })
  .addNode('writer', { type: 'agent', agentName: 'WriterAgent' })
  .addEdge('researcher', 'writer')
  .addEdge('writer', END)
  .setEntryPoint('researcher')
  .compile();

const result = await pipeline.execute('Write a blog post about TypeScript generics');
console.log(result.output);

Conditional routing

const router = runtime
  .createGraph('router')
  .addNode('classifier', { type: 'agent', agentName: 'ClassifierAgent' })
  .addNode('coder', { type: 'agent', agentName: 'CoderAgent' })
  .addNode('writer', { type: 'agent', agentName: 'WriterAgent' })
  .setEntryPoint('classifier')
  .addConditionalEdge('classifier', (state) => (state.data?.type === 'code' ? 'coder' : 'writer'))
  .addEdge('coder', END)
  .addEdge('writer', END)
  .compile();

const result = await router.execute('Write a sorting algorithm in TypeScript');

Parallel fan-out / fan-in

async function splitTask(state: GraphState) {
  return { ...state, data: { ...state.data, split: true } };
}

async function mergeResults(state: GraphState) {
  const results = state.data?.branchResults as ParallelBranchResult[];
  return { ...state, output: results.map((r) => r.output).join('\n---\n') };
}

const parallel = runtime
  .createGraph('parallel-research')
  .addNode('splitter', { type: 'function', fn: splitTask })
  .addNode('parallel-1', { type: 'parallel', branches: ['tech-researcher', 'market-researcher'] })
  .addNode('tech-researcher', { type: 'agent', agentName: 'TechResearchAgent' })
  .addNode('market-researcher', { type: 'agent', agentName: 'MarketResearchAgent' })
  .addNode('combiner', { type: 'function', fn: mergeResults })
  .addEdge('splitter', 'parallel-1')
  .addEdge('parallel-1', 'combiner')
  .addEdge('combiner', END)
  .setEntryPoint('splitter')
  .compile();

const result = await parallel.execute('Analyse the AI framework market');

Streaming execution

for await (const chunk of pipeline.stream('Tell me about GraphRAG')) {
  if (chunk.type === 'node_complete') {
    console.log(`✓ ${chunk.nodeId}: ${chunk.output?.slice(0, 80)}...`);
  }
}

AgentGraph API

interface AgentGraph {
  addNode(id: string, config: GraphNodeConfig): this;
  addEdge(from: string, to: string): this;
  addConditionalEdge(from: string, router: RouterFunction): this;
  setEntryPoint(nodeId: string): this;
  compile(): CompiledGraph;
}

interface CompiledGraph {
  execute(input: string, options?: GraphExecutionOptions): Promise<GraphExecutionResult>;
  stream(input: string, options?: GraphExecutionOptions): AsyncIterable<GraphStreamChunk>;
  visualize(): string; // returns a Mermaid diagram string
}

Pattern 3 — SupervisorAgent: LLM-driven routing

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.

User Task
    │
Supervisor  ←──────────────────────────┐
    │                                   │
┌───▼────────────────┐           Worker result
│  Route to worker?  │                  │
└───────────┬────────┘                  │
            │                           │
     ┌──────▼──────┐                    │
     │ WorkerAgent │───────────────────┘
     └─────────────┘
const supervisor = runtime.createSupervisor({
  name: 'project-manager',
  workers: ['ResearchAgent', 'CoderAgent', 'WriterAgent'],
  maxRounds: 6,
  llm: async (prompt) => {
    const res = await openai.chat.completions.create({
      model: 'gpt-4o-mini',
      messages: [{ role: 'user', content: prompt }],
    });
    return res.choices[0].message.content ?? '';
  },
});

const result = await supervisor.run('Build and document a REST API for a todo app', {
  sessionId: 'proj-001',
});

console.log(result.response);
result.rounds.forEach((round, i) => {
  console.log(
    `Round ${i + 1}: routed to ${round.worker} — ${round.workerResult.response.slice(0, 80)}`
  );
});

SupervisorConfig:

| Field | Type | Default | Description | | ----------- | ------------------------------------- | ------- | -------------------------------------------------- | | name | string | — | Supervisor instance name | | workers | string[] | — | Registered agent names available to the supervisor | | maxRounds | number | 5 | Maximum routing iterations | | llm | (prompt: string) => Promise<string> | — | LLM function for routing decisions | | sessionId | string | auto | Session for memory continuity across rounds |


Architecture

┌──────────────────────────────────────────────────────────────────┐
│                        AgentRuntime                              │
├──────────────┬───────────────┬───────────────┬───────────────────┤
│   Registry   │  State Mgr    │   Executor    │  Tool Executor    │
│  (agents,    │  (in-mem /    │  (step loop,  │  (timeout,        │
│   tools)     │   Redis / DB) │   approval)   │   retry, audit)   │
├──────────────┴───────────────┴───────────────┴───────────────────┤
│                  Multi-Agent Layer                                │
│  ┌──────────────┐  ┌───────────────────┐  ┌────────────────────┐ │
│  │  AgentGraph  │  │  SupervisorAgent  │  │  @Delegate         │ │
│  │  (DAG pipes) │  │  (LLM routing)    │  │  (peer-to-peer)    │ │
│  └──────────────┘  └───────────────────┘  └────────────────────┘ │
├────────────────────────────────────────────────────────────────── ┤
│         Memory Module (@hazeljs/rag)  │  RAG Module               │
└──────────────────────────────────────────────────────────────────┘

State Machine

Every agent execution follows a deterministic state machine:

idle → thinking → using_tool → thinking → ... → completed
                     ↓
               waiting_for_input
                     ↓
               waiting_for_approval
                     ↓
                  failed

Event System

import { AgentEventType } from '@hazeljs/agent';

runtime.on(AgentEventType.EXECUTION_STARTED, (e) => console.log('started:', e.data));
runtime.on(AgentEventType.EXECUTION_COMPLETED, (e) => console.log('completed:', e.data));
runtime.on(AgentEventType.STEP_STARTED, (e) => console.log('step:', e.data));
runtime.on(AgentEventType.TOOL_EXECUTION_STARTED, (e) => console.log('tool:', e.data));
runtime.on(AgentEventType.TOOL_APPROVAL_REQUESTED, (e) => {
  console.log('approval needed:', e.data);
  runtime.approveToolExecution(e.data.requestId, 'admin');
});

// Catch-all
runtime.onAny((e) => console.log(e.type, e.data));

Production deployment (1.0.1+)

For multi-instance deployments, use Redis-backed state and durable approvals:

import { AgentModule } from '@hazeljs/agent';
import { createClient } from 'redis';

const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

await AgentModule.forRootAsync({
  redis: { client: redis },
  useRedisApprovals: true,
  runtime: {
    strictEventHandlers: process.env.NODE_ENV === 'production',
    observabilityProvider: myObservabilityProvider, // optional, from @hazeljs/observability
    enableMetrics: true,
    enableCircuitBreaker: true,
  },
});

Environment variables: REDIS_URL, AGENT_STATE_BACKEND (memory | redis | database)

Factories: createStateManager, createStateManagerFromEnv, createApprovalStore

Observability: Pass observabilityProvider for OpenTelemetry spans (agent.execute, agent.tool.execute, agent.llm). The enableObservability flag is reserved; spans require an injected provider.

Resilience: Circuit breaker and retry use @hazeljs/resilience internally.

See PERSISTENCE.md, PRODUCTION_READINESS.md, and QUICKSTART.md.


HazelJS Module Integration

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,
        strictEventHandlers: process.env.NODE_ENV === 'production',
      },
      agents: [SupportAgent, ResearchAgent, WriterAgent, OrchestratorAgent],
    }),
  ],
})
export class AppModule {}

Best Practices

Keep tools idempotent

@Tool({ description: 'Create an order' })
async createOrder(input: { orderId: string; items: Item[] }) {
  const existing = await this.findOrder(input.orderId);
  if (existing) return existing;            // safe to retry
  return this.createNewOrder(input);
}

Use @Delegate for domain specialisation

Keep each agent focused on one domain. @Delegate lets the orchestrator combine specialists without any agent becoming a monolith.

Choose the right multi-agent pattern

| Pattern | Use when | | ----------------- | ------------------------------------------------------------- | | @Delegate | Two or three agents with a clear orchestrator / worker split | | AgentGraph | Workflow is known at design time; conditional routing matters | | SupervisorAgent | Task decomposition is dynamic; you want LLM-driven routing |

Require approval for destructive actions

@Tool({ requiresApproval: true, description: 'Delete user account' })
async deleteAccount(input: { userId: string }) { /* ... */ }

Handle errors in tools

@Tool({ description: 'Call external API' })
async callExternalAPI(input: { endpoint: string }) {
  try {
    return await this.api.call(input.endpoint);
  } catch (error) {
    return { success: false, error: (error as Error).message };
  }
}

API Reference

AgentRuntime

class AgentRuntime {
  execute(
    agentName: string,
    input: string,
    options?: ExecuteOptions
  ): Promise<AgentExecutionResult>;
  resume(executionId: string, input?: string): Promise<AgentExecutionResult>;
  registerAgent(agentClass: new (...args: unknown[]) => unknown): void;
  registerAgentInstance(name: string, instance: unknown): void;
  createGraph(name: string): AgentGraph;
  createSupervisor(config: SupervisorConfig): SupervisorAgent;
  approveToolExecution(requestId: string, approvedBy: string): void;
  rejectToolExecution(requestId: string, reason?: string): void;
  on(event: string, handler: (e: AgentEvent) => void): void;
  onAny(handler: (e: AgentEvent) => void): void;
}

Decorators

| Decorator | Target | Description | | ------------------- | ------ | ------------------------------------------------------------------------ | | @Agent(config) | Class | Declares a class as an agent | | @Tool(config) | Method | Exposes a method as an LLM-callable tool | | @Delegate(config) | Method | Delegates a method to another agent (registers as @Tool automatically) |

GraphNodeConfig types

// Agent node — runs a registered agent
{ type: 'agent', agentName: string }

// Function node — runs a custom function
{ type: 'function', fn: (state: GraphState) => Promise<GraphState> }

// Parallel node — fans out to multiple branches simultaneously
{ type: 'parallel', branches: string[] }

Examples


License

Apache 2.0

Contributing

Contributions are welcome! See CONTRIBUTING.md for details.