@salama/agentic2
v1.1.0
Published
AI agent framework with multi-phase orchestration, tool execution, and provider adapters
Maintainers
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
findcommand (conflicts with Windows, suggests ripgrep) - Warns on unbounded searches:
grep -r,rgwithout 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:
- Partition OR execute, not both — Don't observe what you delegate
- Match depth to user's request — Surface, medium, or deep analysis
- Provide minimum context — Location + type only
- Spawn in parallel — All independent children in same iteration
- 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}.jsonfrom 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
scratchpadUpdatefrom 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 f003Evidence 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 identifierenabled- Enable/disable flagdescription- Usage instructionsparameters- 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 findcommand (conflicts with Windows)
Performance Warnings
grep -rwithout-mor| headrgwithout--max-count,--max-depth, or| head
🪟 Windows Support
Git Bash Detection
- Checks
AGENTIC_GIT_BASH_PATHenv var - Derives from
where gitoutput - Checks common paths:
C:\Program Files\Git\usr\bin\bash.exeC:\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
// 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
- Safety First - Security checks, validation, timeouts
- Windows Support - Git Bash detection, path conversion
- Performance - Warnings for inefficient operations, evidence curation
- Atomicity - File edits and state saves use temp file + rename
- Simplicity - No external dependencies, pure Node.js
- Hook-Based Architecture - Complete decoupling of agent lifecycle
- Session Persistence - Resume from any iteration, complete audit trail
- 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
