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

@alexnetrebskii/hive-agent

v0.24.0

Published

Minimal TypeScript agent framework inspired by Claude Code architecture

Readme

Hive Agent

Minimal TypeScript agent framework inspired by Claude Code architecture.

Features

  • Stateless design - Works in Firebase Functions, serverless environments
  • No built-in tools - You define your own tools
  • External history - Accepts/returns conversation history (for Firestore, etc.)
  • Sub-agents - Spawn specialized agents for complex tasks
  • Structured I/O - Type-safe input/output schemas for sub-agents
  • Multi-provider - Claude and OpenAI support, easily extensible
  • Interactive - Built-in __ask_user__ tool for clarifying questions
  • Progress tracking - Todo lists and real-time progress callbacks
  • Execution tracing - Full hierarchy tracking with cost breakdown
  • Prompt caching - Claude prompt caching for cost reduction

Installation

pnpm add @alexnetrebskii/hive-agent

Quick Start

import { Hive, ClaudeProvider } from '@alexnetrebskii/hive-agent'

const provider = new ClaudeProvider({
  apiKey: process.env.ANTHROPIC_API_KEY
})

const agent = new Hive({
  systemPrompt: 'You are a helpful assistant.',
  tools: [],
  llm: provider
})

const result = await agent.run('Hello!')
console.log(result.response)

Defining Tools

import type { Tool } from '@alexnetrebskii/hive-agent'

const weatherTool: Tool = {
  name: 'get_weather',
  description: 'Get current weather for a city',
  parameters: {
    type: 'object',
    properties: {
      city: { type: 'string', description: 'City name' }
    },
    required: ['city']
  },
  execute: async ({ city }) => {
    const weather = await fetchWeather(city)
    return { success: true, data: weather }
  }
}

const agent = new Hive({
  systemPrompt: 'You help users check weather.',
  tools: [weatherTool],
  llm: provider
})

Sub-Agents

Spawn specialized agents for complex tasks:

import type { SubAgentConfig } from '@alexnetrebskii/hive-agent'

const researchAgent: SubAgentConfig = {
  name: 'researcher',
  description: 'Research topics in depth using web search',
  systemPrompt: 'You research topics thoroughly and summarize findings.',
  tools: [webSearchTool, readUrlTool]
}

const agent = new Hive({
  systemPrompt: 'You help users with various tasks.',
  tools: [calculatorTool],
  agents: [researchAgent],
  llm: provider
})

// Agent can now use __task__ tool to delegate to researcher
const result = await agent.run('Research the latest AI developments')

Per-Agent Providers

Each sub-agent can use different models or providers:

import { ClaudeProvider, OpenAIProvider } from '@alexnetrebskii/hive-agent'

const claudeProvider = new ClaudeProvider({ apiKey: '...' })
const openaiProvider = new OpenAIProvider({ apiKey: '...', model: 'gpt-4o' })

const fastAgent: SubAgentConfig = {
  name: 'fast_helper',
  description: 'Quick tasks using GPT-4o',
  systemPrompt: '...',
  tools: [...],
  llm: openaiProvider,  // Uses OpenAI instead of parent's Claude
  maxIterations: 5
}

const agent = new Hive({
  systemPrompt: '...',
  tools: [...],
  agents: [fastAgent],
  llm: claudeProvider  // Main agent uses Claude
})

Structured Sub-Agents

Define input/output schemas for type-safe, cost-efficient sub-agent communication:

import type { SubAgentConfig } from '@alexnetrebskii/hive-agent'

const nutritionAgent: SubAgentConfig = {
  name: 'nutrition_counter',
  description: 'Log food and calculate nutrition values',

  // Input schema - parent provides structured parameters
  inputSchema: {
    type: 'object',
    properties: {
      food: { type: 'string', description: 'Food item to log' },
      portionGrams: { type: 'number', description: 'Portion size in grams' },
      meal: { type: 'string', description: 'Meal type: breakfast, lunch, dinner, snack' }
    },
    required: ['food', 'portionGrams', 'meal']
  },

  // Output schema - sub-agent returns structured data via __output__ tool
  outputSchema: {
    type: 'object',
    properties: {
      logged: { type: 'boolean', description: 'Whether food was logged' },
      calories: { type: 'number', description: 'Total calories' },
      protein: { type: 'number', description: 'Protein in grams' }
    },
    required: ['logged', 'calories']
  },

  systemPrompt: `You log food nutrition. Use __output__ to return results.`,
  tools: [searchFoodTool, logMealTool]
}

const agent = new Hive({
  systemPrompt: 'You are a nutrition consultant.',
  tools: [],
  agents: [nutritionAgent],
  llm: provider
})

// Main agent calls sub-agent with structured input:
// __task__({ agent: "nutrition_counter", food: "pasta", portionGrams: 250, meal: "lunch" })
//
// Sub-agent returns structured output:
// { summary: "Logged 250g pasta...", data: { logged: true, calories: 350, protein: 12 } }

Execution Tracing

Track the full execution hierarchy with cost breakdown:

import { Hive, ClaudeProvider, ConsoleTraceProvider } from '@alexnetrebskii/hive-agent'

const agent = new Hive({
  systemPrompt: '...',
  tools: [...],
  agents: [...],
  llm: new ClaudeProvider({ apiKey: '...' }),
  trace: new ConsoleTraceProvider({ showCosts: true }),
  agentName: 'my_agent'
})

const result = await agent.run('Do something complex')

// ConsoleTraceProvider outputs execution tree to console:
// [TRACE START] trace_abc123 - my_agent
// [AGENT START] my_agent
// [LLM] claude:claude-sonnet-4-20250514 - 1250 in / 89 out - $0.0042
// [TOOL] search_food (125ms)
// [AGENT START] nutrition_counter
// [LLM] claude:claude-3-haiku-20240307 - 800 in / 45 out - $0.0003
// [TOOL] log_meal (52ms)
// [AGENT END] nutrition_counter - complete
// [AGENT END] my_agent - complete
// [TRACE END] trace_abc123 - 2.3s - $0.0045

Custom Trace Provider

Implement TraceProvider for custom logging (database, observability platforms):

import type { TraceProvider, Trace, AgentSpan, LLMCallEvent, ToolCallEvent } from '@alexnetrebskii/hive-agent'

class DatadogTraceProvider implements TraceProvider {
  onTraceStart(trace: Trace): void {
    // Start a Datadog trace
  }

  onTraceEnd(trace: Trace): void {
    // End trace, record total cost
    datadogClient.gauge('agent.cost', trace.totalCost)
  }

  onAgentStart(span: AgentSpan, trace: Trace): void {
    // Start agent span
  }

  onAgentEnd(span: AgentSpan, trace: Trace): void {
    // End agent span with status
  }

  onLLMCall(event: LLMCallEvent, span: AgentSpan, trace: Trace): void {
    // Record LLM call metrics
    datadogClient.increment('agent.llm_calls', { model: event.modelId })
  }

  onToolCall(event: ToolCallEvent, span: AgentSpan, trace: Trace): void {
    // Record tool call metrics
    datadogClient.histogram('agent.tool_duration', event.durationMs)
  }
}

Accessing Trace Data

The trace is available in the result for programmatic access:

const result = await agent.run(message)

if (result.trace) {
  console.log(`Total cost: $${result.trace.totalCost.toFixed(4)}`)
  console.log(`Duration: ${result.trace.durationMs}ms`)

  // Walk the execution tree
  function printSpan(span: AgentSpan, depth = 0) {
    const indent = '  '.repeat(depth)
    console.log(`${indent}${span.agentName}: ${span.events.length} events`)
    for (const child of span.children) {
      printSpan(child, depth + 1)
    }
  }
  printSpan(result.trace.rootSpan)
}

Usage by Model

Track token usage broken down by provider and model:

const result = await agent.run(message)

// Aggregated usage by model (includes sub-agents)
if (result.usageByModel) {
  for (const [modelId, usage] of Object.entries(result.usageByModel)) {
    console.log(`${modelId}:`)
    console.log(`  ${usage.inputTokens} input / ${usage.outputTokens} output`)
    console.log(`  ${usage.calls} API calls`)
    if (usage.cacheReadInputTokens) {
      console.log(`  ${usage.cacheReadInputTokens} tokens from cache`)
    }
  }
}
// Output:
// claude:claude-sonnet-4-20250514:
//   2500 input / 180 output
//   2 API calls
// claude:claude-3-haiku-20240307:
//   800 input / 45 output
//   1 API calls
//   650 tokens from cache

Conversation History

Automatic (with Repository Provider)

Pass a conversationId and Hive automatically loads/saves history:

import { Hive, ClaudeProvider, MemoryRepository } from '@alexnetrebskii/hive-agent'

const agent = new Hive({
  systemPrompt: '...',
  tools: [...],
  llm: new ClaudeProvider({ apiKey: '...' }),
  repository: new MemoryRepository()  // Or your custom provider
})

// Hive automatically loads and saves history using conversationId
const result = await agent.run(userMessage, {
  conversationId: 'user-123-chat-456'  // Identity for the conversation
})

// Next message continues the conversation automatically
const result2 = await agent.run(nextMessage, {
  conversationId: 'user-123-chat-456'
})

Custom Repository Provider

Implement RepositoryProvider for your database:

import type { RepositoryProvider, Message } from '@alexnetrebskii/hive-agent'

class FirestoreRepository implements RepositoryProvider {
  constructor(private db: Firestore) {}

  async getHistory(conversationId: string): Promise<Message[]> {
    const doc = await this.db.collection('chats').doc(conversationId).get()
    return doc.exists ? doc.data()?.messages || [] : []
  }

  async saveHistory(conversationId: string, messages: Message[]): Promise<void> {
    await this.db.collection('chats').doc(conversationId).set({ messages })
  }
}

const agent = new Hive({
  systemPrompt: '...',
  tools: [...],
  llm: provider,
  repository: new FirestoreRepository(db)
})

Redis Repository Example

import type { RepositoryProvider, Message } from '@alexnetrebskii/hive-agent'
import { Redis } from 'ioredis'

class RedisRepository implements RepositoryProvider {
  constructor(private redis: Redis, private ttlSeconds = 86400) {}

  async getHistory(conversationId: string): Promise<Message[]> {
    const data = await this.redis.get(`chat:${conversationId}`)
    return data ? JSON.parse(data) : []
  }

  async saveHistory(conversationId: string, messages: Message[]): Promise<void> {
    await this.redis.setex(
      `chat:${conversationId}`,
      this.ttlSeconds,
      JSON.stringify(messages)
    )
  }
}

PostgreSQL Repository Example

import type { RepositoryProvider, Message } from '@alexnetrebskii/hive-agent'
import { Pool } from 'pg'

class PostgresRepository implements RepositoryProvider {
  constructor(private pool: Pool) {}

  async getHistory(conversationId: string): Promise<Message[]> {
    const result = await this.pool.query(
      'SELECT messages FROM conversations WHERE id = $1',
      [conversationId]
    )
    return result.rows[0]?.messages || []
  }

  async saveHistory(conversationId: string, messages: Message[]): Promise<void> {
    await this.pool.query(
      `INSERT INTO conversations (id, messages, updated_at)
       VALUES ($1, $2, NOW())
       ON CONFLICT (id) DO UPDATE SET messages = $2, updated_at = NOW()`,
      [conversationId, JSON.stringify(messages)]
    )
  }
}

Manual History Management

Alternatively, manage history yourself:

// Load history from database
const history = await db.collection('chats').doc(chatId).get()

// Run agent with history
const result = await agent.run(userMessage, {
  history: history.data()?.messages || []
})

// Save updated history
await db.collection('chats').doc(chatId).set({
  messages: result.history
})

Interactive Questions

Agent can pause to ask clarifying questions:

const result = await agent.run('Create a database schema')

if (result.status === 'needs_input') {
  // Show questions to user
  for (const q of result.pendingQuestion!.questions) {
    console.log(q.question)
    console.log(q.options)
  }

  // Save state and wait for user response
  // When user responds, run again with the same history
  const answers = await getUserInput()
  const continued = await agent.run(answers, { history: result.history })
}

Interruption & Cancellation

Stop a running agent when user clicks "Stop" or sends a new message:

Using AbortController (in-memory)

const controller = new AbortController()

// Start agent
const resultPromise = agent.run(message, {
  conversationId,
  signal: controller.signal
})

// User clicks "Stop" button
controller.abort()

const result = await resultPromise
if (result.status === 'interrupted') {
  console.log(`Stopped after ${result.interrupted?.iterationsCompleted} iterations`)
  // result.history contains partial work
}

Using Firestore (for Telegram bots)

// Start task and store reference
const taskRef = db.collection('tasks').doc(taskId)
await taskRef.set({ status: 'running', chatId })

const result = await agent.run(message, {
  conversationId: chatId,
  shouldContinue: async () => {
    const doc = await taskRef.get()
    return doc.data()?.status === 'running'
  }
})

// Handle result
if (result.status === 'interrupted') {
  // User stopped or sent new message
  await sendMessage(chatId, 'Task stopped')
} else {
  await sendMessage(chatId, result.response)
}

// --- In another handler (when user clicks Stop or sends new message) ---
await taskRef.update({ status: 'stopped' })

Continuing Partial Work

When interrupted, result.history contains the work done so far:

const result = await agent.run(message, { signal })

if (result.status === 'interrupted') {
  // Option 1: Discard partial work, start fresh
  const fresh = await agent.run(newMessage, { conversationId })

  // Option 2: Continue from where we left off
  const continued = await agent.run(newMessage, {
    history: result.history  // Include partial work
  })
}

Progress Callbacks

Get real-time feedback during execution:

import { ConsoleLogger } from '@alexnetrebskii/hive-agent'

const logger = {
  ...new ConsoleLogger({ level: 'info' }),
  onProgress: (update) => {
    // update.type: 'thinking' | 'tool_start' | 'tool_end' | 'sub_agent_start' | 'sub_agent_end'
    console.log(`${update.type}: ${update.message}`)
  }
}

const agent = new Hive({
  systemPrompt: '...',
  tools: [...],
  llm: provider,
  logger
})

Prompt Caching (Claude)

Reduce costs by up to 90% with Claude's prompt caching. Cached tokens are billed at 1/10th the price of regular input tokens.

import { ClaudeProvider } from '@alexnetrebskii/hive-agent'

const provider = new ClaudeProvider({
  apiKey: process.env.ANTHROPIC_API_KEY,
  cache: true  // Enable caching for system prompt, tools, and history
})

const agent = new Hive({
  systemPrompt: '...',
  tools: [...],
  llm: provider
})

const result = await agent.run(message)

// Check cache usage
if (result.usage) {
  console.log(`Cache write: ${result.usage.cacheCreationInputTokens || 0} tokens`)
  console.log(`Cache read: ${result.usage.cacheReadInputTokens || 0} tokens`)
}

How It Works

  • First request: Tokens are written to cache (cacheCreationInputTokens)
  • Subsequent requests: Tokens are read from cache (cacheReadInputTokens) at 1/10th cost
  • Cache TTL: 5 minutes (automatically extended on each hit)

Cache breakpoints are automatically placed at optimal positions (system prompt, tools, last user message).

Configuration

interface HiveConfig {
  systemPrompt: string
  tools: Tool[]
  agents?: SubAgentConfig[]

  llm: LLMProvider
  logger?: LogProvider
  repository?: RepositoryProvider

  maxIterations?: number        // Default: 50
  maxContextTokens?: number     // Default: 100000
  contextStrategy?: 'truncate_old' | 'summarize' | 'error'

  thinkingMode?: 'none' | 'enabled'
  thinkingBudget?: number

  review?: ReviewConfig

  // Tracing
  trace?: TraceProvider         // Enable execution tracing
  agentName?: string            // Name for root agent span (default: 'agent')
  modelPricing?: Record<string, ModelPricing>  // Custom pricing overrides
}

interface SubAgentConfig {
  name: string
  description: string
  systemPrompt: string
  tools: Tool[]
  model?: string                // Override model for this agent
  llm?: LLMProvider             // Override provider for this agent
  maxIterations?: number        // Override max iterations
  inputSchema?: JSONSchema      // Structured input parameters
  outputSchema?: JSONSchema     // Structured output data
}

Providers

Claude (Anthropic)

import { ClaudeProvider } from '@alexnetrebskii/hive-agent'

const provider = new ClaudeProvider({
  apiKey: process.env.ANTHROPIC_API_KEY,
  model: 'claude-sonnet-4-20250514',  // Default
  maxTokens: 8192,
  cache: true                         // Enable prompt caching
})

OpenAI

import { OpenAIProvider } from '@alexnetrebskii/hive-agent'

const provider = new OpenAIProvider({
  apiKey: process.env.OPENAI_API_KEY,
  model: 'gpt-4o',  // Default
  maxTokens: 4096,
  baseURL: 'https://api.openai.com/v1'  // Optional, for proxies
})

API Reference

AgentResult

interface AgentResult {
  response: string              // Final text response
  history: Message[]            // Full conversation history
  toolCalls: ToolCallLog[]      // Log of all tool invocations
  thinking?: string[]           // Thinking blocks (if enabled)
  todos?: TodoItem[]            // Current todo list
  pendingQuestion?: PendingQuestion  // If status is 'needs_input'
  status: 'complete' | 'needs_input' | 'interrupted'
  interrupted?: {
    reason: 'aborted' | 'stopped' | 'max_iterations'
    iterationsCompleted: number
  }
  usage?: {
    totalInputTokens: number
    totalOutputTokens: number
    cacheCreationInputTokens?: number
    cacheReadInputTokens?: number
  }
  usageByModel?: Record<string, {  // Usage by provider:model
    inputTokens: number
    outputTokens: number
    cacheCreationInputTokens?: number
    cacheReadInputTokens?: number
    calls: number
  }>
  trace?: Trace                 // Execution trace (if TraceProvider configured)
}

Tool

interface Tool {
  name: string
  description: string
  parameters: JSONSchema
  execute: (params: Record<string, unknown>, context: ToolContext) => Promise<ToolResult>
}

interface ToolResult {
  success: boolean
  data?: unknown
  error?: string
}

interface ToolContext {
  remainingTokens: number
  conversationId?: string
  userId?: string
  metadata?: Record<string, unknown>
}

RepositoryProvider

interface RepositoryProvider {
  // Required: Load conversation history
  getHistory(conversationId: string): Promise<Message[]>

  // Required: Save conversation history
  saveHistory(conversationId: string, messages: Message[]): Promise<void>

  // Optional: Custom state storage
  getState?(conversationId: string): Promise<Record<string, unknown> | null>
  saveState?(conversationId: string, state: Record<string, unknown>): Promise<void>

  // Optional: Caching layer
  getCached?(key: string): Promise<unknown | null>
  setCached?(key: string, value: unknown, ttlMs?: number): Promise<void>
}

License

MIT