Orkun Soylu

4 min read

pi harness: Qwen3.8-27B Q4_K_M vs Q8_0

All 13 of my agent runs fixed the bug or built the feature, with zero failed exact-match edits. Q8_0 was up to 78% slower and no more accurate. Almost all the waiting went into re-reading the prompt.

Tested
Setup

Radeon Pro W7800 48 GB — bare-metal workstationBare metal, Linux, ROCm (Ollama) · AMD Ryzen 5 7600 (6C/12T, AVX-512) · 64 GB DDR5-6400

Runtime
pi v0.84.2 → Ollama (OpenAI-compatible API)
Models

qwen3.8:27bQ4_K_Mtested

qwen3.8:27bQ8_0tested

Verdict

pi plus a local dense 27B is a working coding agent for small, well-specified tasks. Use Q4_K_M — Q8_0 costs time and VRAM and buys no accuracy. Keep the context short: every turn re-reads the whole conversation.

Works

  • 13/13 runs passed the tests; 0 of 32 exact-match edit blocks failed
  • No run touched tests/ despite it being the easy way out
  • pi's whole prompt + tool schemas is 1,917 tokens — light enough for a 27B

Doesn’t

  • No permission system — the agent has all the rights of the user who starts it
  • No prompt cache in Ollama at the time: 17–25K tokens re-processed per task
  • Narrow test: small Python project, short sessions
Runs passed
13 / 13
Failed edits
0 / 32
Task A time, Q4 vs Q8
50 / 89s
Prompt + tools overhead
1,917tokens

Average time per task

lower is better
  • Q4_K_M
  • Q8_0
Task A — bug fix
50 sQ4_K_M · 50 s
89 sQ8_0 · 89 s
Task B — multi-file feature
85 sQ4_K_M · 85 s
114 sQ8_0 · 114 s
Data
Q4_K_MQ8_0
Task A — bug fix50 s89 s
Task B — multi-file feature85 s114 s

Prefill and generation vs context (Q4_K_M)

lower is better

Why agent turns get slower as the session grows

468 tokens
0.9 sPrefill time · 0.9 s
3.6K tokens
5.9 sPrefill time · 5.9 s
14.6K tokens
20.8 sPrefill time · 20.8 s
29.1K tokens
33.7 sPrefill time · 33.7 s
58.2K tokens
87.4 sPrefill time · 87.4 s
Data
Prefill time
468 tokens0.9 s
3.6K tokens5.9 s
14.6K tokens20.8 s
29.1K tokens33.7 s
58.2K tokens87.4 s

What pi is

pi is an MIT-licensed TypeScript agent harness (~250K lines, 453 test files). Its provider layer treats ~40 providers’ quirks as data — even context-overflow detection is a per-provider regex table. What’s deliberately missing: MCP, sub-agents, permission prompts, plan mode, todos, background bash. You get seven tools — bash read write edit ls grep find — and everything else is an extension.

There’s no permission system. pi runs with every right the user who launched it has; no confirmation prompts, no limits on files, processes, network or credentials. Run it in a container or a dedicated VM — that’s a precondition, not a nice-to-have.

How I tested

I installed pi into an isolated npm prefix and pointed its config elsewhere with PI_CODING_AGENT_DIR. I added the model as an OpenAI-compatible provider (supportsDeveloperRole: false, thinkingFormat: qwen-chat-template, temperature 0.7, top_p 0.8, top_k 20).

I picked two tasks with an objective answer — pytest decides, not me — and reset the working tree with git checkout before every run.

  • Task A — bug hunt. A small Python project with 5 tests, one failing: largest_debit() starts with worst_value = Decimal("0"), so it returns None when every balance is positive. A one-line logic bug you only spot by reading the test.
  • Task B — multi-file feature. 6 tests describing a feature that doesn’t exist yet: a new field on the Entry dataclass, a changed post() signature, Ledger.balance_as_of() and report.monthly_summary() — four linked changes across two files.

Both projects’ AGENTS.md said “do not modify tests/”, and I checked the target path of every edit/write.

Results

Task Model Runs Passed Avg time Avg turns Avg tool calls Re-processed input
A (bug) Q4_K_M 5 5/5 50 s 5.6 6.4 17.6K tok
B (feature) Q4_K_M 3 3/3 85 s 5.3 7.3 19.6K tok
A (bug) Q8_0 3 3/3 89 s 7.0 8.3 25.4K tok
B (feature) Q8_0 2 2/2 114 s 5.0 7.5 19.3K tok

The prediction I got wrong: exact-match edit

Before measuring, I was sure the exact-text edit tool would be where a 27B falls over — whitespace drift, stale oldText, retries. What actually happened: 32 edit blocks across 13 runs, zero failures. In Task B the model sent three changes as a single edit call with an array of blocks, and all of them applied.

Edit failures seem to follow how fresh the context is, not model size: pi has the model read the file right before editing, with little in between. Long, messy sessions may behave differently.

Patch quality

All five Task A runs produced the exact same patch — and they also fixed the docstring that had become wrong:

-    """Return the account with the most negative balance, or None if empty."""
+    """Return the account with the lowest balance, or None if empty."""
-    worst_value = Decimal("0")
+    worst_value = None
-        if value < worst_value:
+        if worst_value is None or value < worst_value:

Q8_0 isn’t better, just more expensive

Q4_K_M Q8_0
VRAM 26.8 GB 38.2 GB
Task A average 50 s 89 s
Task B average 85 s 114 s
Accuracy 8/8 5/5

One Q8 run even made two cosmetic edits after the tests had already passed — that’s chattiness, not quality.

Where the waiting goes

pi’s system prompt + 7 tool schemas + the project’s AGENTS.md + the task = 1,917 tokens. A heavyweight coding assistant carries 10–15K at the same point, which is a big deal for a 27B. But Ollama had no prompt cache, so every turn re-processed the whole conversation: input grows from 1.9K to 3.4K per turn, and 17–25K tokens get re-read per task.

Context Prefill Generation
468 tok 0.9 s (517 tok/s) 25.9 tok/s
3.6K tok 5.9 s (623 tok/s) 25.6 tok/s
14.6K tok 20.8 s (702 tok/s) 23.6 tok/s
29.1K tok 33.7 s (865 tok/s) 21.3 tok/s
58.2K tok 87.4 s (666 tok/s) 17.9 tok/s

The way to speed up a local agent isn’t a faster GPU, it’s a shorter context: compact early, define tasks tightly, skip unnecessary file reads. At 60K, every turn is a minute and a half of prefill.

Two traps I fell into

  • pi -p blocks while stdin is open. My first batch hung for ten minutes without printing a single line; with </dev/null the same job finished in 38 s. Close stdin in cron/CI.
  • pgrep -f finds itself. My wait loop used pgrep -f "run.sh taskA", matched its own command line, and waited 59 minutes for itself. Wait on a PID (kill -0 $PID), not a pattern.

What I didn’t test

Long sessions, behaviour after compaction, finding its way around a large codebase, and pi’s headline feature — writing its own TypeScript extensions.