Base agent
3. Write tool
View this stage's code on GitHub →Same idea as Read, mirrored for writing: one more entry in the tools list, one more branch in the dispatcher. Adding a second tool before building the loop is deliberate — it proves the tool-calling mechanism generalizes before anything more complex sits on top of it.
Your task
- Add a
Writetool: requiredfile_pathandcontentstrings. - Dispatch on the tool call's name — whichever of Read/Write the model asks for.
The solution
Try the task above first. When you want to compare, this is exactly what changed since stage 2 (just diff 2 3 shows the same).
Show the solutionHide the solution +39 −9 lines
main.py+39 −9
⋯ """Stage 2: the Read toolStage 3: the Write tool The model can't touch your filesystem on its own — it can only ask. Thisstage adds one tool, Read, and a single round trip: send the prompt andthe tool's spec, check if the model asked to call it, execute it if so,print the result (no loop yet, that's the next stage).Same pattern as Read: advertise the spec, dispatch by name when the modelcalls it. Still one round trip, still no loop — just a second tool.""" import argparse⋯ TOOLS = [ }, }, }, } }, { "type": "function", "function": { "name": "Write", "description": "Write content to a file, creating it if needed or overwriting it if it exists", "parameters": { "type": "object", "required": ["file_path", "content"], "properties": { "file_path": { "type": "string", "description": "The path of the file to write to", }, "content": { "type": "string", "description": "The content to write to the file", }, }, }, }, },] ⋯ def Read(file_path): return f.read() def Write(file_path, content): with open(file_path, "w") as f: f.write(content) return f"Wrote to {file_path}" def main(): parser = argparse.ArgumentParser(description="petite-harness: a tiny AI coding assistant") parser.add_argument("-p", "--prompt", required=True, help="the task to ask the model")⋯ def main(): call = message.tool_calls[0] print(f"[main] model requested tool call: {call.function.name}", file=sys.stderr) if call.function.name != "Read": arguments = json.loads(call.function.arguments) if call.function.name == "Read": result = Read(arguments["file_path"]) elif call.function.name == "Write": result = Write(arguments["file_path"], arguments["content"]) else: raise RuntimeError(f"unknown tool: {call.function.name}") arguments = json.loads(call.function.arguments) print(Read(arguments["file_path"])) print(result) return print(message.content)
Try it
Run this stage's own code:
just stage 3
cd .stages/03
just run "Create a file named hello.txt with exactly one line: Hello world"
cat hello.txt
Expect: Hello world
.stages/03 is a git worktree: your checkout stays on main, so
just keeps working there. Compare with the previous stage with just diff 2 3,
or browse it on GitHub;
just clean-stages removes the worktrees.
Where this fits
The whole agent; this stage builds the highlighted part. Click any part to jump to its stage.