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

@agentiny/core

v0.3.0

Published

Lightweight TypeScript agent framework - core package

Readme

@agentiny/core

Build autonomous reactive agents with TypeScript - A lightweight, zero-dependency framework for creating intelligent systems using trigger-condition-action flows. Perfect for automation, workflow orchestration, state machines, and reactive application logic.

What is @agentiny/core?

@agentiny/core is a TypeScript agent framework that enables you to build reactive, event-driven systems with minimal overhead. It provides a clean, composable API for defining triggers (when to act), conditions (validation logic), and actions (what to do) - all with full async/await support.

Use Cases

  • Workflow Automation - Automate multi-step processes with conditional logic
  • Event-Driven Systems - Build responsive applications that react to state changes and events
  • State Machines - Implement complex state management with clear trigger-condition-action patterns
  • Reactive Agents - Create autonomous agents that monitor conditions and take actions
  • Business Logic Orchestration - Coordinate complex business rules and workflows
  • Real-time Monitoring - Build monitoring systems that respond to state changes

Features

  • Zero Dependencies - Minimal footprint with no runtime dependencies
  • Type-Safe - Full TypeScript support with strict mode enabled
  • Event-Driven Architecture - Reactive trigger system with state management
  • Three Trigger Types - Repeating state-based triggers, one-time triggers, and event-based triggers
  • Async/Await Support - Full async support for all checks, conditions, and actions
  • Cascading Action Support - Wait for all cascading trigger-action flows to complete with settle()
  • Small Bundle Size - Less than 5KB minified + gzipped
  • Performance Optimized - Smart state change tracking minimizes unnecessary evaluations
  • Well-Tested - 152+ comprehensive tests with vitest

Quick Navigation

Installation

npm install @agentiny/core

Or with other package managers:

# pnpm
pnpm add @agentiny/core

# yarn
yarn add @agentiny/core

# bun
bun add @agentiny/core

Quick Start

Basic Example

import { Agent } from '@agentiny/core';

// Create an agent with initial state
const agent = new Agent({
  initialState: { count: 0, message: '' },
});

// Add a trigger using the low-level API
agent.addTrigger({
  id: 'counter-trigger',
  check: (state) => state.count > 5,
  conditions: [(state) => state.count % 2 === 0],
  actions: [
    (state) => {
      state.message = `Count is ${state.count}`;
      console.log(state.message);
    },
  ],
  repeat: true,
});

// Start the agent
await agent.start();

// Update state - triggers will be evaluated
agent.setState({ count: 6 });

// Stop the agent
await agent.stop();

Using Convenience Methods

The library provides sugar methods for common patterns:

when() - State-based repeating trigger

const agent = new Agent({ initialState: { temperature: 0 } });

// Repeat when temperature exceeds 30
const triggerId = agent.when(
  (state) => state.temperature > 30,
  [(state) => console.log('Temperature alarm:', state.temperature)],
);

await agent.start();
agent.setState({ temperature: 35 });

once() - One-time trigger

const agent = new Agent({ initialState: { initialized: false } });

// Fire only once when initialized
agent.once((state) => state.initialized === true, [(state) => console.log('System ready!')]);

await agent.start();
agent.setState({ initialized: true });

on() - Event-based trigger

const agent = new Agent({ initialState: {} });

// Listen for specific events
agent.on('user-login', [(state) => console.log('User logged in!')]);

await agent.start();
agent.emitEvent('user-login');

API Reference

Agent

The main class for creating and managing agents.

Constructor

new Agent<TState>(config?: AgentConfig<TState>)

Options:

  • initialState?: TState - Initial state object
  • triggers?: Trigger<TState>[] - Initial triggers to register
  • onError?: (error: Error) => void - Error handler callback

Methods

State Management
  • getState(): TState - Get current state
  • setState(newState: TState): void - Update state
  • subscribe(callback: (state: TState) => void): () => void - Subscribe to state changes
Trigger Management
  • addTrigger(trigger: Trigger<TState>): void - Register a trigger
  • getTrigger(id: string): Trigger<TState> | undefined - Get trigger by ID
  • getAllTriggers(): Trigger<TState>[] - Get all registered triggers
  • removeTrigger(id: string): void - Remove a trigger
  • clearTriggers(): void - Remove all triggers
Agent Lifecycle
  • start(): Promise<void> - Start the agent
  • stop(): Promise<void> - Stop the agent
  • isRunning(): boolean - Check if agent is running
  • getStatus(): AgentStatus - Get current status
  • settle(quietCycles?: number, timeout?: number): Promise<void> - Wait for all cascading actions to complete
Convenience Methods
  • when(check, [actions] | [conditions, actions]): string - Create a repeating state-based trigger
  • once(check, [actions] | [conditions, actions]): string - Create a one-time state-based trigger
  • on(event, [actions] | [conditions, actions], repeat?): string - Create an event-based trigger
  • emitEvent(event: string): void - Emit an event
Event Trigger Management
  • removeEventTrigger(event: string, triggerId: string): void - Remove a specific event-based trigger
  • removeAllEventTriggersForEvent(event: string): void - Remove all triggers listening to an event
  • getEventTriggersForEvent(event: string): Trigger<TState>[] - Get all triggers for a specific event
  • getEventTriggers(): Map<string, Trigger<TState>[]> - Get all event-based triggers organized by event

State

Reactive state container with subscription support.

const state = new State<TState>(initialValue);

state.get(); // Get current value
state.set(newValue); // Update value
state.subscribe(callback); // Subscribe to changes

Types

TriggerFn

type TriggerFn<TState> = (state: TState) => boolean | Promise<boolean>;

Function that checks if a trigger should fire.

ConditionFn

type ConditionFn<TState> = (state: TState) => boolean | Promise<boolean>;

Function that checks if a condition is met.

ActionFn

type ActionFn<TState> = (state: TState) => void | Promise<void>;

Function that performs an action.

Trigger

interface Trigger<TState> {
  id: string;
  check: TriggerFn<TState>;
  conditions?: readonly ConditionFn<TState>[];
  actions: readonly ActionFn<TState>[];
  repeat?: boolean;
  delay?: number;
}

Complete trigger definition.

AgentConfig

interface AgentConfig<TState> {
  initialState?: TState;
  triggers?: Trigger<TState>[];
  onError?: (error: Error) => void;
  idleTimeout?: number;
}

Configuration for creating an agent.

Options:

  • initialState - Initial state object
  • triggers - Initial triggers to register
  • onError - Error handler callback
  • idleTimeout - Idle timeout in milliseconds between trigger checks when no settle() is pending (default: 100ms). Lower values = more responsive but higher CPU usage.

AgentStatus

enum AgentStatus {
  Idle = 'idle',
  Running = 'running',
  Stopped = 'stopped',
}

Agent execution status.

AgentError

Custom error class for agent-related errors.

class AgentError extends Error {
  code: string;
  context?: unknown;
}

settle() - Waiting for Cascading Actions

The settle() method allows you to wait for all cascading trigger-action flows to complete. This is essential when one action's state changes trigger additional actions, creating a chain of effects.

Why Use settle()?

When you update the agent's state, it might trigger multiple cascading actions:

setState(state1)
  ↓
Trigger 1 fires, executes action → setState(state2)
  ↓
Trigger 2 fires, executes action → setState(state3)
  ↓
Trigger 3 fires, executes action
  ↓
No more state changes (settled)

Without settle(), you won't know when all cascading effects are complete. settle() solves this elegantly by detecting when the agent has been quiet for N consecutive polling cycles.

Method Signature

settle(quietCycles = 2, timeout = 10000): Promise<void>

Parameters:

  • quietCycles (optional, default: 2) - Number of consecutive polling cycles with no state changes required before settling (each cycle is ~10ms, so default is ~20ms)
  • timeout (optional, default: 10000) - Maximum time to wait in milliseconds before rejecting with a timeout error

Returns: Promise that resolves when the agent is quiet, or rejects on timeout/error

Throws:

  • AgentError with code 'AGENT_NOT_RUNNING' if agent is not running
  • AgentError with code 'INVALID_ARGUMENT' if quietCycles <= 0
  • AgentError with code 'SETTLE_TIMEOUT' if timeout is exceeded
  • AgentError with code 'AGENT_STOPPED' if agent stops while waiting

Examples

Basic Usage

const agent = new Agent({ initialState: { count: 1 } });

agent.when(
  (state) => state.count < 3,
  [
    (state) => {
      state.count++;
    },
  ],
);

await agent.start();
agent.setState({ count: 1 });

// Wait for all cascading actions (cascades: 1 → 2 → 3)
await agent.settle();

console.log(agent.getState().count); // 3
await agent.stop();

Multi-Step Workflow

interface WorkflowState {
  stage: 'init' | 'processing' | 'validating' | 'complete';
}

const agent = new Agent<WorkflowState>({
  initialState: { stage: 'init' },
});

// Stage 1: init → processing
agent.when(
  (state) => state.stage === 'init',
  [
    (state) => {
      state.stage = 'processing';
      console.log('Processing...');
    },
  ],
);

// Stage 2: processing → validating
agent.when(
  (state) => state.stage === 'processing',
  [
    (state) => {
      state.stage = 'validating';
      console.log('Validating...');
    },
  ],
);

// Stage 3: validating → complete
agent.when(
  (state) => state.stage === 'validating',
  [
    (state) => {
      state.stage = 'complete';
      console.log('Complete!');
    },
  ],
);

await agent.start();
agent.setState({ stage: 'init' });

// Wait for all 3 cascading actions to complete
await agent.settle();
console.log(agent.getState()); // { stage: 'complete' }

Custom Quiet Cycles

// Require 5 quiet cycles (~50ms) instead of default 2
await agent.settle(5);

// Custom timeout (5 seconds instead of 10)
await agent.settle(2, 5000);

Error Handling

try {
  // Try to settle with impossible requirements
  await agent.settle(100, 50); // 100 cycles in 50ms
} catch (error) {
  if (error instanceof AgentError) {
    if (error.code === 'SETTLE_TIMEOUT') {
      console.log('Timed out waiting for agent to settle');
      console.log('Quiet cycles so far:', error.context.currentQuietCycles);
    }
  }
}

Examples

Real-time Counter

import { Agent } from '@agentiny/core';

interface CounterState {
  count: number;
  total: number;
}

const agent = new Agent<CounterState>({
  initialState: { count: 0, total: 0 },
});

// Increment total every time count increases
agent.when(
  (state) => state.count > state.total,
  [
    (state) => {
      state.total = state.count;
    },
  ],
);

// Log when count reaches 10
agent.once((state) => state.count >= 10, [(state) => console.log('Reached 10!')]);

await agent.start();

for (let i = 1; i <= 15; i++) {
  agent.setState({ count: i, total: 0 });
  await new Promise((r) => setTimeout(r, 100));
}

await agent.stop();

Event-Driven User Flow

import { Agent } from '@agentiny/core';

interface AppState {
  user: string | null;
  message: string;
}

const app = new Agent<AppState>({
  initialState: { user: null, message: '' },
});

// Listen for login event
app.on('login', [
  (state) => {
    state.message = `Welcome ${state.user}!`;
    console.log(state.message);
  },
]);

// Listen for logout event with condition
app.on(
  'logout',
  [(state) => state.user !== null], // condition
  [
    (state) => {
      state.message = `Goodbye ${state.user}!`;
      state.user = null;
      console.log(state.message);
    },
  ],
);

await app.start();

app.setState({ user: 'Alice' });
app.emitEvent('login');

app.emitEvent('logout');

Conditional Execution with Delay

import { Agent } from '@agentiny/core';

const agent = new Agent({ initialState: { critical: false } });

agent.addTrigger({
  id: 'alert-trigger',
  check: (state) => state.critical === true,
  conditions: [(state) => state.critical],
  actions: [(state) => console.log('CRITICAL ALERT!')],
  delay: 5000, // Wait 5 seconds before executing
  repeat: false, // Execute only once
});

await agent.start();
agent.setState({ critical: true });
// Will log "CRITICAL ALERT!" after 5 seconds, then remove trigger

Cascading Actions with settle()

import { Agent } from '@agentiny/core';

interface ProcessingState {
  stage: 'input' | 'processing' | 'validating' | 'output';
  data: string;
}

const agent = new Agent<ProcessingState>({
  initialState: { stage: 'input', data: '' },
});

// Input → Processing
agent.when(
  (state) => state.stage === 'input' && state.data.length > 0,
  [
    (state) => {
      console.log('Processing data...');
      state.stage = 'processing';
    },
  ],
);

// Processing → Validating
agent.when(
  (state) => state.stage === 'processing',
  [
    (state) => {
      console.log('Validating data...');
      state.stage = 'validating';
    },
  ],
);

// Validating → Output
agent.when(
  (state) => state.stage === 'validating',
  [
    (state) => {
      console.log('Data ready!');
      state.stage = 'output';
    },
  ],
);

await agent.start();
agent.setState({ stage: 'input', data: 'important data' });

// Wait for all cascading actions to complete
await agent.settle();

console.log(agent.getState()); // { stage: 'output', data: 'important data' }
await agent.stop();

Performance & Optimization

@agentiny/core is built with performance in mind:

Event-Driven Trigger Evaluation

The agent uses an event-driven architecture for optimal performance. Triggers are evaluated immediately when:

  • State changes via setState() - triggers evaluated instantly
  • Events are emitted via emitEvent() - triggers evaluated instantly
  • settle() is called - switches to fast 10ms polling for quiet cycle detection

When the agent is idle (no state changes or events), it uses a configurable idle timeout (default: 100ms) instead of continuous polling. This dramatically reduces CPU usage for idle agents.

Configuring Idle Timeout

You can tune the idle timeout based on your use case:

// Low latency application - check more frequently when idle
const agent = new Agent({
  initialState: {},
  idleTimeout: 50, // 50ms between checks when idle
});

// Background processing - save CPU with longer idle timeout
const agent = new Agent({
  initialState: {},
  idleTimeout: 500, // 500ms between checks when idle
});

Note: The idleTimeout only affects idle periods. State changes and events always trigger immediate evaluation, regardless of this setting.

Smart State Change Tracking

Triggers are only checked when state actually changes, not on every cycle. This means:

  • Idle agents consume minimal CPU (configurable via idleTimeout)
  • State changes trigger immediate evaluation (0ms latency)
  • The settle() function uses fast 10ms polling for accurate quiet cycle detection

Memory Efficiency

  • Automatic cleanup of subscriptions and triggers
  • No memory leaks from dangling event listeners
  • Proper error handling prevents memory accumulation

Scalability

Tested with 100+ triggers with no performance degradation. The event-driven architecture ensures consistent behavior regardless of trigger count.

Testing

The package includes 152+ comprehensive tests covering:

  • All agent lifecycle methods
  • State management and subscriptions
  • All trigger types (state-based, one-time, event-based)
  • Async operations and error handling
  • Edge cases and complex scenarios

Run tests with:

npm run test          # Watch mode
npm run test:run      # Single run

Error Handling

Errors during action execution are caught and passed to the error handler:

const agent = new Agent({
  initialState: { count: 0 },
  onError: (error) => {
    console.error('Agent error:', error.message);
  },
});

agent.addTrigger({
  id: 'failing-trigger',
  check: (state) => state.count > 0,
  actions: [
    (state) => {
      throw new Error('Something went wrong!');
    },
  ],
  repeat: true,
});

await agent.start();
agent.setState({ count: 1 }); // Error will be logged

Best Practices

1. Handle Errors Gracefully

Always provide an error handler to catch and log errors:

const agent = new Agent({
  initialState: {},
  onError: (error) => {
    console.error('Agent error:', error.message);
    // Send to logging service
  },
});

2. Clean Up Event Triggers

Remove event triggers when no longer needed:

const id = agent.on('event', [action]);
// Later...
agent.removeEventTrigger('event', id);

// Or remove all for an event:
agent.removeAllEventTriggersForEvent('event');

3. Use Type-Safe State

Define your state interface for better TypeScript support:

interface AppState {
  user: User | null;
  loading: boolean;
  errors: string[];
}

const agent = new Agent<AppState>({
  initialState: { user: null, loading: false, errors: [] },
});

4. Leverage Conditions for Complex Logic

Use conditions to add validation before actions:

agent.when(
  (state) => state.user !== null,
  [
    (state) => state.user.role === 'admin', // condition
  ],
  [(state) => console.log('Admin action allowed')],
);

5. Use One-Time Triggers for Initialization

Perfect for one-time setup tasks:

agent.once((state) => state.initialized === true, [(state) => console.log('System initialized')]);

6. Use settle() for Cascading Actions

When you have triggers that cascade (actions that trigger more actions), use settle() to wait for all effects to complete:

// Trigger a chain of actions
agent.setState({ stage: 'start' });

// Wait for all cascading actions to finish
await agent.settle();

// Now safe to proceed knowing all effects are done
console.log('All cascading actions complete!');

Performance Characteristics

  • Lightweight: Less than 5KB minified + gzipped
  • No dependencies: Pure TypeScript with zero runtime dependencies
  • Event-driven: Immediate trigger evaluation on state changes (0ms latency)
  • CPU-efficient: Configurable idle timeout reduces CPU usage when idle
  • Memory-safe: Proper cleanup of triggers and subscriptions
  • Scalable: Handles 100+ triggers efficiently

TypeScript Support

Full TypeScript support with strict mode enabled:

// Fully typed state
interface AppState {
  user: { id: string; name: string } | null;
  isLoading: boolean;
  errors: string[];
}

const agent = new Agent<AppState>({
  initialState: {
    user: null,
    isLoading: false,
    errors: [],
  },
});

// Type-safe access in all callbacks
agent.when(
  (state) => state.user !== null,
  [
    (state) => {
      console.log(state.user.name); // Fully typed!
    },
  ],
);

AI Agent Integration

While @agentiny/core provides the foundational framework, it integrates seamlessly with AI providers through companion packages:

These adapters allow you to build intelligent agents that leverage AI models as part of their trigger-condition-action flows.

Ecosystem

The agentiny framework consists of:

  • @agentiny/core - Core agent framework (this package)
  • @agentiny/utils - Utility wrappers (retry, timeout, validation)
  • @agentiny/openai - OpenAI integration
  • @agentiny/anthropic - Anthropic SDK integration
  • @agentiny/gemini - Google Gemini integration

Contributing

Contributions are welcome! Please feel free to submit issues and pull requests.

License

MIT

See Also