npm package: @salama/agentic2
Description: AI agent framework with multi-phase orchestration, tool execution, and provider adapters
Author: Acabes
Latest version: 1.1.0

# 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

```bash
# 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.

```javascript
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.

```javascript
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.

```javascript
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:**
```javascript
// 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](docs/spawn-agent-reference.md) 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

```javascript
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:**
```javascript
const state = await stateManager.initialize({
  sessionId: 'sess_123',
  userPrompt: 'Analyze project structure',
  workingDirectory: '/path/to/project',
  config: agentConfig
});
```

**Resume from Iteration:**
```javascript
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:

```javascript
// 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:

```javascript
// 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:

```javascript
{
  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`:

```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

```javascript
{
  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/file` → `C:\path\to\file`
- Windows → POSIX: `C:\path\to\file` → `C:/path/to/file`

## 📝 Examples

### Search and Read Files

```javascript
// 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

```javascript
// 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:

```javascript
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