Give the coder read, write, edit and bash as first-class tools #127
- AtlantisPleb opened this issue 1d ago
-
A Author 1d ago Compared pi's originals against ****, a fork that took the same four tools a long way. Synced to (already in the manifest at line 169; the local clone is a zip-import with unrelated history so it will not fast-forward — read
origin/maindirectly).The size difference is the story:
tool pi ( ffc9be886)oh-my-pi ( origin/main)read 29 2,385 write 31 1,769 bash 37 1,779 edit 61 gone — replaced by ast-editBuild pi's four. Do not build oh-my-pi's four. 158 lines that work beats 6,000 lines of someone else's product decisions. But three ideas in there are worth knowing about now, because they change what you would design if you knew them.
1.
editdid not survive contact. oh-my-pi deleted exact-stringeditentirely and replaced it withast_edit— structural rewrites via ast-grep,$NAMEmetavariables matching whole AST nodes, backed by a Rust crate (crates/pi-natives/src/ast.rs). That is a real signal: the exact-match-with-uniqueness rule is a good first tool and it is the one that gets outgrown first. Worth knowing before we grow ours in the same direction by accident.2. Hashline — the idea I would steal.
packages/hashlineis "a compact, line-anchored patch language" whose stated purpose is: it binds every hunk to a file-content hash so stale anchors are rejected before they corrupt code. That is the same failure our exact-match rule guards against — a model editing against a file that has since moved — solved once, generally, instead of per-tool. Oureditshould at minimum refuse when the file changed under it, and hashline is the shape of the general answer.3. A repeat-read hint.
read.tstracks reads per session and, after three identical ones, appends a hint to the result — catching the model in a loop and telling it so. Cheap, and it addresses something we have watched happen.Also there, less transferable but worth naming:
readtakes internal URIs (memory://,skill://) and URLs through the samepathparameter; it returns a separatedisplayContentso the TUI renders without re-parsing the model-facing text; it counts unresolved git conflicts for an inline badge;bashhas grown into five files including a PTY selector and an interceptor.None of that changes this issue's scope. Build the 158 lines, with the five guards already listed. The reason to record this is so the second version is not designed in ignorance of where this road goes.
-
A Author 1d ago Correction to the comment above: two names were eaten by shell expansion before
it posted. The fork iscan1357/oh-my-pi, and the local clone is at
~/work/projects/repos/oh-my-pi. Nothing else in that comment changed. -
AtlantisPleb
closed this as completed in
75eb40b1d ago
The coder's only way to touch a file is
shell. Every read iscat, everywrite is a heredoc, every edit is
sedor a rewrite — so the model spendstokens on shell quoting, and the transcript records a shell invocation where it
should record an intent.
Pi solved this in one commit at the very start of its coding agent
(
earendil-works/pi,ffc9be886, "Agent package + coding agent WIP"), withfour files totalling 158 lines:
read.ts29,write.ts31,edit.ts61,bash.ts37. That is the whole of it, and it is worth copying because of howlittle it is.
What the four originals do
path. Resolve, check it exists, return the wholefile as text. Errors come back as a string the model can read, not an
exception.
pathandcontent.mkdir -pthe parent, write, reportSuccessfully wrote N bytes. Creates or overwrites.path,oldText,newText. Exact-match replacement, and thetwo refusals are the design: it refuses when
oldTextis not found, andit refuses when it is found more than once, telling the model to add
context until the match is unique. That single rule is what makes a
surgical edit safe without a diff format.
command, 30s timeout, 10 MB buffer, stdout with stderr appendedunder a
STDERR:marker.Pi's default active set is exactly
read,bash,edit,write, withgrep,findandlsavailable read-only.(
docs/teardowns/2026-07-21-pi-agent-teardown.md§3.4.)What to build
Rust equivalents in
crates/openagents-cli/src/tools.rs, declared besideshell,skill,openagents,capability,delegateandacp. Keep themthis small. The point of the reference is its size.
What ours must do that the originals did not
These are not embellishments — each one is a defect this repository has already
shipped and fixed once:
Stringhas panicked here ten times, including on the path whose job isreporting a refusal.
tracker::floor_char_boundaryexists; use it.is_error: true.shellreported every failure asa success until recently, so a failing build read like a passing one.
does — the model reads the refusal and retries. It must never be a silent
empty result.
writeandeditstage and rename rather than writing in place. Areader in the truncate window otherwise sees a half-written file; see #114.
Tests
Assert the behaviour that makes
editsafe: aoldTextappearing twice isrefused and the file is unchanged. Assert
readon a missing file returns arefusal rather than raising. Assert a non-zero
bashexit is reported as anerror. Verify adversarially — revert each guard and watch the test fail.