The Tool System

How agents extend their capabilities. The interface between thinking and doing.

Evidence source: Amp Code v0.0.1769212917 (45+ tools, comprehensive permission system)


The Core Insight

An LLM alone can only generate text. Tools give it agency - the ability to:

  • Read files and understand codebases
  • Make edits that actually persist
  • Run commands and see results
  • Search the web for documentation

Without tools, you have a chatbot. With tools, you have an agent.


Tool Architecture

The Three-Part Structure

Every tool in Amp has three components:

┌─────────────────────────────────────────────────────────────────┐
│                         TOOL DEFINITION                          │
│                                                                  │
│   ┌─────────────┐   ┌─────────────┐   ┌─────────────────────┐  │
│   │    SPEC     │   │   FUNCTION  │   │     EXECUTION       │  │
│   │             │   │             │   │      PROFILE        │  │
│   │ • name      │   │ • fn()      │   │                     │  │
│   │ • schema    │   │             │   │ • serial?           │  │
│   │ • description│  │             │   │ • resourceKeys()    │  │
│   │ • aliases   │   │             │   │                     │  │
│   └─────────────┘   └─────────────┘   └─────────────────────┘  │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Tool Definition Interface

interface ToolDefinition {
  spec: {
    name: string;                     // Tool name constant
    description: string;              // Full description for the LLM
    inputSchema: JSONSchema;          // What arguments it accepts
    source: "builtin" | "mcp" | "toolbox";
    meta?: {
      disableTimeout?: boolean;       // Don't auto-timeout
    };
    executionProfile?: {
      serial?: boolean;               // Must run alone
      resourceKeys?: (args: any) => ResourceKey[];
    };
    aliases?: ToolAlias[];            // Alternative schemas for modes
  };
  fn: ToolFunction;                   // The actual implementation
  preprocessArgs?: (args: any, env: ToolEnv) => any;
}

interface ResourceKey {
  key: string;        // File path or resource identifier
  mode: "read" | "write";
}

Why this structure?

  • spec tells the LLM what the tool does
  • fn executes it
  • executionProfile enables parallel execution by declaring conflicts

Tool Sources

Three Origins

Prefix Source Example
(none) Built-in native Read, Grep, Bash
mcp__ MCP server tools mcp__github__search_code
tb__ Toolbox custom tb__custom_script

Tool Name Resolution

Tools support aliases for backwards compatibility:

const TOOL_ALIASES = {
  // Legacy names → current names
  Read: "Read",
  read: "Read",
  read_file: "Read",
  Write: "create_file",
  write: "create_file",
  write_file: "create_file",
  Edit: "edit_file",
  edit: "edit_file",
  Delete: "delete_file",
  delete: "delete_file",
  run_terminal_command: "Bash"
};

Why aliases? Models trained on older documentation might use Write or edit. Aliases let them work without retraining.


Tool Categories

Amp organizes 45+ tools into functional categories:

File Operations

Tool Purpose Permission
Read Read files/directories Always allowed
edit_file Replace text in files Requires approval
create_file Create/overwrite files Requires approval
glob Find files by pattern Always allowed
undo_edit Revert last edit Always allowed
delete_file Delete files Requires approval

Search

Tool Purpose Model Used
Grep Regex search in files Native (ripgrep)
finder AI-powered code search Gemini 3 Flash Preview
web_search Search the web Backend API

Execution

Tool Purpose Notes
Bash Run shell commands Serial, no timeout
Check Run CI/CD checks No timeout

Subagents

Tool Purpose Model
Task Spawn subtasks Claude Opus 4.5
finder Code search Gemini 3 Flash Preview
oracle Deep reasoning GPT-5.2
librarian Multi-repo search Claude Haiku 4.5
kraken Multi-file refactor Claude Haiku 4.5

Tool Execution

The Execution Flow

Tool Request from LLM
        │
        ▼
┌───────────────────┐
│ Permission Check  │──── Denied ────▶ { status: "rejected-by-user" }
└─────────┬─────────┘
          │ Allowed
          ▼
┌───────────────────┐
│ Preprocess Args   │──── Transform args if needed
└─────────┬─────────┘
          │
          ▼
┌───────────────────┐
│ Resolve Aliases   │──── Map to actual tool name
└─────────┬─────────┘
          │
          ▼
┌───────────────────┐
│ Execute Function  │──── Run the tool
└─────────┬─────────┘
          │
          ▼
┌───────────────────┐
│ Post-Process      │──── Truncate, format result
└─────────┬─────────┘
          │
          ▼
    Tool Result

Tool Status States

type ToolStatus =
  | "in-progress"       // Currently executing
  | "done"              // Successful completion
  | "error"             // Execution failed
  | "cancelled"         // User cancelled
  | "rejected-by-user"  // Permission denied
  | "blocked-on-user";  // Waiting for approval

Result Format

Success:

{
  status: "done",
  result: string | object,
  trackFiles?: string[],  // Files modified (for tracking)
}

Error:

{
  status: "error",
  error: {
    message: string,
    errorCode?: string,
    absolutePath?: string,  // For file operations
  }
}

Permission System

Permission Classes

Always Allowed (no approval needed):

  • Read, glob, Grep, finder
  • web_search, read_web_page
  • read_thread, find_thread, task_list

Requires Approval:

  • Bash - Shell execution
  • create_file, edit_file, delete_file - File modifications

Auto-Allowed Patterns:

  • Writes to .amp/ directory
  • Common dev commands (npm, git, python, etc.)
  • Format commands

Permission Check Logic

function checkToolPermission(
  toolName: string,
  modeConfig: ModeConfig,
  toolboxConfig?: ToolboxConfig
): boolean {
  // MCP tools
  if (toolName.startsWith("mcp__")) {
    return modeConfig.allowMcp ?? false;
  }

  // Toolbox tools
  if (toolName.startsWith("tb__")) {
    if (!(modeConfig.allowToolbox ?? false)) {
      return false;
    }
    // Check subagent type restrictions
    if (modeConfig.subagentType && toolboxConfig?.subagentTypes) {
      return toolboxConfig.subagentTypes.includes("all") ||
             toolboxConfig.subagentTypes.includes(modeConfig.subagentType);
    }
    return true;
  }

  // Native tools - check inclusion in mode's tool list
  return modeConfig.includeTools.includes(toolName);
}

Execution Profiles & Batching

Why Execution Profiles?

When the LLM requests multiple tools at once:

Read file A
Read file B
Edit file A
Read file C

Can we run them in parallel? It depends on conflicts.

Resource Keys

Each tool declares what resources it touches:

// Read tool
executionProfile: {
  serial: false,
  resourceKeys: (args) => [{
    key: args.path,
    mode: "read"
  }]
}

// edit_file tool
executionProfile: {
  serial: false,
  resourceKeys: (args) => [{
    key: args.path,
    mode: "write"
  }]
}

Conflict Rules

  1. No profile = assume conflict (conservative)
  2. Serial tools = always conflict
  3. Same resource key + any write = conflict
  4. Read-read on same resource = no conflict

Batching Algorithm

def batch_tools_by_conflict(tool_uses):
    batches = []
    current_batch = []

    for tool in tool_uses:
        if conflicts_with_batch(tool, current_batch):
            if current_batch:
                batches.append(current_batch)
                current_batch = []
        current_batch.append(tool)

    if current_batch:
        batches.append(current_batch)

    return batches

Example:

Input: [Read A, Read B, Edit A, Read C]

Batch 1: [Read A, Read B]      # No conflict - parallel
Batch 2: [Edit A]               # Conflicts with Read A - new batch
Batch 3: [Read C]               # Could be in Batch 2, but after Edit A

Timeout Handling

Default Behavior

Tools timeout after 120 seconds unless meta.disableTimeout: true.

Tools with Disabled Timeout

Long-running tools that need to complete:

  • Bash - Shell commands can take time
  • Task - Subagents need to complete
  • finder, oracle, librarian - Subagent tools
  • Check - CI/CD can be slow
  • kraken - Multi-file refactoring

Timeout Behavior

// Timeout resets on progress
// Long-running tools emit progress to stay alive

{
  status: "error",
  error: {
    message: "Tool execution timed out after 120 seconds"
  }
}

Result Truncation

Large tool results must be truncated before sending to the model:

Truncation Limits

Tool Limit Notes
Read 65,536 bytes Per file
Bash 50,000 chars Output only
Grep 100 matches Total results
read_web_page 262,144 bytes Content
General 102,400 bytes All tool results

Truncation Implementation

const TRUNCATION_LIMIT = 102400;  // 100KB

function truncateResult(content: string): string {
  if (content.length > TRUNCATION_LIMIT) {
    return `${content.slice(0, TRUNCATION_LIMIT)}

[Tool result truncated: ${Math.round(content.length / 1024)}KB exceeds limit. Please refine the query.]`;
  }
  return content;
}

Tool Input Schema

JSON Schema Format

Tools declare their inputs using JSON Schema:

{
  "type": "object",
  "properties": {
    "path": {
      "type": "string",
      "description": "Absolute path to the file"
    },
    "read_range": {
      "type": "array",
      "items": { "type": "number" },
      "minItems": 2,
      "maxItems": 2,
      "description": "Line range [start, end], 1-indexed"
    }
  },
  "required": ["path"]
}

Best Practices for Schemas

  1. Required fields first - Most important args should be required
  2. Clear descriptions - The LLM reads these
  3. Sensible defaults - Don't require everything
  4. Validation - Schema validates before execution

Implementing Your Own Tools

Minimal Tool

def create_tool(name: str, description: str, schema: dict, fn: callable):
    return {
        "spec": {
            "name": name,
            "description": description,
            "inputSchema": schema,
            "source": "builtin"
        },
        "fn": fn
    }

# Example: Simple file counter
file_counter = create_tool(
    name="count_files",
    description="Count files matching a pattern",
    schema={
        "type": "object",
        "properties": {
            "pattern": {"type": "string", "description": "Glob pattern"}
        },
        "required": ["pattern"]
    },
    fn=lambda args, env: {
        "status": "done",
        "result": len(glob.glob(args["pattern"], recursive=True))
    }
)

Tool with Execution Profile

file_writer = {
    "spec": {
        "name": "write_file",
        "description": "Write content to a file",
        "inputSchema": {
            "type": "object",
            "properties": {
                "path": {"type": "string"},
                "content": {"type": "string"}
            },
            "required": ["path", "content"]
        },
        "source": "builtin",
        "executionProfile": {
            "serial": False,
            "resourceKeys": lambda args: [
                {"key": args["path"], "mode": "write"}
            ]
        }
    },
    "fn": write_file_impl
}

Implementation Checklist

Building your own tool system? Ensure:

  • Tool Registration

    • Schema validation
    • Name aliasing
    • Source tracking (builtin/mcp/toolbox)
  • Execution

    • Permission checking
    • Argument preprocessing
    • Timeout handling
    • Result truncation
  • Batching

    • Execution profiles
    • Resource key extraction
    • Conflict detection
    • Parallel execution within batches
  • Error Handling

    • Standard error format
    • Error codes for common failures
    • Graceful degradation

What's Next

Now you understand the tool system. Let's look at the specific tools you need.

05-core-tools.md - Read, edit_file, create_file, Bash, glob, Grep specifications

For reconstruction-grade detail:

04-tool-system.spec.md - Full tool definitions and schemas