A broken npm symlink, a 400 from Claude extra usage, and a home directory full of every project on the machine. Then one repo, four tools, three commits.
What Happened
Pi went from a broken global install on this Debian box to a working coding-agent harness aimed at one repo. Three commits landed on ~/projects/test-agent, branch main.
What the repo holds now
| Commit | Payload |
|---|---|
| 64f199c | Integer coercion for memory_size and llm_max_tokens; workspace checks with Path.resolve() and is_relative_to; twelve pytest tests; AGENTS.md; .gitignore |
| 8e98a31 | The rest of the existing app — main.py, core.py, memory.py, config, requirements, README |
| 6281d4f | README section for pytest -q and AGENT_* overrides |
Harness: Pi v0.84.2. Auth: Anthropic Console API key, not Claude.ai extra usage. Model sent on the wire: claude-opus-4-8.
Why it mattered
Why a harness at all
A model is only the weights. The harness is tools, cwd, context files, sessions, auth, compaction, the TUI. Pi keeps that layer small on purpose: four tools — read, write, edit, bash — and a short system prompt. Everything else is opt-in. Smaller context, visible tool calls, provider-agnostic, files on disk instead of a sealed personality.
Why the installer fought back
~/.npm-global/bin/pi already pointed at the old package name @mariozechner/pi-coding-agent. The current package is @earendil-works/pi-coding-agent. npm refused to overwrite the link (EEXIST). After the stale symlink was gone, PATH still had to include ~/.npm-global/bin or the binary sat on disk while the shell said pi: not found.
Why the first model call 400’d
Subscription OAuth in a third-party harness does not spend the Claude plan. Anthropic bills that traffic as extra usage. The Console API key is the path that actually answers.
Why $HOME was the wrong cwd
Launching Pi in /home/lvydvy exposed every tree on the machine — Zig caches, WordPress, tarballs, unrelated apps. No single stack, no git root, and the same permissions as the login user. Tokens burned on listing. Risk high. A harness is only as narrow as the working directory.
Why test-agent
A small Python REPL agent already on disk: YAML config, memory deque, file tools, Anthropic call. Enough surface to change, test, and commit without opening llama.cpp or a game engine.
Why those particular edits
The first read of the repo found real faults: env overrides arrive as strings (maxlen=”100″ is a trap); str.startswith treats work-evil as inside work; pytest was named and empty; execute_code is raw exec(). The patch fixed the first three and left exec() alone on purpose.
How the work actually ran
Install. Remove the May 3 mariozechner symlink, uninstall the old global package, install @earendil-works/pi-coding-agent, export PATH=”$HOME/.npm-global/bin:$PATH”. Confirm which pi lands on the earendil-works cli.js.
Auth and model. /login on the subscription produced the extra-usage warning and a 400. API key written to ~/.pi/agent/auth.json. /model claude-opus-4-8. The model’s own sentence “claude-opus-4-6-20251101” is not the router. Footer and /model are. From the 4.6 generation on, dateless IDs are pinned snapshots: claude-opus-4-8 is 4.8, not “whatever Opus is newest.”
Scope. A placeholder path the-actual-project was copied into the shell and failed. Real directories were listed from bash, not from a $HOME agent session. Chosen root: /home/lvydvy/projects/test-agent. Rule that held: pwd must print that path before pi starts.
Session one. Prompt asked for a one-screen summary and an AGENTS.md if missing. Layout, run/test commands, and fragilities went into that file. Later startups show [Context] AGENTS.md.
Session two. One constrained prompt: coerce ints, replace prefix checks, add tests, do not grow execute_code. Edits in agent/config.py and agent/tools.py; new tests/test_config.py and tests/test_tools.py; pytest installed into .venv; 12 passed.
Git, with a human still in the loop. git diff opened less. Keystrokes meant for the shell went into the pager — hence h, broken filenames, and the > continuation prompt. GIT_PAGER=cat keeps diffs in the terminal. First commit was only the new slice. Second commit added the pre-existing application files. Paste-accident files were deleted by find/name patterns, not by quoting em-dashes in rm. git add . stayed off until the junk was gone.
Session three. README-only prompt. Diff reviewed. 6281d4f. Working tree clean.
The loop that remains: one directory, one task, read the diff, commit. Pi edits. Ownership stays with the person at the keyboard.
Why Pi is worth keeping
Not a chat window with a terminal skin. Infrastructure for running a model against a tree:
- Four tools, every call visible.
- Project rules as files (AGENTS.md, skills, session trees).
- Same harness across providers.
- Extensions and packages when the core is too small.
- Interactive, pi -p, JSON, RPC, SDK.
What this night proved on a smaller scale: a strong model can be aimed at one repo, changes can be test-backed, and git can keep the record. That is the difference between a teammate and a home-directory lawnmower.