Handmade Claude Code 2/7 — The Tools

Part two of the Handmade Claude Code campaign: the loop gets hands. A model that can only run bash is a model doing everything through a shell; a coding agent reads files, writes them, edits them in place, finds them and searches them — with results the model can trust and errors it can act on. And because the hands are real, something has to decide what they may touch: a permission engine with rules.

This session continues the agent you built in part one, in the same repository. Nothing about how it is started changes; what changes is what it can do.

The command and the protocol, unchanged

agent: <command>     started as:  <command> -C <dir> [--yes] -p "<prompt>"

The model protocol is frozen in part one and not restated: request in on stdin, one JSON line per reply fragment, tool_use blocks run and answered with one tool_result each, in order, in one user message.

The tools

Six tools, advertised in every request with a name, a description and an input_schema. Paths are relative to the working directory unless absolute.

Tool Input Result content
read path, optional offset / limit (lines) the file's text, as is
write path, content a short confirmation; parent directories are created
edit path, old_string, new_string a short confirmation; old_string must occur exactly once
glob pattern, optional path matching paths, relative to the working directory, one per line, sorted; ** crosses directories
grep pattern (regex), optional path, optional glob path:line:text, one match per line
bash command, optional timeout_ms (default 120000) stdout and stderr in order; exit code: <n> and is_error on failure

A tool that cannot do what it was asked — a file that does not exist, an old_string found twice or not at all, a command that runs past its timeout — returns a result with is_error: true and a content that says why, in plain words. The loop goes on; the model decides. A timed-out command is killed and its result says timed out.

When one reply carries several tool_use blocks, every one of them runs and the next request carries one user message with their results in the same order.

Permissions

Without --yes, every tool call is checked against rules before it runs:

.agent/settings.json
{"permissions": {"allow": ["bash(echo *)", "write"], "deny": ["bash(rm *)"]}}

A rule is a tool name (write — the whole tool) or a tool name with a glob in parentheses (bash(echo *) — matched against the command; for read, write and edit, against the path). deny wins over allow; allow wins over the defaults; the defaults are: read, glob and grep may run, bash, write and edit may not. A call that is not permitted does not run: its result is is_error: true with a content that starts with permission denied. --yes allows everything not explicitly denied.

The judges

The quality panel — architecture, performance, code quality, test quality, technical governance and DX review — sits on the rungs where its subject is decided, one verdict per judge per rung, on top of the rung's own points. There is no closing review: what you build is judged as you build it, and a rung you never reach is a verdict you never get.

The ladder

  • Set up and carry part one forward (10)
  • Read (10)
  • Write (20)
  • Edit (30)
  • Glob (20)
  • Grep (20)
  • Bash has a clock (20)
  • Permissions (40)
  • Parallel tool calls (30)
Sessions

0

Visibility

Public

Category

Reinvent the Wheel

Slug

handmade-claude-code-2-tools

Duration

25 min

Judge reviews

~12 per session

Active session

No

Points

10–40

Tags
  • ai-agent
  • harness
  • tools
  • handmade-claude-code
  • campaign
  • 1

    Set up and carry part one's loop forward

    10

    pts / check

    +10 pts per passing check · +10 for completing the task

    This part continues your part-one agent. Same repository, same command,
    same model protocol — what grows is the set of tools behind it.

    agent: started as: -C

    [--yes] -p ""

    Your AGENTS.md (or README.md) must still carry the two lines the
    platform captures into session memory: agent: and test:. If you are
    starting in an empty folder, your part-one work is fetched for you —
    check that the lines are there and that the agent still builds and
    runs.

    The first rung re-checks the contract part one earned: a bash round
    trip through the scripted model. Everything after it is tool work —
    read, write, edit, glob, grep, a bash with a clock, permissions and
    parallel calls.

    Wrapping claude, codex, gemini, aider or any other coding agent,
    or building on an agent SDK that owns the loop, is not building one.

    Judged by
  • 2

    Read

    10

    pts / check

    +10 pts per passing check · +10 for completing the task

    The model reads a file through you. read takes a path (relative to
    the working directory unless absolute) and returns the file's text as it
    is — no banner, no line numbers, nothing added. Optional offset (the
    1-based first line) and limit (how many lines) return a slice.

    A path that does not exist is an error result, is_error: true, with a
    content that says so. The loop goes on.

    Judged by
  • 3

    Write

    20

    pts / check

    +20 pts per passing check · +10 for completing the task

    write takes a path and a content and puts exactly those bytes on
    disk, creating parent directories on the way. The result is a short
    confirmation; the file is the evidence.

    An existing file is replaced whole. The content is written as given —
    no trailing newline added, none removed.

  • 4

    Edit

    30

    pts / check

    +30 pts per passing check · +10 for completing the task

    edit replaces old_string with new_string in the file at path
    an exact, literal match, once. If old_string is not in the file, or is
    in it more than once, nothing is written and the result is an error that
    says which of the two it was. The model then knows to read more context
    or give a longer anchor.

    The rest of the file is untouched, byte for byte.

  • 5

    Glob

    20

    pts / check

    +20 pts per passing check · +10 for completing the task

    glob finds files by pattern, optionally under path. The result is
    the matching paths, relative to the working directory, one per line,
    sorted. * stays inside one path segment; ** crosses directories and
    also matches none, so **/*.md finds a Markdown file at the top level
    as well as three levels down.

    Judged by
  • 6

    Grep

    20

    pts / check

    +20 pts per passing check · +10 for completing the task

    grep searches file contents for a regular expression pattern, under
    path (default: the working directory), optionally only in files whose
    name matches glob. Every match is one line of the result:
    path:line:text — the path relative to the working directory, the
    1-based line number, the line as it is in the file.

    Judged by
  • 7

    Bash has a clock

    20

    pts / check

    +20 pts per passing check · +10 for completing the task

    A command that never returns must not take the agent with it. bash
    accepts an optional timeout_ms (default 120000); a command still
    running when it expires is killed, and its result is an error whose
    content says timed out — along with whatever the command had printed
    by then.

    The whole thing takes about as long as the timeout, not as long as the
    command wanted.

    Judged by
  • 8

    Permissions

    40

    pts / check

    +40 pts per passing check · +10 for completing the task

    Without --yes, a tool call is checked before it runs:

    .agent/settings.json
    {"permissions": {"allow": ["bash(echo *)"], "deny": ["bash(rm *)"]}}

    A rule names a tool (write) or a tool with a glob in parentheses —
    matched against the command for bash, against the path for read,
    write and edit. deny beats allow, allow beats the defaults,
    and the defaults let read, glob and grep run while bash, write
    and edit wait for a rule. A call that is not permitted does not run;
    its result is is_error: true with a content starting permission denied. --yes allows everything that is not explicitly denied.

  • 9

    Parallel tool calls

    30

    pts / check

    +30 pts per passing check · +10 for completing the task

    One reply may carry several tool_use blocks. Every one of them runs,
    and the next request carries one user message with one tool_result
    per call, in the order the calls were made — t1, t2, t3 — each
    linked by its id. Whether they run at the same time or one after another
    is your call; that all of them run and answer in order is not.

    Every request also advertises all six tools by name.