Field note
Pi-Bolt: A Compiled Coding Agent That Can Afford Guardrails
Pi-Bolt compiles the Pi agent harness to native code. I measured 14.6x faster startup, and explain why that speed makes real agent guardrails affordable.
There is a benchmark number in the Pi-Bolt README that looks like a typo. Writing a 400 KB file through a tool call takes 143.7 seconds on Pi running under Node 24, and 1.2 seconds on Pi-Bolt. That is not a 2x or a 5x. It is roughly 120x, for an operation that is just writing a file.
I did not take that number on faith, and I did not take the rest of the table on faith either. The README publishes four platform tables, so I transcribed all of them and recomputed every multiple from the raw pairs rather than trusting the prose. Here is what the tables actually say.
What the README actually reports
Pi-Bolt 1.0.0 against Pi 1.4.0 as released on npm, run by stock Bun 1.4.4 and by Node 24, doing the same work against the same local model. Lower is better.
| Benchmark | Pi-Bolt | Pi on Bun 1.4.4 | Pi on Node 24 |
|---|---|---|---|
| Writing a 400 KB file through a tool call, s | 1.2 | 194.8 | 143.7 |
| CPU, streaming a 60,000-character answer, s | 2.34 | 21.92 | 18.62 |
| CPU per prompt, prompts 21 to 40, ms | 46 | 328 | 352 |
| CPU, interactive session, ms | 167 | 1,087 | 1,180 |
| Memory after 40 prompts, MB | 117 | 309 | 521 |
| Ready to type, ms | 41 | 366 | 423 |
Linux x86-64, one CPU. Recomputing the Node column against Pi-Bolt gives 119.8x on the file write, 8.0x less CPU streaming, 7.7x less CPU per prompt, 7.1x less CPU over a session, 4.5x less memory, and 10.3x sooner ready to type.
The other three platforms move in the same direction, and the spread is the interesting part:
| Linux x86-64 | Linux ARM64 | macOS (M5) | Windows x64 | |
|---|---|---|---|---|
| File write, s | 1.2 | 1.3 | 0.6 | 1.8 |
| CPU streaming, s | 2.34 | 2.32 | 1.35 | 2.69 |
| CPU per prompt, ms | 46 | 42 | 17 | 53 |
| Memory after 40 prompts, MB | 117 | 120 | 89 | 79 |
| Ready to type, ms | 41 | 37 | 24 | 78 |
Across all four platforms and both baselines, the README’s own summary ranges check out against its tables: 61 to 162 times faster on the file write, 4.8 to 17 times less CPU streaming, 5.3 to 8.8 times less CPU per prompt, 5.9 to 7.1 times less CPU over a session, 2.5 to 15 times less memory, and 3.8 to 11 times sooner ready to type. I verified each of those six ranges by recomputation, and every one lands inside its stated bound.
One caveat worth stating plainly, because it is where this class of benchmark usually goes wrong: the low ends of those ranges are not “Pi-Bolt is weaker on some platform”, they are Bun being a better baseline than Node on that measurement. Windows x64 shows only 61x on the file write against Node’s 110.1 s, and that is still 61x.
What I measured myself
The tables above are the vendor’s, not mine. I have the upstream agent and the compiled build on the same machine, so I measured the one thing I could measure honestly here: startup.
Benchmark 1: pi --version (Pi 1.0.2, on Node)
Time (mean ± σ): 215.9 ms ± 33.6 ms
Benchmark 2: ~/.pi-bolt/.../pi --version (Pi-Bolt 1.0.0, native)
Time (mean ± σ): 14.8 ms ± 2.4 ms
14.59 ± 3.30 times faster than pi --version
Fourteen times, not 120. Different versions, different workload, so mine is the narrower and safer claim. Note also that the vendor’s own macOS table reports 24 ms ready-to-type against Node’s 151 ms, which is 6.3x rather than my 14.6x. Mine is measuring --version exiting; theirs is measuring an interactive session accepting keystrokes. They are different stopwatches, and neither is wrong.
Why an interpreter costs you more than seconds
The 143-second file write is the interesting number, and it is not really about I/O. Under an interpreter, the cost of any code that runs on every tool call is paid at the speed of the interpreter. That economic fact propagates upward through every design decision you make on top of the harness.
Concretely: if checking a command before you run it costs 3 ms interpreted, you will check it on the paths you care about and skip it where you are in a hurry. If the same check is compiled into the binary and costs a rounding error, you check everything, always, because there is no reason not to.
That is the difference between a guardrail and a suggestion. A guardrail that is too expensive to run everywhere is just documentation with extra steps. The same cost logic shows up in token economics of agent loops, where per-call overhead compounds into the dominant term.
What 36 compiled-in checks look like
Pi-Bolt ships with extensions compiled directly into the executable rather than loaded at run time. The build I am using right now reports 36 of them, including rg-enforce, pdf-guard, browser-session-guard, jev-mandate-guard, seo-publish-guard, script-lang-guard, jbox-autoroute, xerj-enforce, ask-multi, task-panel, and chain-provider.
I have a financial interest in disclosing the context here: this is my own harness configuration, layered on Pi-Bolt. But the mechanism is general, and it is the part worth understanding.
Each of those checks sits at the tool_call boundary and can refuse an action. And each one exists because something went wrong first. Four examples from the log on this machine:
| Guard | The failure that created it |
|---|---|
browser-session-guard | ~12 pkills and a deleted SingletonLock in one task, closing real windows with logged-in state |
pdf-guard | A render that silently fell back to the user’s system Chrome when the bundled shell was missing |
script-lang-guard | Python scripts quietly becoming the default for loop-bound work |
browser-session-guard (again) | A false positive on the word “Singleton” appearing in a branch name |
That last row is the honest one. The guard blocked a legitimate command because a branch was called perf/query-worker-singleton. A guardrail is a program, and programs have false positives. You accept them because the alternative — a closed browser with someone’s logged-in tabs gone — is worse and unrecoverable.
The test suite behind these is not decorative either: the guard extensions in this configuration carry 19, 45, 21, 35, 39, 22 and 17 tests respectively. That is where the engineering sits.
The honest caveat about JIT builds
Pi-Bolt has two build flavours, and the README is unusually candid about the trade-off. The default build has no JIT: Pi’s own code is compiled, but an extension loaded at run time is interpreted. For heavy plugins — search, parsing, anything using bun:ffi — there is a -jit variant, and Pi stays within about 5% of the default.
If you are running 36 extensions where some do real work on every call, you want the JIT build. Saying “compiled” without that caveat would be marketing rather than measurement.
Where the 120x actually comes from
I want to be careful, because I did not reproduce the file-write benchmark and I am not going to pretend otherwise. The plausible mechanism is that tool-call plumbing in an interpreted runtime pays repeated dispatch, serialisation and garbage-collection costs that compiled code does not — and a 400 KB write touches all of it. The vendor’s numbers across four platforms (Linux x86-64, Linux ARM64, macOS, Windows) show the same shape, from 61x to 162x.
Independent reproduction is the next step, not a conclusion. The startup number I measured is the one I would defend in isolation, because I ran it on this machine with a harness and can re-run it.
What the compiled build is really buying
Not typing speed. The honest framing is a budget shift.
Every agent harness has a fixed cost per tool call, and that cost caps how much verification you can afford. When the floor drops by an order of magnitude, the ceiling on how carefully the agent behaves rises with it. You go from “check the dangerous commands” to “check every command, and also enforce repo conventions, and also refuse to touch a live browser session.”
That is the actual argument for compiling an agent. Not that it starts sooner — though 215 ms to 15 ms is pleasant — but that the checks you would otherwise skip become cheap enough to always run.
Pi is MIT-licensed and minimal by design, which is what makes this possible: there is less surface to port, and the extension boundary is the only place behaviour lives. Pi-Bolt is what happens when you take that boundary seriously and pay for it ahead of time instead of on every call. If you are still deciding which primitive belongs where, hooks, skills and subagents covers that split; guardrails are the hook-shaped one.
Try it: curl -fsSL https://pi-bolt.opensec.in/install.sh | sh, then verify with pi-bolt --version. If the line ends in JIT on, you have the build with JIT enabled: no JIT at all is the default, and an extension loaded at run time is interpreted there.