agent-loop.md

bot0 Agent Loop - Implementation Guide

Overview

The agent loop is the core execution engine of the bot0 daemon. It receives user input, calls Claude, handles tool calls, and loops until task completion. This document describes the implemented architecture and how to use it.

Key principle: Receive input → Call LLM → Execute tools → Loop until done.


Architecture

┌─────────────────────────────────────────────────────────────────────────────┐
│                         BOT0 AGENT LOOP                                      │
│                                                                             │
│   User Input ──► Agent Loop ──► Claude API ──► Tool Execution ──► Response │
│                      │              │              │                        │
│                      └──────────────┴──────────────┘                        │
│                              (loops until done)                             │
│                                                                             │
│   ┌─────────────────────────────────────────────────────────────────────┐   │
│   │                         COMPONENTS                                   │   │
│   │                                                                      │   │
│   │   1. Tool Registry    - Register, list, execute tools               │   │
│   │   2. Basic Tools      - shell, read_file, write_file, list_dir, glob│   │
│   │   3. Bot0Agent        - Core loop calling Claude + handling tools   │   │
│   │   4. HTTP Server      - Test endpoint for development               │   │
│   │                                                                      │   │
│   └─────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

File Structure

packages/daemon/
├── src/
│   ├── index.ts              # Entry point, exports
│   ├── agent/
│   │   ├── index.ts          # Exports
│   │   ├── loop.ts           # Bot0Agent class - the main agent loop
│   │   └── types.ts          # AgentTask, RunOptions, ProgressUpdate types
│   ├── tools/
│   │   ├── index.ts          # ToolRegistry class + exports
│   │   ├── types.ts          # Tool interface, ToolDefinition
│   │   ├── shell.ts          # Execute shell commands
│   │   ├── read-file.ts      # Read file contents
│   │   ├── write-file.ts     # Write file contents
│   │   ├── list-dir.ts       # List directory contents
│   │   └── glob.ts           # Search files by pattern
│   └── server/
│       └── http.ts           # Simple HTTP server for testing
└── package.json

Core Components

1. Tool Registry

The ToolRegistry manages tool registration and execution:

typescript
import { ToolRegistry, shellTool, readFileTool } from '@bot0/daemon'; const registry = new ToolRegistry(); registry.register(shellTool); registry.register(readFileTool); // Get tool definitions for Claude API const definitions = registry.getDefinitions(); // Execute a tool const result = await registry.execute('shell', { command: 'ls -la' });

Methods:

MethodDescription
register(tool)Register a new tool
get(name)Get a tool by name
getNames()List all registered tool names
getDefinitions()Get Claude API tool definitions
execute(name, input)Execute a tool
has(name)Check if tool exists

2. Tool Interface

All tools implement the Tool interface:

typescript
interface Tool { name: string; description: string; inputSchema: Anthropic.Tool['input_schema']; execute: (input: Record<string, unknown>) => Promise<string>; }

Built-in Tools:

ToolNameDescription
shellToolshellExecute shell commands
readFileToolread_fileRead file contents
writeFileToolwrite_fileWrite content to files
listDirToollist_dirList directory contents
globToolglobSearch files by pattern

3. Bot0Agent

The main agent class that runs the loop:

typescript
import { Bot0Agent, ToolRegistry } from '@bot0/daemon'; const agent = new Bot0Agent(registry, { model: 'claude-sonnet-4-20250514', maxTokens: 8096, }); const result = await agent.run( { id: '123', prompt: 'List the files in the current directory', source: 'local', }, { onProgress: (update) => console.log(update), maxIterations: 50, } );

Agent Loop Flow:

┌─────────────────────────────────────────────────────────────────────────────┐
│                            AGENT LOOP FLOW                                   │
│                                                                             │
│   1. User sends prompt                                                      │
│      │                                                                      │
│      ▼                                                                      │
│   2. Build messages array with system prompt + user message                 │
│      │                                                                      │
│      ▼                                                                      │
│   3. Call Claude API with tools                                             │
│      │                                                                      │
│      ├─► stop_reason: "end_turn" ─► Return response (DONE)                 │
│      │                                                                      │
│      └─► stop_reason: "tool_use" ─► Execute tools                          │
│                                        │                                    │
│                                        ▼                                    │
│                                     4. Add tool results to messages         │
│                                        │                                    │
│                                        └─► Loop back to step 3              │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

4. Types

AgentTask:

typescript
interface AgentTask { id: string; // Unique task identifier prompt: string; // User prompt/instruction source: 'local' | 'remote'; context?: string; // Optional context }

RunOptions:

typescript
interface RunOptions { onProgress?: (update: ProgressUpdate) => void; maxIterations?: number; // Default: 50 systemPrompt?: string; // Override system prompt }

ProgressUpdate:

typescript
interface ProgressUpdate { type: 'thinking' | 'assistant' | 'tool_call' | 'tool_result' | 'error' | 'done'; content?: string; // For assistant responses tool?: string; // Tool name input?: unknown; // Tool input result?: string; // Tool result error?: string; // Error message }

AgentResponse:

typescript
interface AgentResponse { success: boolean; response?: Anthropic.Message; text?: string; // Extracted text response error?: string; iterations?: number; // Loop count }

HTTP Test Server

A simple HTTP server for testing the agent:

typescript
import { Bot0Agent, ToolRegistry, startTestServer } from '@bot0/daemon'; const registry = new ToolRegistry(); // ... register tools const agent = new Bot0Agent(registry); startTestServer(agent, 7070);

Endpoints:

MethodPathDescription
GET/healthHealth check + list tools
GET/toolsGet tool definitions
POST/taskRun a task

POST /task Request:

json
{ "prompt": "List files in the current directory", "context": "Optional context string" }

POST /task Response:

json
{ "success": true, "text": "Here are the files...", "iterations": 2 }

Usage

Development Mode

Start the agent server for testing:

bash
cd packages/daemon export ANTHROPIC_API_KEY=sk-... pnpm dev:agent

Output:

bot0 agent server running on http://localhost:7070

Endpoints:
  GET  /health - Health check
  GET  /tools  - List available tools
  POST /task   - Run a task (JSON body: { "prompt": "..." })

Test Commands

bash
# Health check curl http://localhost:7070/health # List available tools curl http://localhost:7070/tools # Run a simple task curl -X POST http://localhost:7070/task \ -H "Content-Type: application/json" \ -d '{"prompt": "List the files in the current directory"}' # Multi-tool task curl -X POST http://localhost:7070/task \ -H "Content-Type: application/json" \ -d '{"prompt": "Create a file called hello.txt with Hello World, then read it back"}'

Creating Custom Tools

Implement the Tool interface:

typescript
import type { Tool } from '@bot0/daemon'; export const myCustomTool: Tool = { name: 'my_tool', description: 'Description shown to Claude', inputSchema: { type: 'object', properties: { param1: { type: 'string', description: 'First parameter', }, }, required: ['param1'], }, execute: async (input) => { const param1 = input.param1 as string; // Do something return `Result: ${param1}`; }, }; // Register it registry.register(myCustomTool);

System Prompt

The default system prompt:

You are bot0, an intelligent AI assistant with access to tools that let you
interact with the local system.

You help users accomplish tasks by using the available tools. Be proactive
in solving problems and always explain what you're doing.

Guidelines:
- Use tools to gather information before making decisions
- If a command fails, analyze the error and try an alternative approach
- Be concise but thorough in your explanations
- Always verify your work when possible

Override with RunOptions.systemPrompt.


Error Handling

The agent handles errors gracefully:

  1. Tool execution errors - Returned to Claude as error results, allowing it to retry or take alternative action

  2. API errors - Caught and returned in AgentResponse.error

  3. Max iterations - Prevents infinite loops (default: 50)

typescript
const result = await agent.run(task); if (!result.success) { console.error('Task failed:', result.error); console.log('Completed iterations:', result.iterations); }

Programmatic Usage

Import and use in your own code:

typescript
import { Bot0Agent, ToolRegistry, shellTool, readFileTool, writeFileTool, listDirTool, globTool, } from '@bot0/daemon'; // Create registry with all tools const registry = new ToolRegistry(); registry.register(shellTool); registry.register(readFileTool); registry.register(writeFileTool); registry.register(listDirTool); registry.register(globTool); // Create agent const agent = new Bot0Agent(registry); // Run a task const result = await agent.run({ id: Date.now().toString(), prompt: 'Find all TypeScript files and count the lines of code', source: 'local', }); console.log('Success:', result.success); console.log('Response:', result.text);

Future Additions

The following features are planned but not yet implemented:

  • Hooks system - preToolUse, postToolUse callbacks
  • Context compaction - Summarize long conversations
  • Streaming responses - Real-time token streaming
  • ctx0 integration - Context protocol support
  • Error recovery - Automatic retries with backoff
  • Rate limiting - Prevent API abuse

References