In Chapter 1, the LLM could only talk. Here’s where it learns to act.

The Big Picture

In chapter 1, the LLM can only talk. In chapter 2, it can act — run commands, read files, edit files.

┌─────────────────────────────────────────────────┐
│                  AGENT LOOP Chapter 2           │
│                                                 │
│  User ──> messages[] ──> LLM API                │
│                               │                 │
│                          stop_reason?           │
│                          /         \            │
│                    end_turn       tool_use      │
│                       │               │         │
│                     Print    TOOL_HANDLERS      │
│                               handler(input)    │
│                                   │             │
│                             tool_result         │
│                                   │             │
│                          messages[] ──> LLM     │
│                          (can chain again!)     │
└─────────────────────────────────────────────────┘

New from Chapter 1: an inner loop inside the tool_use branch — because the LLM can call tools multiple times before finishing.

Three Key Concepts

1. Two Parallel Structures: TOOLS + TOOL_HANDLERS

// TOOLS → tell the LLM: "here's what you can use"
// (a schema is just a description of what inputs the tool accepts — name, types, required fields)
var tools = []map[string]any{
    {
        "name":        "bash",
        "description": "Run a shell command",
        "input_schema": map[string]any{
            "type": "object",
            "properties": map[string]any{
                "command": map[string]any{"type": "string"},
            },
            "required": []string{"command"},
        },
    },
    // ... read_file, write_file, edit_file
}

// TOOL_HANDLERS → tell your code: "which function to call"
type ToolHandler func(input map[string]any) string

var toolHandlers = map[string]ToolHandler{
    "bash":       toolBash,
    "read_file":  toolReadFile,
    "write_file": toolWriteFile,
    "edit_file":  toolEditFile,
}

Want to add a new tool? Just add one entry to TOOLS and one to TOOL_HANDLERS. The loop doesn’t change.

2. Dispatch — How the LLM “Calls” a Tool

The LLM doesn’t directly call a Python function. It returns a tool name + input as JSON. Your code executes it.

func processToolCall(toolName string, toolInput map[string]any) string {
    handler, ok := toolHandlers[toolName]
    if !ok {
        return "error: unknown tool " + toolName
    }
    return handler(toolInput)
}

Flow:

LLM says: "call bash with command='ls -la'"
    ↓
process_tool_call("bash", {"command": "ls -la"})
    ↓
tool_bash(command="ls -la")
    ↓
result returned to LLM as a string

Errors are returned as strings rather than panics — the LLM can read the error text and decide to try a different approach. If you panicked instead, the LLM would never see what went wrong.

When stop_reason is tool_use, the response looks like this:

// response.Content[0].Name  → "bash"
// response.Content[0].Input → {"command": "ls -la"}

3. Inner Loop — The LLM Can Chain Tools

User: "add a comment to hello.txt"
    ↓
LLM → tool_use: read_file("hello.txt")    ← read first
    ↓
LLM → tool_use: edit_file(...)            ← then edit
    ↓
LLM → end_turn: "Done"

You collect all tool results into a slice, then send them back as a single user message.

for {
    response, _ := client.Messages.Create(ctx, params)

    if response.StopReason == "end_turn" {
        break
    }

    if response.StopReason == "tool_use" {
        // execute tools, add results to messages as "user"
        messages = append(messages, anthropic.NewUserMessage(toolResults...))
        continue // back to LLM
    }
}

Important: Tool results go into messages[] with role: "user" — this is an Anthropic API requirement.

Takeaway

  • Tools — TOOLS (schema for the LLM) + TOOL_HANDLERS (functions for your code)
  • Add a new tool — add 2 entries, no loop changes
  • The LLM doesn’t “run” anything — it returns a name + input, you execute
  • Tool results must go into messages[] with role “user”
  • Inner loop — LLM can chain multiple tools before end_turn