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

agentloop

v3.0.0

Published

> **A TypeScript Framework for Building Tool-Using AI Agents**

Readme

AgentLoop

A TypeScript Framework for Building Tool-Using AI Agents

AgentLoop is a TypeScript framework that enables developers to build AI agents capable of executing complex tool chains. With support for 10+ AI providers, JavaScript-based tool calling, and error handling, AgentLoop abstracts away the complexity of multi-step AI workflows while maintaining full type safety and extensibility.

Note: AgentLoop uses JavaScript-based tool calling instead of traditional function calling APIs. AI models generate JavaScript code that calls tools, providing more flexibility and control over execution flow.

TypeScript Node.js License: ISC npm

What's New in v2.0.0

  • Dynamic Re-Planning System: Replaced static task lists with dynamic progress analysis for better task completion
  • Enhanced Security: Completely removed eval execution mode - SES is now the only execution mode for maximum security
  • Intelligent Stagnation Prevention: Advanced detection and recovery from repetitive actions
  • Linearized Data Structures: Simplified and flattened data types for better performance and developer experience
  • XML References for Large Content: Uses XML literal blocks to avoid string escaping issues when LLMs work with large content
  • Architecture Cleanup: Removed outdated execution modes and streamlined the codebase

🚀 Key Features

Multi-Provider AI Support

  • 10+ AI Providers: OpenAI, Google (Gemini), Anthropic (Claude), Mistral, Cohere, Groq, Fireworks, DeepSeek, Perplexity, Azure
  • Unified Interface: Switch between providers without code changes
  • Provider-Specific Optimizations: Automatic handling of rate limits, context windows, and capabilities

Secure Code Execution

  • Maximum Security: SES (Secure EcmaScript) is the only execution mode - no unsafe alternatives
  • Compartmentalized: All AI-generated code runs in isolated SES compartments
  • Cross-Platform: Works in Node.js and browsers with zero configuration
  • Zero Dependencies: SES included out of the box, no additional installation needed

Innovative Tool Calling

  • JavaScript-Based Tools: AI writes JavaScript functions for tool execution
  • Type-Safe Validation: Zod schemas ensure runtime type safety
  • XML Literal Blocks: Reference large content without string escaping issues
// Clean JavaScript without escaping:
writeFile("path", LiteralLoader.load("file_content_123"));
<literals>
  <literal id="file_content_123">
const data = "hello
world";
console.log(data);
  </literal>
</literals>
  • Additional Features: Dependency management, parallel/sequential execution

Error Handling & Monitoring

  • Stateless Architecture: Horizontally scalable, no internal state storage
  • Error Handling: Multiple error types with automatic retry logic
  • Dynamic Re-Planning: AI analyzes progress and adapts strategy instead of following rigid task lists
  • Smart Stagnation Prevention: Detects repetitive actions and guides AI toward task completion
  • Lifecycle Hooks: Monitor and customize agent execution with progress updates

Developer Experience

  • Full TypeScript Support: Complete type safety from tools to responses
  • Extensible Architecture: Custom providers, templates, and tool formats
  • Configurable: Fine-tune behavior for different use cases
  • Error Recovery: Built-in error handling and retry mechanisms

Installation

npm install agentloop

That's it! SES (Secure EcmaScript) is included out of the box for secure JavaScript execution.

Quick Start

Basic Agent Setup

import { AgentLoop, FormatMode } from 'agentloop';
import { DefaultAIProvider } from 'agentloop/providers';

class MyAgent extends AgentLoop {
  protected systemPrompt = "You are a helpful assistant with file management capabilities.";
  
  constructor() {
    super(new DefaultAIProvider({
      service: 'openai',
      apiKey: process.env.OPENAI_API_KEY,
      model: 'gpt-4'
    }), {
      formatMode: FormatMode.LITERAL_JS,
      maxIterations: 10
    });
    
    this.setupTools();
  }
  
  private setupTools() {
    // Define your tools with Zod validation
    this.defineTool(z => ({
      name: 'read_file',
      description: 'Read contents of a file',
      argsSchema: z.object({
        filepath: z.string().describe('Path to the file')
      }),
      handler: async ({ args }) => {
        const content = await fs.readFile(args.filepath, 'utf8');
        return { content, success: true };
      }
    }));
  }
}

Running the Agent

const agent = new MyAgent();

// Manage conversation history as array
const conversationHistory: Array<{role: 'user' | 'agent', message: string}> = [];

// Push user message first
conversationHistory.push({ role: 'user', message: "Read the package.json file and tell me about the project" });

const result = await agent.run({
  userPrompt: "Read the package.json file and tell me about the project",
  ...(conversationHistory.length > 1 && {
    context: {
      "Old Conversation History": conversationHistory
        .slice(0, -1) // Exclude current user message
        .map(entry => `${entry.role}: ${entry.message}`)
        .join('\n')
    }
  })
});

// Push agent response after receiving it
if (result.agentResponse) {
  conversationHistory.push({
    role: 'agent',
    message: String((result.agentResponse.args as Record<string, unknown>)?.value) || ""
  });
}

console.log(result.agentResponse?.args);

📖 API Reference

run(input: AgentRunInput): Promise<AgentRunOutput>

The main method to execute the agent. This is a stateless operation that processes a single turn of conversation.

Parameters (AgentRunInput)

| Property | Type | Required | Description | |----------|------|----------|-------------| | userPrompt | string | ✅ | The user's message or instruction to the agent | | context | Record<string, unknown> | ❌ | Context data including conversation history (pass history as context["Conversation History"]) | | completionOptions | Record<string, unknown> | ❌ | Optional AI provider completion options (temperature, max_tokens, etc.) |

Return Value (AgentRunOutput)

| Property | Type | Description | |----------|------|-------------| | interactionHistory | Interaction[] | Complete interaction history including the new turn. Persist this for the next run | | agentResponse | AgentResponse? | The agent's final response (only present if agent called the final_tool) |

Interaction Types

An Interaction can be one of:

  • UserPrompt - User's input message with message field
  • ToolCallReport - Results from tool execution(s) with linearized ToolCall[]
  • AgentResponse - Agent's final response with args field containing the response data

Data Structure - Linearized & Simplified

AgentLoop uses a linearized data structure for better type safety and easier access:

// ✅ NEW: Direct field access
interface ToolCall {
  taskId: string;
  type: "tool_call";
  timestamp: string;
  toolName: string;    // Direct access (was: result.context.toolName)
  success: boolean;    // Direct access (was: result.context.success)  
  error?: string;      // Direct access (was: result.context.error)
  args: unknown;       // All tool data (was: result.context.*)
}

interface UserPrompt {
  taskId: string;
  type: "user_prompt";
  timestamp: string;
  message: string;     // Direct access (was: prompt.context)
}

interface AgentResponse {
  taskId: string;
  type: "assistant";
  timestamp: string;
  args: unknown;       // Response data (was: response.context)
  error?: string;
  tokenUsage?: TokenUsage;
}

Benefits: No more nested .context properties - everything is top-level and type-safe.

Example Usage

// Manage conversation history as array
const conversationHistory: Array<{role: 'user' | 'agent', message: string}> = [];

// Push user message first
conversationHistory.push({ role: 'user', message: "Read the file config.json" });

const result1 = await agent.run({
  userPrompt: "Read the file config.json",
  ...(conversationHistory.length > 1 && {
    context: {
      "Conversation History": conversationHistory
        .slice(0, -1) // Exclude current user message
        .map(entry => `${entry.role}: ${entry.message}`)
        .join('\n')
    }
  })
});

// Push agent response after receiving it
if (result1.agentResponse) {
  conversationHistory.push({
    role: 'agent',
    message: String((result1.agentResponse.args as Record<string, unknown>)?.value) || ""
  });
}

// Continue conversation
// Push user message first
conversationHistory.push({ role: 'user', message: "Now analyze the configuration" });

const result2 = await agent.run({
  userPrompt: "Now analyze the configuration",
  ...(conversationHistory.length > 1 && {
    context: {
      "Conversation History": conversationHistory
        .slice(0, -1) // Exclude current user message
        .map(entry => `${entry.role}: ${entry.message}`)
        .join('\n'),
      "priority": "high"
    }
  }),
  completionOptions: { temperature: 0.3 }
});

// Push agent response after receiving it
if (result2.agentResponse) {
  conversationHistory.push({
    role: 'agent',
    message: String((result2.agentResponse.args as Record<string, unknown>)?.value) || ""
  });
}

// Final response from agent (if any)
if (result2.agentResponse) {
  console.log("Agent says:", result2.agentResponse.args);
}

Stateless Design & Conversation Management

AgentLoop is completely stateless - it doesn't store any conversation history internally. The new context-based approach gives you complete control over conversation history:

Key Changes in v2.0.0:

  • No more prevInteractionHistory: Replaced with flexible context-based approach
  • Full Control: You decide how to format and structure conversation history
  • Custom Formats: Support any history format that works for your use case
  • Scalable: Easy to persist to databases, session storage, or distributed systems

How it Works:

  1. Pass conversation history in context["Conversation History"] as a formatted string
  2. Build and maintain the history string between turns using your preferred format
  3. Handle conversation state in your application with full control over structure

Example of Different History Formats:

// Array format (recommended)
const conversationHistory: Array<{role: 'user' | 'agent', message: string}> = [
  { role: 'user', message: 'Hello' },
  { role: 'agent', message: 'Hi there!' },
  { role: 'user', message: "What's 2+2?" },
  { role: 'agent', message: 'The answer is 4' }
];

// Alternative: Simple string format
let historyString = "User: Hello\nAgent: Hi there!\nUser: What's 2+2?\nAgent: The answer is 4\n";

// Alternative: Custom structured format
let structuredHistory = `
=== Conversation Context ===
Previous Questions: 2
Last Topic: Mathematics
User Preferences: Detailed explanations

=== History ===
[Turn 1] User: Hello
[Turn 1] Agent: Hi there!
[Turn 2] User: What's 2+2?
[Turn 2] Agent: The answer is 4
`;

// Use the array format with conditional context
// Push user message first
conversationHistory.push({ role: 'user', message: "Continue our conversation" });

const result = await agent.run({
  userPrompt: "Continue our conversation",
  ...(conversationHistory.length > 1 && {
    context: {
      "Conversation History": conversationHistory
        .slice(0, -1) // Exclude current user message
        .map(entry => `${entry.role}: ${entry.message}`)
        .join('\n'),
      "User Preferences": "concise",
      "Session ID": "abc-123"
    }
  })
});

// Push agent response after receiving it
if (result.agentResponse) {
  conversationHistory.push({
    role: 'agent',
    message: String((result.agentResponse.args as Record<string, unknown>)?.value) || ""
  });
}

This design makes AgentLoop incredibly flexible and easy to integrate with any storage or state management system.

JavaScript Execution Security

AgentLoop uses SES (Secure EcmaScript) as the only execution mode for maximum security:

const agent = new MyAgent(provider); // SES is always used - no configuration needed

SES (Secure EcmaScript) - Only Mode

  • 🔒 Maximum Security: All code runs in isolated compartments
  • 🛡️ Memory Safe: No access to global objects or sensitive APIs
  • 📦 Zero Configuration: SES included out of the box
  • 🌐 Cross-Platform: Works identically in Node.js and browsers
  • 🚫 No Unsafe Alternatives: Eliminates any possibility of insecure execution

Why SES-Only?

  • Security First: No compromise on security - every execution is safe
  • Predictable: Consistent security model across all environments
  • Zero-Risk: Eliminates entire class of security vulnerabilities
  • Production Ready: Battle-tested secure execution without complexity

🛠️ Core Concepts

Dynamic Re-Planning System

AgentLoop uses an advanced dynamic re-planning system that eliminates infinite loops and improves task completion:

How It Works

// The AI uses self-reasoning to track progress and plan actions
{
  goal: "List files in the directory",
  goal_status: "pending",  // or "success"/"failed"
  pending_action: "Executing list_files tool to get directory contents",
  progress_summary: "1. Received user request to list directory files"
}

Key Benefits

  • No More Static Task Lists: AI dynamically analyzes what's been accomplished
  • Intelligent Decision Making: AI decides next actions based on current progress
  • Automatic Task Completion: AI recognizes when enough data is gathered
  • Stagnation Prevention: Detects and prevents repetitive actions

Progress Tracking

The system displays progress in a structured format:

# CURRENT TASK PROGRESS

## EXECUTION HISTORY  
### Turn 1: Listing files in current directory
  - Tool `list_files` -> SUCCESS
    Output: {"files": ["package.json", "src/", "README.md"]}

## PROGRESS SUMMARY
1. Listed directory contents and found 3 items including package.json and src folder
Currently executing list_files tool to get directory contents (success)

## NEXT TASK
Analyze the progress above and determine the best action to achieve the user's goal.

Tools with Dependencies

this.defineTool(z => ({
  name: 'analyze_code',
  description: 'Analyze code quality',
  dependencies: ['read_file'],  // Depends on read_file tool
  argsSchema: z.object({
    filepath: z.string()
  }),
  handler: async ({ args, dependencies }) => {
    const fileContent = dependencies.read_file.result.content;
    // Analyze the content...
  }
}));

JavaScript Tool Calling Format

AI writes JavaScript functions for structured tool execution:

import { LiteralLoader, toolCalls } from './utils';
import { toolSchemas } from './toolSchemas';

function callTools() {
  toolCalls.push(
    toolSchemas.read_file.parse({
      filepath: "package.json"
    })
  );
  
  toolCalls.push(
    toolSchemas.final_tool.parse({
      value: LiteralLoader("analysis-result")
    })
  );
  
  return toolCalls;
}

Lifecycle Hooks

const agent = new MyAgent({
  hooks: {
    onToolCallStart: async (call) => {
      console.log(`Executing: ${call.toolName}`);
    },
    onProgressUpdate: async (progressSummary, pendingAction, goal, iteration) => {
      console.log(`Turn ${iteration}: ${goal}`);
      console.log(`Progress: ${progressSummary}`);
      console.log(`Current: ${pendingAction}`);
    },
    onStagnationDetected: async (repeatedAction, iteration) => {
      console.log(`Stagnation detected at turn ${iteration}: ${repeatedAction}`);
    },
    onError: async (error) => {
      logger.error('Agent error:', error);
    }
  }
});

🏗️ Architecture

Process Flow

AgentLoop Process Flow

The diagram above illustrates AgentLoop's iterative execution process: receiving user input, building prompts with context and tool schemas, generating and executing JavaScript code through AI providers, validating and executing tools, and feeding results back into the loop until task completion.

Key Components

  • AgentLoop: Main execution engine with stateless design
  • AIProvider: Abstraction layer for multiple AI services
  • FormatHandler: Manages tool calling formats and execution
  • PromptManager: Handles prompt templates and context
  • ToolSystem: Type-safe tool definition and execution
  • ErrorHandler: Error management and recovery

📋 Examples

File Management Agent

import { AgentLoop } from 'agentloop';
import { DefaultAIProvider } from 'agentloop/providers';
import { z } from 'zod';
import * as fs from 'fs/promises';
import * as path from 'path';

class FileManagerAgent extends AgentLoop {
  protected systemPrompt = "You are a helpful file management assistant. Help users read, write, and organize files safely.";

  constructor(private basePath: string = process.cwd()) {
    super(new DefaultAIProvider({
      service: 'openai',
      apiKey: process.env.OPENAI_API_KEY!,
      model: 'gpt-4'
    }));

    this.setupTools();
  }

  private setupTools() {
    this.defineTool(z => ({
      name: 'read_file',
      description: 'Read the contents of a file',
      argsSchema: z.object({
        filepath: z.string().describe('Path to the file to read')
      }),
      handler: async ({ args }) => {
        try {
          const fullPath = path.resolve(this.basePath, args.filepath);
          const content = await fs.readFile(fullPath, 'utf8');
          const stats = await fs.stat(fullPath);
          return { 
            content, 
            filepath: args.filepath,
            size: stats.size,
            success: true 
          };
        } catch (error) {
          return { 
            error: error.message, 
            success: false 
          };
        }
      }
    }));

    this.defineTool(z => ({
      name: 'write_file',
      description: 'Write content to a file',
      argsSchema: z.object({
        filepath: z.string().describe('Path to the file to write'),
        content: z.string().describe('Content to write to the file')
      }),
      handler: async ({ args }) => {
        try {
          const fullPath = path.resolve(this.basePath, args.filepath);
          await fs.mkdir(path.dirname(fullPath), { recursive: true });
          await fs.writeFile(fullPath, args.content);
          
          return { 
            filepath: args.filepath,
            bytesWritten: Buffer.byteLength(args.content, 'utf8'),
            success: true 
          };
        } catch (error) {
          return { 
            error: error.message, 
            success: false 
          };
        }
      }
    }));

    this.defineTool(z => ({
      name: 'list_directory',
      description: 'List files and directories in a path',
      argsSchema: z.object({
        dirpath: z.string().optional().describe('Directory path (default: current directory)')
      }),
      handler: async ({ args }) => {
        try {
          const fullPath = path.resolve(this.basePath, args.dirpath || '.');
          const entries = await fs.readdir(fullPath, { withFileTypes: true });
          
          const items = entries.map(entry => ({
            name: entry.name,
            type: entry.isDirectory() ? 'directory' : 'file'
          }));

          return { 
            path: args.dirpath || '.',
            items,
            count: items.length,
            success: true 
          };
        } catch (error) {
          return { 
            error: error.message, 
            success: false 
          };
        }
      }
    }));

    this.defineTool(z => ({
      name: 'final_response',
      description: 'Provide the final response to the user',
      argsSchema: z.object({
        message: z.string().describe('Final message to the user')
      }),
      handler: async ({ args }) => ({ message: args.message, final: true })
    }));
  }
}

// Usage
const agent = new FileManagerAgent('/project/path');
const result = await agent.run({
  userPrompt: "List all files in the src directory, read package.json, and create a project-info.md file with a summary of the project structure and dependencies",
  context: {
    "Conversation History": ""
  }
});

Development Scripts

# Build and run examples
npm run build:examples
node dist/console.js

# Run code editor demo
npm run demo:codeeditor

# Development with watch mode
npm run dev:watch

⚙️ Configuration

Agent Options

interface AgentLoopOptions {
  maxIterations?: number;                    // Max reasoning iterations (default: 100)
  parallelExecution?: boolean;              // Run tools in parallel (default: true)
  toolTimeoutMs?: number;                   // Tool execution timeout (default: 30s)
  // JavaScript execution is always secure with SES - no configuration needed
  stagnationTerminationThreshold?: number;  // Prevent infinite loops (default: 3)
  maxInteractionHistoryCharsLimit?: number; // Memory management (default: 100k)
  sleepBetweenIterationsMs?: number;        // Rate limiting (default: 2s)
  hooks?: AgentLifecycleHooks;              // Event handlers including progress updates
}

interface AgentLifecycleHooks {
  onIterationStart?: (iteration: number) => Promise<void>;
  onToolCallStart?: (call: PendingToolCall) => Promise<void>;
  onToolCallEnd?: (result: ToolCall) => Promise<void>;
  onProgressUpdate?: (progressSummary: string | null, pendingAction: string | null, goal: string | null, iteration: number) => Promise<void>;
  onStagnationDetected?: (repeatedAction: string, iteration: number) => Promise<void>;
  onAgentFinalResponse?: (result: AgentResponse) => Promise<void>;
  onError?: (error: AgentError) => Promise<void>;
}

AI Provider Configuration

// OpenAI
new DefaultAIProvider({
  service: 'openai',
  apiKey: process.env.OPENAI_API_KEY,
  model: 'gpt-4o',
  temperature: 0.1
})

// Google Gemini
new DefaultAIProvider({
  service: 'google', 
  apiKey: process.env.GEMINI_API_KEY,
  model: 'gemini-2.0-flash'
})

// Anthropic Claude
new DefaultAIProvider({
  service: 'anthropic',
  apiKey: process.env.ANTHROPIC_API_KEY,
  model: 'claude-3-5-sonnet-20241022'
})

Security

AgentLoop uses SES (Secure EcmaScript) as the only execution mode for maximum security:

  • Compartmentalized Execution: All AI-generated code runs in isolated SES compartments
  • Zero Configuration: SES is included and enabled by default
  • Cross-Platform: Works identically in Node.js and browsers
  • No Unsafe Alternatives: Removed eval and other insecure execution modes
// Security is automatic - no configuration needed
const agent = new MyAgent(aiProvider, {
  maxIterations: 10,
  parallelExecution: true
  // SES security is always enabled
});

🔧 Additional Features

Custom AI Providers

class CustomProvider implements AIProvider {
  async completion(prompt: string): Promise<AICompletionResponse> {
    // Implement your custom AI integration
  }
}

Custom Tool Formats

class CustomFormatHandler implements FormatHandler {
  async parseResponse(response: string, tools: Tool[]): Promise<PendingToolCall[]> {
    // Parse custom tool calling format
  }
}

Custom Prompt Templates

class CustomTemplate implements BasePromptTemplate {
  buildPrompt(params: BuildPromptParams): string {
    // Build custom prompt structure
  }
}

📊 Error Handling

AgentLoop provides error handling with automatic recovery:

enum AgentErrorType {
  TOOL_NOT_FOUND = 'tool_not_found',
  INVALID_RESPONSE = 'invalid_response', 
  TOOL_EXECUTION_ERROR = 'tool_execution_error',
  STAGNATION_ERROR = 'stagnation_error',
  // ... 15 more error types
}

Retry Configuration

{
  toolExecutionRetryAttempts: 5,    // Retry tool failures
  connectionRetryAttempts: 3,       // Retry AI provider failures
  retryDelay: 1000,                 // Delay between retries
  failureHandlingMode: 'fail_fast'  // How to handle failures
}

🧪 Testing

# Run all tests
npm test

# Run specific test suites
npm run test:unit
npm run test:integration  
npm run test:gemini

# Test coverage
npm run test:coverage

📚 Documentation

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

  1. Fork the repository
  2. Create your feature branch: git checkout -b feature/amazing-feature
  3. Commit your changes: git commit -m 'Add amazing feature'
  4. Push to the branch: git push origin feature/amazing-feature
  5. Open a Pull Request

📄 License

This project is licensed under the ISC License - see the LICENSE file for details.

🙏 Acknowledgments

  • Built with AI-SDK for unified AI provider access
  • Powered by Zod for runtime type safety
  • Secure execution via SES (Secure ECMAScript)
  • Inspired by the agent frameworks community

AgentLoop - Build AI Agents That Get Things Done