Learn Claude Code
s02

Tools

Tools & Execution

One Handler Per Tool

154 LOC4 toolsTool dispatch map
The loop stays the same; new tools register into the dispatch map

s01 > [ s02 ] s03 > s04 > s05 > s06 > s07 > s08 > s09 > s10 > s11 > s12

"Add a tool, add a handler" -- the loop stays unchanged, new tools register in the dispatch map.

Harness layer: Tool dispatch -- expanding the boundaries of what the model can reach.

Problem

With only bash, everything goes through shell. cat truncation is unpredictable, sed breaks on special characters, and every bash call is an unconstrained security surface. Specialized tools (read_file, write_file) can enforce path sandboxing at the tool level.

Key insight: adding tools doesn't require changing the loop.

Solution

+--------+      +-------+      +------------------+
|  User  | ---> |  LLM  | ---> | Tool Dispatch    |
| prompt |      |       |      | {                |
+--------+      +---+---+      |   bash: run_bash |
                    ^           |   read: run_read |
                    |           |   write: run_wr  |
                    +-----------+   edit: run_edit |
                    tool_result | }                |
                                +------------------+

The dispatch map is a dict: {tool_name: handler_function}.
One lookup replaces any if/elif chain.

Core Concepts

Path Sandboxing

def safe_path(p: str) -> Path:
    path = (WORKDIR / p).resolve()
    if not path.is_relative_to(WORKDIR):
        raise ValueError(f"Path escapes workspace: {p}")
    return path

Dispatch Map

TOOL_HANDLERS = {
    "bash":       lambda **kw: run_bash(kw["command"]),
    "read_file":  lambda **kw: run_read(kw["path"], kw.get("limit")),
    "write_file": lambda **kw: run_write(kw["path"], kw["content"]),
    "edit_file":  lambda **kw: run_edit(kw["path"], kw["old_text"], kw["new_text"]),
}

Key Code

for block in response.content:
    if block.type == "tool_use":
        handler = TOOL_HANDLERS.get(block.name)
        output = handler(**block.input) if handler \
            else f"Unknown tool: {block.name}"
        results.append({"type": "tool_result", "tool_use_id": block.id, "content": output})

Adding a tool = adding a handler + a schema. The loop never changes.

What's New (s01 → s02)

Components01s02
Tools1 (bash only)4 (bash, read, write, edit)
DispatchHardcoded bashTOOL_HANDLERS dictionary
Path safetyNonesafe_path() sandbox
Agent loopUnchangedUnchanged

Deep Dive: Design Decisions

Q1: Why a dispatch map instead of if/elif?

Extensibility. if/elif requires modifying dispatch logic for every new tool. With dispatch map, adding a tool is one line in the dict — the dispatch logic never changes. MCP (s17) takes this to the extreme: tool definitions dynamically loaded from external servers.

Q2: Is safe_path's path traversal defense complete?

Mostly complete. .resolve() handles symlinks, .is_relative_to() catches ../../../etc/passwd. Edge case: TOCTOU race conditions between check and use; production uses os.open() + fstat().

Q3: How important are tool descriptions?

Critically important. The model relies entirely on the description to choose which tool to use. This is the core theme of s21 (ACI Tool Design).

Q4: Why does read_file have a 50K character truncation?

Context window protection. A 1MB file ≈ 300K+ tokens, which would overflow the context. 50K chars ≈ 15K tokens is a safe upper bound. Claude Code uses ~200K char truncation with model notification.

Q5: What's the division of labor among the four tools?

ToolResponsibilityWhy not bash
read_fileRead file contentbash cat truncation unpredictable
write_fileCreate/overwrite filesbash echo struggles with special chars
edit_fileFind-and-replace editsbash sed fragile with multi-line/special chars
bashExecute arbitrary commandsKept as fallback for uncovered scenarios

Design principle: Specialized tools first, bash as fallback.

Try It

cd learn-claude-code
python agents/s02_tool_use.py

Recommended prompts:

  • "Read the file requirements.txt" — triggers read_file
  • "Create greet.py with a greet(name) function" — triggers write_file
  • "Edit greet.py to add a docstring" — triggers edit_file
  • "Run python greet.py" — triggers bash

References

  • Claude API: Tool Use — Anthropic Docs. JSON Schema format for tool definitions, tool_use/tool_result message protocol.
  • Building Effective Agents — Anthropic, Dec 2025. ACI (Agent-Computer Interface) principles — tool name and description design directly impacts agent effectiveness.
  • Claude Code: Tool Architecture — Anthropic Docs. Claude Code's 10+ specialized tools, validating "specialized tools over generic bash."