10. Coding Tools: read, edit, write, bash
What separates a coding agent from an ordinary tool-using agent shows up most in the write operations. Reading files and searching let the model observe the world; edit, write, and bash let it change the world and verify the results. This is also where the risk is highest: editing the wrong file, clobbering the user's changes, running dangerous commands, producing oversized output, and hung test processes all happen in real repositories.
Read before you edit
Both the system prompt and the tool runtime should enforce one rule: read the target file before editing it. If the model calls edit on a file it has not read, the runtime can refuse and return an error tool result:
Cannot edit src/config.ts because it has not been read in this session. Read the file first, then retry with exact oldText.
This error is not conservatism. The reliability of edit depends on exact context. When the model has not read the file, it usually guesses at the shape of the code, which leads to failed replacements or wrong edits.
Edit is not "have the model emit a patch"
For a teaching project, start with an exact-replacement edit:
type EditInput = {
path: string;
oldText: string;
newText: string;
};
The runtime reads the current file, checks that oldText appears exactly once, and then replaces it with newText. If it does not appear, return a correctable error; if it appears multiple times, also return an error and ask the model to provide more context. This is far easier to validate than having the model output a patch directly, and it makes self-correction much easier for the model.
Failure feedback should be specific:
oldText was not found in src/parser.ts.
The file currently contains a similar line:
return parse(input, options);
Read the latest file contents and retry with exact oldText.
Given feedback like this, the model can usually re-read the file and issue a correct edit.
The boundaries of write
write is for creating new files or replacing a file wholesale. It is more dangerous than edit because it does not require an oldText match. Recommended policy:
- Creating a new file can be allowed directly, but the path must still be inside the workspace.
- Before overwriting an existing file, require that the file has been read, or require user confirmation.
- For large file writes, show a diff in the UI.
- After writing, put a summary of the change into the tool result.
Do not use write for small fixes. Small changes go through edit, which lowers the risk of clobbering.
The boundaries of bash
bash is the most powerful and most dangerous tool. Even a minimal implementation needs:
- A confined cwd.
- Timeouts.
- stdout/stderr truncation.
- Exit codes.
- AbortSignal cancellation.
- Confirmation or refusal for dangerous commands.
When feeding command output to the model, preserve the command, the exit code, a truncation note, and the key output:
Command: npm run check
Exit code: 1
Output was truncated to the last 200 lines.
src/index.ts:42:10 - error TS2322 ...
When output is long, keeping the tail is usually more useful than keeping the middle, because test and compiler errors tend to be summarized at the end.
The file write queue
The model may call two write tools in parallel within a single turn. Even if your loop executes them serially, leave the boundary in place for future parallelism. Write operations on the same real path must go into the same queue:
edit src/a.ts -> waits for previous write to src/a.ts
write src/a.ts -> same queue
edit src/b.ts -> can run independently
The queue key should be the resolved real path. Symlinks, relative paths, and case differences can all point to the same file. Without a write queue, two tools will compute their changes against stale content, and whichever hits disk last overwrites the other.
Diffs for humans, summaries for the model
The model does not need a full unified diff to continue; the user interface does. The result of an edit can be split into two layers:
- For the model: the edit succeeded, the file, the line count, and a suggested next step.
- For the UI: a structured diff, the old content, the new content, and whether the file was created.
If you stuff every full diff into the context, the agent burns through tokens fast. What the model actually needs is "the change is done" and "which tests to run next."
Exercises
Implement edit, write, and bash.
Acceptance criteria:
editrequiresoldTextto appear exactly once.- A failed
editreturns a correctable tool result instead of throwing an uncaught exception. writehas an explicit policy for overwriting existing files.bashhas timeouts, truncation, exit codes, and cancellation.- Writes to the same file never overwrite each other in parallel.
- UI events get the diff details, while the model context only gets a short summary.