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

@salama/agentic2

v1.1.0

Published

AI agent framework with multi-phase orchestration, tool execution, and provider adapters

Readme

Agentic2 - Agent Framework

Complete agent framework with essential tools and state management:

  • 4 Core Tools: bash, file-edit, file-edit-regex, spawn-agent
  • Hook-Based State Manager: Session persistence, evidence curation, windowing
  • 105 Tests Passing: Comprehensive test coverage

🚀 Quick Start

# Run tool tests
node test.js

# Run state manager tests
npm test

# Run simple agent example
node examples/simple-agent.js

📦 Tools

1. bash - Shell Command Execution

Execute shell commands with Windows/Unix support, security checks, and performance warnings.

import { executeToolCall } from './src/tools/executor.js';

const result = await executeToolCall('bash', {
  command: 'ls -la',
  timeout: 30000,  // optional, default 30s
  path: '/c/some/directory'  // optional, default current dir
});

console.log(result.stdout);
console.log(result.stderr);
console.log(result.exitCode);

Features:

  • ✅ Windows Git Bash auto-detection
  • ✅ Path conversion (POSIX ↔ Windows)
  • ✅ Security blocks (rm -rf /, fork bombs, force git ops)
  • ✅ Performance warnings (unbounded grep/rg)
  • ✅ Timeout handling with partial results
  • ✅ 5-minute max execution time

Safety:

  • Blocks dangerous commands: rm -rf /, fork bombs, mkfs, dd
  • Blocks interactive git commands: git rebase -i, git add -i
  • Blocks find command (conflicts with Windows, suggests ripgrep)
  • Warns on unbounded searches: grep -r, rg without limits

2. file-edit - Exact String Replacement

Edit files by replacing exact strings. Safer than regex for simple replacements.

const result = await executeToolCall('file-edit', {
  path: './example.txt',
  oldString: 'Hello World',
  newString: 'Hello Universe',
  replaceAll: false,  // optional, default false (first match only)
  preview: false      // optional, default false (shows changes without applying)
});

console.log(`Replaced ${result.replacements} occurrences`);

Features:

  • ✅ Atomic writes (temp file + rename)
  • ✅ Uniqueness validation (warns if multiple matches)
  • ✅ Preview mode
  • ✅ Windows path support

3. file-edit-regex - Pattern-Based Replacement

Edit files using regex patterns with capture groups.

const result = await executeToolCall('file-edit-regex', {
  path: './example.js',
  pattern: 'function (\\w+)\\(',
  replacement: 'const $1 = (',
  flags: 'g',           // optional: g, i, m, s
  preview: false,       // optional: preview changes
  maxReplacements: 1000 // optional: safety limit
});

console.log(`Replaced ${result.replacements} matches`);

Features:

  • ✅ Capture groups: $1, $2, ..., $9, $&, $`, $', $$
  • ✅ ReDoS protection (5s timeout)
  • ✅ Catastrophic backtracking detection
  • ✅ 10MB file size limit
  • ✅ Preview mode
  • ✅ Atomic writes

Safety:

  • Validates regex patterns before execution
  • Detects dangerous patterns: (a+)+, (a*)*, nested quantifiers
  • Enforces replacement limits (default 1000)
  • Timeout protection against ReDoS attacks

4. spawn-agent - Parallel Task Decomposition

Spawn independent child agents for parallel task execution using a principle-based approach.

5 Core Principles:

  1. Partition OR execute, not both — Don't observe what you delegate
  2. Match depth to user's request — Surface, medium, or deep analysis
  3. Provide minimum context — Location + type only
  4. Spawn in parallel — All independent children in same iteration
  5. Default to shallow — When user intent is ambiguous

When to Use:

  • Task partitions into independent scopes (e.g., "analyze src/, tests/, config/")
  • Children can operate in parallel without sequential dependencies
  • Read-only observation and analysis tasks

Prompt Format:

CONTEXT: [target location + project type].
TASK: [focused work matching user's depth].
DEPTH: [user's depth keyword] ([level] - [explanation]).
CONSTRAINTS: [what to skip / what NOT to do].

Example:

// User: "Analyze the codebase structure"
// AI spawns 3 children in parallel:

await executeToolCall('spawn-agent', {
  userPrompt: `CONTEXT: src/ at ./src/, Node.js project.
TASK: List files and subdirectories, note organization.
DEPTH: structure (surface - files and organization only).
CONSTRAINTS: Skip detailed content analysis.`,
  maxIterations: 20  // Scale by depth: surface(15-25), medium(30-40), deep(50-60)
});

await executeToolCall('spawn-agent', {
  userPrompt: `CONTEXT: tests/ at ./tests/, Node.js project.
TASK: List test files and subdirectories, note organization.
DEPTH: structure (surface - files and organization only).
CONSTRAINTS: Skip detailed test content analysis.`,
  maxIterations: 20
});

await executeToolCall('spawn-agent', {
  userPrompt: `CONTEXT: config/ at ./config/, Node.js project.
TASK: List configuration files, note organization.
DEPTH: structure (surface - files and organization only).
CONSTRAINTS: Skip detailed config content analysis.`,
  maxIterations: 20
});

Features:

  • ✅ Parallel execution (spawn all in same iteration)
  • ✅ Depth mapping (surface/medium/deep)
  • ✅ Independent sessions (no shared state)
  • ✅ Resource limits (10 per parent, 50 global)
  • ✅ 7-minute default timeout per child
  • ✅ Non-interactive (children can't ask user questions)
  • ✅ Depth limit = 1 (no grandchildren)

Expected Performance:

  • 75% reduction in pre-spawn investigation iterations
  • 55% reduction in total execution time for parallel tasks
  • Accurate scope matching (no unnecessary deep dives)

See spawn-agent Reference for detailed guidance, depth mapping table, decision trees, and comprehensive examples.

🧠 State Management

Hook-based state management system with session persistence, evidence curation, and reference-based windowing.

Overview

The state manager provides a complete lifecycle system for agent execution:

  • Session-based persistence - Every iteration saves complete state
  • Evidence curation - AI-driven reduction of tool outputs (50-95% token savings)
  • Reference-based windowing - Keep relevant findings, archive unreferenced ones
  • Resume capability - Continue from any previous iteration
  • Configurable request tracking - Single mode (simple) or multiple mode (flexible)

Quick Start

import { createScratchpadStateManager } from './src/state/scratchpad-state-manager.js';
import agentConfig from './config/agent-config.json' assert { type: 'json' };

// Create state manager
const stateManager = createScratchpadStateManager(agentConfig);

// Initialize new session
const state = await stateManager.initialize({
  sessionId: 'sess_001',
  userPrompt: 'Find database configuration',
  workingDirectory: process.cwd(),
  config: agentConfig
});

// Agent loop
let iteration = 1;
state.iteration = iteration;

// 1. Before AI call - inject state
const messages = [
  { role: 'system', content: stateManager.systemPromptSection },
  { role: 'user', content: 'Find database configuration' }
];
await stateManager.beforeAICall({ messages, state, iteration });

// 2. After AI response - process updates
const aiResponse = {
  scratchpadUpdate: {
    add: { confirmedFacts: ['Found config file'] },
    append: { reasoningProgress: 'Investigating... ' }
  }
};
await stateManager.afterAIResponse({ response: aiResponse, state, iteration });

// 3. After tool execution - add findings
const toolResults = [
  { tool: 'bash', command: 'cat config.yml', output: '...', timestamp: new Date().toISOString() }
];
await stateManager.afterToolExecution({ toolResults, state, iteration, sessionId: 'sess_001' });

// 4. Before next iteration - window and save
await stateManager.beforeNextIteration({ state, iteration, sessionId: 'sess_001', done: false });

// 5. Finalize when complete
await stateManager.finalize({ state, iteration, sessionId: 'sess_001' });

See examples/simple-agent.js for a complete working example.

6 Lifecycle Hooks

1. initialize({ sessionId, userPrompt?, workingDirectory, config, fromIteration? })

  • Creates session directory structure
  • New session: Creates initial state with user request
  • Resume: Loads state-{N}.json from disk
  • Returns initial state object

2. beforeAICall({ messages, state, iteration })

  • Formats current state as markdown
  • Injects state into messages array as system message
  • No return value (modifies messages in place)

3. afterAIResponse({ response, state, iteration })

  • Processes scratchpadUpdate from AI response
  • Handles user request status updates
  • Applies evidence curation to last finding
  • Returns updated state

4. afterToolExecution({ toolResults, state, iteration, sessionId })

  • Creates finding objects for each tool result
  • Saves evidence to disk (always, no thresholds)
  • Adds findings to state with metadata
  • Returns updated state

5. beforeNextIteration({ state, iteration, sessionId, done })

  • Applies reference-based windowing to findings
  • Clears dead ends if task complete
  • Saves complete state to disk: state-{iteration}.json
  • Returns updated state

6. finalize({ state, iteration, sessionId })

  • Logs completion message
  • No state changes (already saved in beforeNextIteration)

Session Management

Create New Session:

const state = await stateManager.initialize({
  sessionId: 'sess_123',
  userPrompt: 'Analyze project structure',
  workingDirectory: '/path/to/project',
  config: agentConfig
});

Resume from Iteration:

const state = await stateManager.initialize({
  sessionId: 'sess_123',
  fromIteration: 5,  // Resume from iteration 5
  workingDirectory: '/path/to/project',
  config: agentConfig
});

Session Directory Structure:

./sessions/
  sess_123/
    state-1.json          # Complete state after iteration 1
    state-2.json          # Complete state after iteration 2
    state-3.json          # Complete state after iteration 3
    findings/
      f001-full.txt       # Full evidence for finding f001
      f002-full.txt       # Full evidence for finding f002
      f003-full.txt       # Full evidence for finding f003

Evidence Curation

The AI can request curation of large tool outputs to reduce token usage:

// AI response with curation request
const aiResponse = {
  evidenceCuration: {
    mode: 'curated',
    chunks: [
      { type: 'line', start: 1, end: 10, label: 'Header section' },
      { type: 'line', start: 95, end: 100, label: 'Footer section' },
      { type: 'char', start: 500, end: 600, label: 'Error message' }
    ]
  }
};

// State manager extracts chunks and removes full content from memory
await stateManager.afterAIResponse({ response: aiResponse, state, iteration });

// Full content remains on disk at findings/f001-full.txt
// State only contains curated chunks (90%+ token reduction)

Benefits:

  • 50-95% token reduction for large outputs
  • Full content always preserved on disk
  • AI controls what's important to keep in context
  • Retrieve full content anytime with retrieve-finding tool

Reference-Based Windowing

Keeps frequently referenced findings in context longer:

// Configuration
{
  "windowing": {
    "findings": {
      "referenced": 10,    // Keep last 10 referenced findings
      "unreferenced": 3    // Keep last 3 unreferenced findings
    }
  }
}

// Findings referenced in scratchpad stay active
state.scratchpad.confirmedFacts = [
  'Database config found [f001]',
  'Security settings verified [f005]'
];

// After windowing:
// - f001, f005 kept (referenced)
// - Last 3 unreferenced kept
// - Older unreferenced archived (retrievable from disk)

Scratchpad Update Operations

The AI can update the scratchpad using 4 operations:

{
  scratchpadUpdate: {
    // Add items to arrays
    add: {
      confirmedFacts: ['New fact here'],
      currentActions: ['New action here']
    },

    // Remove items from arrays
    remove: {
      confirmedFacts: ['Old fact to remove']
    },

    // Append to strings (concatenation)
    append: {
      reasoningProgress: 'Additional reasoning. '
    },

    // Set values (overwrite)
    set: {
      scope: 'New scope',
      'userRequest.status': 'completed'  // Nested paths supported
    }
  }
}

Configuration

State manager configuration in config/agent-config.json:

{
  "stateManager": {
    "userRequests": {
      "multiple": false,  // Single mode (simple) or multiple mode (flexible)
      "statuses": ["in_progress", "needs_clarification", "completed", "failed"]
    },
    "evidenceCuration": {
      "enabled": true,
      "defaultMode": "full"  // Show full content by default
    },
    "windowing": {
      "findings": {
        "referenced": 10,     // Keep last 10 referenced findings
        "unreferenced": 3     // Keep last 3 unreferenced findings
      }
    },
    "storage": {
      "basePath": "./sessions"  // Session storage directory
    }
  }
}

Single vs Multiple Request Mode:

  • Single Mode (multiple: false): Simple mode for one task at a time

    • Displays one active request
    • AI updates with userRequest.status
    • Best for straightforward workflows
  • Multiple Mode (multiple: true): Flexible mode for concurrent tasks

    • Displays all requests with status badges
    • AI updates with updateRequest: { id, status }
    • Best for complex multi-task workflows

State Structure

{
  iteration: 3,
  scratchpad: {
    workingDirectory: '/path/to/project',

    // User requests (single or multiple mode)
    userRequests: [
      {
        id: 'req_1',
        request: 'Find database configuration',
        status: 'completed',
        createdAt: '2024-01-15T12:00:00.000Z',
        completedAt: '2024-01-15T12:05:00.000Z'
      }
    ],

    // Tool execution findings
    findings: [
      {
        id: 'f001',
        iteration: 1,
        tool: 'bash',
        command: 'cat config/database.yml',
        fullContentPath: './sessions/sess_123/findings/f001-full.txt',
        fullContentSize: 1450,
        displayMode: 'curated',
        curatedChunks: [
          { type: 'line', start: 1, end: 10, label: 'Config', content: '...' }
        ]
      }
    ],

    // Knowledge fields
    scope: 'Database configuration analysis',
    confirmedFacts: [
      'Database uses PostgreSQL [f001]',
      'Connection pooling enabled [f002]'
    ],
    currentActions: ['Analyzing security settings'],
    workingNotes: 'Need to check SSL configuration',
    deadEnds: ['Tried Redis config - not relevant'],
    tentativeThoughts: 'May need to check environment variables',
    reasoningProgress: 'Found config. Verified settings. ',
    draftResponse: ['Configuration analysis complete']
  }
}

🔧 Configuration

Tool definitions are in config/tools.json. Each tool has:

  • name - Tool identifier
  • enabled - Enable/disable flag
  • description - Usage instructions
  • parameters - Parameter definitions

🛡️ Security

Blocked Commands

  • rm -rf / (except /home, /tmp)
  • Fork bombs: :(){ : | : & }; :
  • Direct disk writes: > /dev/sda
  • Filesystem formatting: mkfs
  • Dangerous dd: dd if=
  • Force git operations: git push --force, git reset --hard HEAD~N
  • Global git config: git config --global
  • find command (conflicts with Windows)

Performance Warnings

  • grep -r without -m or | head
  • rg without --max-count, --max-depth, or | head

🪟 Windows Support

Git Bash Detection

  1. Checks AGENTIC_GIT_BASH_PATH env var
  2. Derives from where git output
  3. Checks common paths:
    • C:\Program Files\Git\usr\bin\bash.exe
    • C:\Program Files\Git\bin\bash.exe
    • %LOCALAPPDATA%\Programs\Git\usr\bin\bash.exe

Path Conversion

  • POSIX → Windows: /c/path/to/fileC:\path\to\file
  • Windows → POSIX: C:\path\to\fileC:/path/to/file

📝 Examples

Search and Read Files

// Find TypeScript files
await executeToolCall('bash', {
  command: 'rg --files -g "*.ts" | head -30'
});

// Search file contents
await executeToolCall('bash', {
  command: 'rg "function.*export" -l | head -20'
});

// Batch read multiple files
await executeToolCall('bash', {
  command: 'for f in src/*.js; do echo "=== $f ===" && head -50 "$f"; done | head -300'
});

Edit Files

// Simple replacement
await executeToolCall('file-edit', {
  path: './config.json',
  oldString: '"enabled": false',
  newString: '"enabled": true'
});

// Convert function syntax
await executeToolCall('file-edit-regex', {
  path: './utils.js',
  pattern: 'function (\\w+)\\((.*?)\\) \\{',
  replacement: 'const $1 = ($2) => {',
  flags: 'g'
});

// Preview changes first
const preview = await executeToolCall('file-edit', {
  path: './app.js',
  oldString: 'const API_URL = "localhost"',
  newString: 'const API_URL = "api.example.com"',
  preview: true
});

console.log('Before:', preview.before);
console.log('After:', preview.after);

📂 Structure

agentic2/
├── src/
│   ├── tools/
│   │   └── executor.js           # Core tool implementations
│   └── state/
│       ├── scratchpad-state-manager.js  # Main state manager (6 hooks)
│       ├── storage.js                   # Session I/O operations
│       ├── helpers.js                   # Business logic (update, window, curate)
│       └── formatters.js                # State formatting for AI
├── config/
│   ├── tools.json                # Tool definitions
│   └── agent-config.json         # State manager configuration
├── tests/
│   ├── storage.test.js           # Storage layer tests (27 tests)
│   ├── helpers.test.js           # Helper function tests (37 tests)
│   ├── formatters.test.js        # Formatter tests (21 tests)
│   ├── state-manager-integration.test.js  # Integration tests (10 tests)
│   └── e2e.test.js               # End-to-end tests (10 tests)
├── examples/
│   └── simple-agent.js           # State manager usage example
├── sessions/                     # Runtime session storage (gitignored)
├── package.json                  # Package config
├── test.js                       # Tool test suite
├── jest.config.js                # Test configuration
└── README.md                     # This file

🎯 Design Principles

  1. Safety First - Security checks, validation, timeouts
  2. Windows Support - Git Bash detection, path conversion
  3. Performance - Warnings for inefficient operations, evidence curation
  4. Atomicity - File edits and state saves use temp file + rename
  5. Simplicity - No external dependencies, pure Node.js
  6. Hook-Based Architecture - Complete decoupling of agent lifecycle
  7. Session Persistence - Resume from any iteration, complete audit trail
  8. Token Efficiency - AI-driven evidence curation (50-95% reduction)

🔄 Integration

Complete integration example with tools and state management:

import { executeToolCall } from './src/tools/executor.js';
import { createScratchpadStateManager } from './src/state/scratchpad-state-manager.js';
import toolsConfig from './config/tools.json' assert { type: 'json' };
import agentConfig from './config/agent-config.json' assert { type: 'json' };

// Initialize state manager
const stateManager = createScratchpadStateManager(agentConfig);

// Create or resume session
const state = await stateManager.initialize({
  sessionId: 'sess_001',
  userPrompt: 'Find and analyze database configuration',
  workingDirectory: process.cwd(),
  config: agentConfig
});

// Agent loop
let iteration = 1;
const maxIterations = 10;
let done = false;

while (iteration <= maxIterations && !done) {
  state.iteration = iteration;

  // 1. Prepare messages with state
  const messages = [
    { role: 'system', content: stateManager.systemPromptSection },
    { role: 'user', content: 'Find and analyze database configuration' }
  ];
  await stateManager.beforeAICall({ messages, state, iteration });

  // 2. Call AI (your implementation)
  const aiResponse = await callAI(messages);

  // 3. Process AI response
  await stateManager.afterAIResponse({
    response: aiResponse,
    state,
    iteration
  });

  // 4. Execute tools if requested
  if (aiResponse.toolCalls) {
    const toolResults = [];

    for (const toolCall of aiResponse.toolCalls) {
      const result = await executeToolCall(toolCall.name, toolCall.parameters);
      toolResults.push({
        tool: toolCall.name,
        command: toolCall.parameters.command || JSON.stringify(toolCall.parameters),
        output: result.stdout || result.output || JSON.stringify(result),
        timestamp: new Date().toISOString()
      });
    }

    // 5. Add findings to state
    await stateManager.afterToolExecution({
      toolResults,
      state,
      iteration,
      sessionId: 'sess_001'
    });
  }

  // 6. Window and save before next iteration
  await stateManager.beforeNextIteration({
    state,
    iteration,
    sessionId: 'sess_001',
    done: aiResponse.finalAnswer ? true : false
  });

  if (aiResponse.finalAnswer) {
    done = true;
  }

  iteration++;
}

// 7. Finalize session
await stateManager.finalize({
  state,
  iteration: iteration - 1,
  sessionId: 'sess_001'
});

See examples/simple-agent.js for a complete working implementation.

📄 License

MIT