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)
0
Public
Reinvent the Wheel
handmade-claude-code-2-tools
25 min
~12 per session
No
10–40
- ai-agent
- harness
- tools
- handmade-claude-code
- campaign
1
Set up and carry part one's loop forward
+10 pts per passing check · +10 for completing the task
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:andtest:. 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,aideror any other coding agent,
or building on an agent SDK that owns the loop, is not building one.Judged by
The Debrief 2
Read
+10 pts per passing check · +10 for completing the task
10
pts / check
+10 pts per passing check · +10 for completing the task
The model reads a file through you.
readtakes apath(relative to
the working directory unless absolute) and returns the file's text as it
is — no banner, no line numbers, nothing added. Optionaloffset(the
1-based first line) andlimit(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
Code Quality 3
Write
+20 pts per passing check · +10 for completing the task
20
pts / check
+20 pts per passing check · +10 for completing the task
writetakes apathand acontentand 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 per passing check · +10 for completing the task
30
pts / check
+30 pts per passing check · +10 for completing the task
editreplacesold_stringwithnew_stringin the file atpath—
an exact, literal match, once. Ifold_stringis 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 per passing check · +10 for completing the task
20
pts / check
+20 pts per passing check · +10 for completing the task
globfinds files bypattern, optionally underpath. 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**/*.mdfinds a Markdown file at the top level
as well as three levels down.Judged by
Performance 6
Grep
+20 pts per passing check · +10 for completing the task
20
pts / check
+20 pts per passing check · +10 for completing the task
grepsearches file contents for a regular expressionpattern, underpath(default: the working directory), optionally only in files whose
name matchesglob. 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
Performance 7
Bash has a clock
+20 pts per passing check · +10 for completing the task
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 optionaltimeout_ms(default 120000); a command still
running when it expires is killed, and its result is an error whose
content saystimed 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
DX Review 8
Permissions
+40 pts per passing check · +10 for completing the task
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 forbash, against the path forread,writeandedit.denybeatsallow,allowbeats the defaults,
and the defaults letread,globandgreprun whilebash,write
andeditwait for a rule. A call that is not permitted does not run;
its result isis_error: truewith a content startingpermission denied.--yesallows everything that is not explicitly denied.9
Parallel tool calls
+30 pts per passing check · +10 for completing the task
30
pts / check
+30 pts per passing check · +10 for completing the task
One reply may carry several
tool_useblocks. Every one of them runs,
and the next request carries one user message with onetool_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.