I gave a model a four-file bug and watched it invent a fifth file.
Not maliciously. Not stupidly, exactly. It did what chat models do when you make the repo live in the prompt: it smoothed over the missing parts. The function probably lived in parser.ts. The tests probably used Jest. The fixture probably looked like the three lines I pasted.
So it wrote a plausible patch for a plausible project, and for about eight seconds it looked useful. Then the actual repo said no.
That is the first boring lesson in coding agents: the harness is the agent. A model can propose code. A coding agent can look at the files, change the files, run the test, read the failure, and keep enough state around to spare you from narrating the whole thing back at it like a tired court stenographer.
This series builds that up with Pi, one primitive at a time. Pi is small enough that the seams show. You can install it, point it at a toy repo, watch the loop, then start adding the boring parts that make the loop useful: repo instructions, shell commands, sessions, extensions, hooks, verification gates, and references instead of paste walls.
The toy repo is intentionally dumb. tiny-ledger reads receipt text and prints JSON. It has fixtures, tests, and just enough ambiguity to expose the difference between a model that can talk about code and a harness that can work on code.

The receipt
Start with a tiny bug report:
tiny-ledgershould parse a receipt line likeCoffee 3.50into{ "name": "Coffee", "price": 3.5 }. It currently drops decimal prices.
If you paste only that into a chat model, the model has to hallucinate the rest of the world. It might guess the parser file. It might guess the test runner. It might guess the module format. It might be right by accident. That is not the same thing as working on the repo.
The problem is not that the model is useless. The problem is that the human is doing the harness manually: compressing the repo into prose, asking for a patch, copying the answer back into files, running tests, pasting failures, and hoping the model's imagined project lines up with the real one.
That loop works until it doesn't. Then it gets stupid fast.
Create the toy repo
The first exercise is deliberately small. You should feel every primitive before the series adds more machinery.
mkdir tiny-ledger
cd tiny-ledger
npm init -y
npm pkg set type=module
npm pkg set scripts.test="node --test"
mkdir -p src fixtures test
Create the first fixture:
cat > fixtures/cafe-simple.txt <<'EOF'
Coffee 3.50
Bagel 4.25
EOF
Create the intentionally broken parser:
cat > src/parseReceipt.js <<'EOF'
export function parseReceipt(text) {
return text
.trim()
.split(/\n+/)
.filter(Boolean)
.map((line) => {
const match = line.match(/^(.+)\s+(\d+)$/);
if (!match) return null;
return { name: match[1].trim(), price: Number(match[2]) };
})
.filter(Boolean);
}
EOF
Create the failing test:
cat > test/parseReceipt.test.js <<'EOF'
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";
import { parseReceipt } from "../src/parseReceipt.js";
test("parses simple decimal prices", () => {
const input = readFileSync("fixtures/cafe-simple.txt", "utf8");
assert.deepEqual(parseReceipt(input), [
{ name: "Coffee", price: 3.5 },
{ name: "Bagel", price: 4.25 },
]);
});
EOF
Run it once without the agent:
npm test
Expected result: the test fails because the parser only accepts integer prices.
That failing test is the receipt. It anchors the whole thing in a command you can run instead of a grand theory about agentic software development, which would be unforgivable behavior honestly.
Add the smallest project contract
Before launching Pi, add the local rules. The repo needs to teach the harness what counts as good work here.
cat > AGENTS.md <<'EOF'
# tiny-ledger agent rules
- Inspect files before editing.
- Parser changes need a fixture and a test.
- Prefer small functions over regex piles.
- Run `npm test` before claiming done.
EOF
Now install and start Pi from inside the repo:
npm install -g @earendil-works/pi-coding-agent
pi
In Pi, ask for the smallest grounded fix:
The parser is dropping decimal prices. Inspect the repo, fix the smallest thing, and run the test before summarizing.
The interesting part is not the final regex. It is the loop:
- Pi reads the files instead of guessing them.
- Pi edits the real parser instead of writing a patch for an imaginary one.
- Pi runs
npm test. - Pi reads the failure or pass output.
- Pi summarizes what happened with the output as evidence.
Repo-grounded execution is the primitive. The model proposes, the harness verifies, the verifier's output becomes the next input. Once that loop closes, you have an agent. Until it does, you have a smart autocomplete with a chat history.
What changed
Direct generation gives you an answer. A coding-agent harness gives the model hands and a feedback loop.
The model still matters. A weak model with tools is still weak. But once the task is code, the model's text is only part of the system. The useful unit is the whole harness: context loading, file access, edit operations, shell commands, session state, and the rules that decide when the loop is allowed to stop.
This is why "just prompt better" keeps turning into a trap. Prompt quality helps, but it cannot replace observation. If the model cannot inspect the repo, it has to infer the repo. If it cannot run the test, it has to narrate confidence. If it cannot read the error, the human becomes the error transport.
You can absolutely work that way. People do it every day. It is just not a coding agent yet.
Exercise
Repeat the same bug three ways:
- Paste the bug report into a chat model without files. Save the patch it suggests.
- Paste
src/parseReceipt.jsand the failing test into the chat model. Save the second patch. - Run Pi inside the repo and let it inspect, edit, and test.
Compare:
- which version guessed file names or test setup?
- which version noticed
AGENTS.md? - which version produced test evidence?
- which version made you move the most information by hand?
Do not waste the exercise dunking on the chat model. Watch where the work went. If the human is ferrying repo state, command output, and failure context between the model and the project, the harness is still mostly the human.
Next primitive
The next post is about tools. ReAct, CodeAct, shell access, and the slightly annoying truth that most useful coding-agent behavior reduces to a loop of read, edit, run, inspect, repeat.