Same loop, more tools
“The loop didn’t change at all — I just added things to the TOOLS array.” — s02_tool_use.py, verbatim.
What do you actually touch when adding a tool?
The s01 agent only knows bash. To also support read_file / write_file / edit_file, what do you change?
Most people’s first instinct: modify the loop. Wrong. The loop doesn’t need a single change. You only need three things:
- Write a Python handler (
run_read(path, limit)). - Register it in the
TOOL_HANDLERSdispatch map ("read_file": lambda **kw: run_read(...)). - Add a JSON schema entry to the
TOOLSarray (tells the model the tool’s name and parameters).
When the loop sees a tool_use block it looks up block.name in the dispatch map, executes the handler, and stuffs the output into a tool_result. Exactly the same path as bash.
# Dispatch map: name → handler lambda 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"]), }
See how dispatch routes a request
The widget below lets you click a tool_use request the model might send, and watch how it gets routed to the right Python handler. Key insight: block.name determines the path.
safe_path: the fence you must not skip
Giving an agent file access creates an obvious security risk: path escape. The model is supposed to work inside /home/user/project/, but it sends read_file("../../etc/passwd").
s02 has a small function that handles this:
def safe_path(p: str) -> Path: path = (WORKDIR / p).resolve() # normalize, resolve .. and symlinks if not path.is_relative_to(WORKDIR): raise ValueError(f"Path escapes workspace: {p}") return path
The critical pair is .resolve() + .is_relative_to(): expand to an absolute path first, then verify it’s still inside the sandbox. Without the first step, foo/../../etc slips through. Without the second, there’s no check at all.
Identifying safe vs unsafe paths
Five paths a model might pass as a read_file argument. Which ones does safe_path allow, and which does it block? Assume WORKDIR = /home/user/project.
Don’t add dangerous tools to your agent
Adding tools is easy, but remember: every tool you add extends the model’s capability boundary. s02 already has a bash blacklist (rm -rf /, sudo, shutdown) and write_file is sandboxed by safe_path. Before adding a tool to production, ask yourself three questions:
- Can this tool make irreversible changes? (rm, send email, git push)
- What can it leak? (env vars, .ssh, cookies)
- Could malicious output be re-injected as instructions? (prompt injection via tool output)
Lesson s07 will add a “permissions layer” that lifts these decisions out of code and makes them declarative.