The zero-token software factory for AI coding agents.
Answer everything a parse, a count or a set difference can answer before the agent opens. Pack the rest into one window. Measure what the session used, and what it was spared.
# Node ≥ 20 and git. No npm dependencies.
npm i -g bundlebox
cd your-repo && bb init && bb doctor
An agent is billed for what it reads
The problem
Most of what a coding agent reads in a session is not the problem it was asked to solve. It is orientation: where the code is, which function serves which route, whether two tables agree, what the test command is, what changed since last week.
Measured over 170 sessions in the workspace this tool was built for, the median session re-read 37% of the files it opened, spent 88% of its tool turns on a single call, and half of all sessions ran long enough to compact at least once. Every one of those questions has an answer a parse, a count, a path check or a set difference can produce in milliseconds for nothing. The agent was paying tokens for arithmetic.
If the answer is a set difference, a path check, a count or a parse, it is a detector and it costs nothing.
Everything that fails that test is packed into a brief the agent can act on without searching, and the agent is opened with the smallest window that can hold that brief plus the files it names. What it used, and what the factory saved it from paying, is measured off the transcript the agent already writes. bundlebox never calls a model itself.
The design goal, stated as a number: a model should touch about a tenth of the work.
One document in, working code out
The pipeline
The pipeline has one paid stage. Every other stage is a local computation whose whole purpose is to make that one stage shorter.
A pipeline does not fail by erroring. It fails by skipping — a corpus nobody ran, a board older than the scenarios in it, findings nobody compiled. Each of those is silent, and each makes the next stage produce a confident answer about stale inputs. So every stage carries an exit criterion that is evaluated now, not a memory of having run once.
$ bb mainboard gaps
ok Genesis 1 world, 23 capabilities, 3 rules from docs/PRD.md
ok Situation 82 open of 84, scanned 9m ago
GAP Scenarios the newest board is older than the newest scenario
— it is reporting on a corpus that has changed
9 of 10 stages hold. The first that does not is scenarios. Closed by:
bb cookbook run --base <url>
Deciding what to write is free. Writing it is not.
Genesis
Give it the document you already wrote — a PRD, a spec, a README, a pasted prompt — and it derives the half of a scenario corpus that is derivable rather than judged.
$ bb genesis docs/PRD.md --base http://127.0.0.1:4400
prd — from docs/PRD.md, 412 lines
9 surfaces · 34 rules · 61 capabilities · 3 actors · 12 constants
what it could not settle — stated, not filled in:
? how a request authenticates — the rules assume a signed-in caller and no
auth route is named; put it in the corpus `setup`
Every item cites the line it came from, because a surface with no source is a guess and a corpus built on guesses goes red about itself. What the derivation could not settle is listed rather than filled in: a model that invents a base URL produces a corpus that is red everywhere for a reason that has nothing to do with the product.
From there, coverage is a set difference. bb genesis plan ranks every capability no scenario touches and assigns each a tier from its own shape — a read with no path parameter cannot be anything but simple; anything naming a relationship between two parties is complicated whether or not somebody felt like writing it that way.
Then bb genesis pack writes one brief per surface. The brief carries the derived half already done: the routes, the tier, the rules to cite with their line numbers, the step skeleton the tier forces, the exact expectation vocabulary, and one acceptance command. The agent is told what to write, not asked what is missing.
Each brief also carries a short table of what it does not accept — "I read the handler, the shape is obvious"; "I'll assert the status and move on" — with the reason each does not apply. It is three hundred tokens that reliably saves several thousand.
genesis calls a model. bb genesis send hands a packed file to whichever agent this box has, and it still needs --run --spend before anything opens.A detector asks what the files say. A scenario asks what the system does.
Scenarios
A corpus is a persona's week written down: JSON on disk, executed by the Rust kernel, no model anywhere, findings with evidence.
{
"id": "items-write-read",
"surface": "items",
"severity": "high",
"rule": ["POST /items returns 201 with version 1 — src/items.py:88"],
"steps": [
{"name": "create", "do": "POST /items",
"body": {"title": "audit {{run}}", "due": "{{+2d}}"},
"expect": {"status": 201, "json": {"version": 1}},
"save": {"item": "id"}},
{"name": "read it back — a 201 that stored nothing looks identical from here",
"do": "GET /items/{{item}}",
"expect": {"status": 200, "json": {"id": "{{item}}"}, "max_ms": 500}}
]
}
Four properties do the work, and each exists because the alternative produces a board that is green for the wrong reason.
The rule and the assertion live in the same file. Every scenario quotes the behaviour from the source with its constant names. Without that, a red step is an argument about what the corpus wants. With it, a red step is the system contradicting something written down, and the fix is obvious in whichever direction it turns out to lie.
The run is paced. A service that limits to sixty requests a minute turns an unpaced 130-step corpus into an all-red board about the limiter, with every surface after the halfway point answering 429. The client paces itself and rides out a 429 rather than recording it.
A failed precondition blocks its scenario instead of cascading. One unwritable directory used to read as nineteen product defects; now it reads as one environment fact and the steps after it are marked blocked, which is a different state from failed and is reported as one.
Green has to mean something was checked. bb cookbook check needs no server and refuses a scenario in which nothing asserts, an unknown surface, an expectation key nothing implements, a duplicate id, and an actor that names nobody. Green-because-nothing-was-checked and green-because-everything-held look identical from the outside, and that gate is the only thing between them.
Which scenarios to run, and when
A corpus grows faster than the wall clock available to run it, so running all of it every time is the thing that stops happening. bb cookbook select ranks by expected information — how often a scenario has been red, how often it has changed its mind, how long since anything looked, what it costs in steps — scaled by the severity of what it is about. Every term is printed beside the score, because a ranking nobody can read is a ranking nobody will trust enough to run.
$ bb cookbook select --budget 60
scenario sev last history age steps value
-------------------- ------ -------- --------- ---- ----- -----
calendar-conflict high failed 3/4 red 2.1d 7 6.63
brief-timelapse high passed 0/4 red 2.1d 9 3.02
search-federated medium never run - - 5 2.31
12 selected, 58 steps of a 60-step budget
Three runtimes, one set of answers
Architecture
The CLI, the adapters and the store are Node with no dependencies, because a global install must be one command and a cron worker must run what is on disk. Two classes of operation do not belong in a scripting runtime and were moved out.
| Runtime | Owns | Why there |
|---|---|---|
Rustbbk | Tree walks, fingerprints, token estimates, duplicate windows, symbol indexes, acceptance gates with real timeouts, worktrees — and now scenario execution, load simulation and health probing. | A corpus is hundreds of sockets and a simulation is thousands. Threads, one connection per worker, one shared pacer. A scripting runtime spends the wall clock on its own scheduler. |
Pythonbundlebox_expert | The forward-chaining rule engine, triage, confidence shrinkage, transcript signals, the process model, memory — and now the world derivation, coverage planning, scenario selection and board verdicts. | Expert-system and ML work is readable and testable here and nowhere else. Standard library only. |
| Node | Every verb, the adapters, the store, the console. | One npm i -g, no build step, no node_modules. |
Both optional runtimes degrade cleanly. Without the kernel the JavaScript engine runs, and it is also what runs when the base is https or the corpus uses a pattern outside the kernel's documented subset — the dispatcher says which engine ran and why, because a board that does not name its engine is a board whose numbers cannot be compared with the last one. Without python3 the learning verbs say so and the zero-token path is unaffected. Where two runtimes implement the same fact, a test pins them to identical answers: which runtime happens to be installed must not change a number.
What it does at more than one caller
Simulation
A corpus asks whether the system obeys its own rules once. A simulation asks whether it still does under a hundred callers.
$ bb simulate run smoke --base http://127.0.0.1:4400 --levels 1,8,32
conc requests rate p50 p95 p99 max errors
---- -------- -------- ------ ------ ------ ------ ------
1 1391 1389.9/s 0.6ms 1.3ms 2.2ms 4.6ms
8 1921 1914.3/s 4.1ms 6.6ms 9.7ms 16.2ms
32 2287 2258.8/s 11.1ms 22.0ms 96.8ms 267.6ms
floor 0.3ms measured in this run · budget 25ms (4× the floor)
Latency budgets here are never absolute milliseconds. A profile names a multiple of the floor measured in that same run, clamped, with a slack floor under it. A constant written on one box is wrong on every other one, and a performance board that cries wolf on a slower laptop is a board nobody reads twice.
The bar is declared before the work, not argued about after it
Governance
Every other verb here answers "what is wrong with what was written". bb auditor answers the question that comes first — what is in scope, what bar does it have to meet, who decides when it does not, and what counts as proof — and it answers it while changing the code is still cheap.
Four artefacts, in this order, because each one is the input to the next. Change the scope and the standards selection is invalid, not stale. Change the standards and findings in flight are against a bar that no longer exists.
| Step | What it settles |
|---|---|
| scope | What is in, what is out, and where the boundary runs. Work that leaves it stops and says so. |
| standards | What good looks like inside that boundary — chosen by what the tree is, never by ticking a list. |
| governance | Which findings hold a release, who can accept a risk, and who signs. |
| assurance | What counts as proof. A finding with no evidence a reader can open is refused at ingest. |
Selection is derived, not declared. The auditor reads signals the tree already emits — does it move money, does it hold records about people, does it bind a socket, does it cross a process boundary — and returns the standards those signals imply, each carrying the signal that pulled it in. A selection you disagree with is arguable rather than mysterious.
$ bb auditor standards console
17 of 29 standard(s) in force for console — level B (hazardous)
id domain adal the bar because
SEC-1 SEC B No secret reaches version control every tree gets these
REL-1 REL B Every external call has a timeout and a bound it calls out over the network
OBS-2 OBS C A health check answers without reading the store it binds or routes HTTP
…
Signals read from the tree: always · service (it binds or routes HTTP) · network
The level is ADAL — A catastrophic, B hazardous, C major — and it is set by the consequence of a defect in that area, not by the highest bar among standards that apply everywhere. It changes verification depth and who signs. It never turns a standard off.
Then it is checked, and unproven is not green
bb auditor gate <area> re-reads an area against its own charter and returns one of three states per standard. met means something on disk supports it. failed means something on disk contradicts it. unproven means nobody looked — and it never collapses into met, because a standard nobody checked reported as passing is the exact failure this whole tree exists to remove.
$ bb auditor gate console
SEC-1 met secret-scan ran within 24h and found nothing here
REL-4 unproven no detector computes this and no current review cites it
MNT-1 failed 1 open finding(s) from god-file
6 met · 3 failed · 11 unproven exit 10
Where a detector already computes part of a standard, the brief hands that output over rather than asking for it. The auditor never spends a turn on a number a local verb produces for nothing; it asks only for the half that is judgement. That is the whole economic argument for the verb.
Living and dated are different artefacts
A charter is living: when the area moves, re-derive it. A review is dated and is never edited — a new one is written beside it. The only thing a review is worth is being a fixed point to measure drift against, and updating one destroys it. bb auditor drift reports both, separately, with the different instruction each needs.
Where the tokens go, and where they come back
The budget model
projected = overhead + brief + payload × churn + reserve
Overhead is the system prompt, tool schemas and instruction files, before a word of the task. It is probed, not assumed: bb tokens profile --probe opens a one-turn session under the exact flags a lane spawns with and reads the first usage block. Payload is every file in scope, read once; when a finding names a symbol, the payload is the located region plus a widening allowance, not the file. Churn is the term everyone forgets — the same files re-read after edits, plus tool output about them — and it measures near 2.4 on real transcripts. Reserve is held back for the model's own output, per kind of unit.
Five levers act on those terms, in the order they were measured to matter.
- Lean session flags. Naming the seven tools a lane uses, dropping user settings sources, MCP servers and slash commands took a lane's opening window from 44.3k to 29.2k tokens on the reference box. The flags are not independent, so the stack is probed as a combination.
- Region, not file. A route table is two to four percent of the file that holds it. Quoting the located region with line numbers turns the common case into a session that never opens the file.
- Say it once. Facts shared by every finding in a unit are hoisted into one header. Six near-identical hints become one with a hole in it.
- A cache-stable prefix. Static guardrails first, dynamic evidence last, so parallel lanes share a prompt-cache write instead of each priming their own. Cache reads bill at a tenth of input.
- The wire. An optional local compression proxy in front of the lane, talked to over the process boundary and never imported. Reported on its own row with its own denominator, because a cache bust can take back what compression removed.
| Before | After | |
|---|---|---|
| Opening window per spawned session, before any work | 44.3k tokens | 29.2k (−34%) |
| The same findings, planned | 5 sessions, 1.3M projected | 2 sessions, 351k (−74%) |
| One pipeline tick | 226 agent turns | 133 s, 0 model tokens |
What the current window has left
Monitoring
Claude Code bills in five-hour rolling blocks that start with the first message after a gap, not in calendar days. A daily total answers the wrong question.
$ bb monitor
block 17:10 → 22:10 2h 23m left
used [###################---------] 68.2% 60.1k of 88.1k (p90)
the 90th percentile of 14 completed blocks over 8 days
burn 1.9k tokens/min over 58m, 214 turns
runs out in 14m at this rate — the budget runs out first
cache 85.5M read, 1.4M written — read bills at a tenth of input
OK
Two clocks run at once and the earlier one decides: the block expires on the wall clock whatever the rate, and the budget runs out at this rate whatever the clock. Reporting only one of them is how a window ends early.
The limit defaults to this account's own 90th-percentile block over the last eight days rather than a published figure, because a published figure is wrong for anybody on a different plan. Every number carries where it came from: measured, p90, plan, or unknown — and unknown is printed rather than replaced with a zero.
This is not only a display. bb monitor guard is asked immediately before the one thing in this factory that costs anything, and it refuses to open an agent when the block is nearly spent. A call opened with twenty minutes left is cut off half-written: it spends the tokens and produces nothing to accept. Exit codes make it usable from a hook or a cron line without parsing — 0 ok, 10 near, 11 reached, 20 indeterminate.
The console
bb console serves one read-only page on loopback: where the pipeline is, what the window has left, and every session with its own title, what it used, and how many agent turns the local path had already displaced for it. bb console build writes the same page as a single self-contained file that opens without a server.
It pushes rather than polls. The store is files, so the filesystem already knows when something changed: /api/stream watches it, recomputes the state once per change however many tabs are open, and sends it. A timer was wrong in both directions — a run that finished was invisible for nine seconds, and an idle workspace recomputed everything six times a minute per tab.
The header always says which state the connection is in: live, polling, reconnecting, or static. A dashboard that cannot tell you it has lost the server is worse than one that is plainly offline, because the stale numbers still look like numbers.
There is no write route, and that is checked rather than asserted: the page's one derived cache is read without being written back, and the query parameter that used to trigger a fold of every transcript over HTTP is gone.
On a public benchmark, measuring the thing it actually does
SWE-bench Verified
SWE-bench Verified is 500 real GitHub issues with the maintainer's own patch as ground truth. The number people quote from it is resolve rate, and producing one needs a model, a container per repository, and the official harness. bb bench swebench does not produce that number and does not estimate it — an estimated resolve rate is a made-up figure with a real benchmark's name on it.
What it measures is the half bundlebox is responsible for, on the same public instances: given only the issue text, does the context it packs contain the files the maintainer actually changed, and what does that context cost?
$ bb bench swebench run --n 20 --repos requests,flask,seaborn,pylint,pytest,xarray
LOCALISATION in scope (budgeted to be read): 13 of 24 gold file(s) — 54.2%
named (in scope, or pointed at): 17 of 24 — 70.8%
bare (top 10 files read whole): 20 of 24 — 83.3%
11 instance(s) had every gold file in scope; 14 had every one named
CONTEXT 66.3k packed against 3.5M bare — 98.1% less, 53.1x
The bare arm still localises better. It reads ten whole files and finds three more gold files than the packed brief names, at fifty-three times the context. That is the trade this benchmark exists to put a number on, and printing only the half that flatters the tool would make the number worthless.
Every input is public — the dataset over the Hugging Face datasets-server, the repository over a blobless git clone at the instance's own base commit. The run records the instance ids it used and the exact command that reproduces it, so the number can be contradicted rather than only repeated.
What the benchmark changed
It was run first, and then acted on. Four defects it exposed, each fixed and re-measured on the same twenty instances:
| What the run showed | What was wrong | Named recall after |
|---|---|---|
| Test files ranked first on almost every issue | A test names the thing under test more often than the implementation does. Test files now score a third unless the issue is itself about a test. | — |
| Briefs came out at 2–5k tokens against a 120k floor | The scope could only shrink. It now grows into the budget that was already paid for, skipping what it cannot afford rather than stopping there. | — |
| The file the fix belonged in sat just outside the scope | Ranked candidates the budget could not afford were discarded. Naming one costs about fifteen tokens; budgeting one to be read costs its whole size times churn. They are now named. | 54.2% |
| An exact symbol match lost to three loose ones | Every symbol hit scored the same. A symbol that is one of the issue's words now scores far higher, and hits within one file decay. | 70.8% |
The same run priced a fifth defect. The grow loop stopped at the first candidate it could not afford, so one oversized file blocked every smaller file behind it and a scope that had been cut could never grow again. On repositories with large files the scope collapsed to 1–8 files at 3% of the window while the gold file sat below the cut, named but not budgeted. Skipping an unaffordable candidate instead of stopping took in-scope recall from 45.8% to 54.2% for 0.6% more context.
37.5% → 70.8% named recall over four changes, on the same instances, at the same context cost. The baseline run is on disk beside the current one.
Measured and estimated are never added
Reporting
| Row | How it is known |
|---|---|
| used | Measured to the token from the transcript, deduplicated by message id |
| saved: cache | Measured — those exact tokens billed at the cache-read discount |
| saved: wire | Measured from the proxy's own counters, when it is up |
| saved: automation | An estimate, printed as a range: turns the local verbs displaced × this session's own marginal-to-full per-turn cost |
A model with no price in the table is reported with its tokens and no cost. A verb that could not look returns unknown, never an empty list. A unit with no acceptance command is marked unproven, not passed. An eval whose frame is empty is skipped, never green — unless its filter selects violations and its spec says so, in which case no rows is the measurement.
Every report also states what it could not see. bb failsafe status ends with the sources that were blind, because a status that reports "nothing failing" while three of its five sources could not look is the most expensive kind of green there is.
The verbs
Reference
| Verb | What it answers | Cost |
|---|---|---|
genesis | A document becomes a world model, a corpus and the briefs that fill it | 0 |
cookbook | A persona's week against the running system; red steps become findings | 0 |
simulate | The same request at rising concurrency, against a floor-relative budget | 0 |
mainboard | Six views over one ledger, and which pipeline stage does not hold | 0 |
runbook | Is it up, what broke since the last call, what is it costing | 0 |
auditor | The bar before the work — scope, standards, governance, assurance — and the gate that checks it after | 0 |
frames | A dataframe over the factory's own data, and the evals that read it | 0 |
failsafe | What is failing, why, and the operation that closes it | 0 |
monitor, console | What the block has left; one page for the whole workspace | 0 |
scan, findings, explain, fix | Sixteen detectors with evidence and expected-value triage; local actuators that write a patch before a file | 0 |
compile, route, context, gates | Units packed to a window with quoted regions; lanes with conflicts as affinity; does this scope fit | 0 |
snapgen, pinpoint, oversight | Fingerprinted reference tables; one problem to one budgeted brief; god files, bloat, duplication | 0 |
snapgen skeleton, blast, callers | A file's declarations without its bodies; what a diff can reach and what reading it costs; who imports a symbol and who merely names it | 0 |
bench, bench swebench | The same task packed and bare, both counted; and the same two arms on public SWE-bench Verified instances | 0 |
pipeline, buckmaster, scripts | Gears with gates and fingerprinted skips; episodes, signals, rules, model, memory | 0 |
git | Commit, push, draft PR, review to findings, gated merge, with guards | 0 |
wire, mcp, hook | Install into every agent on the box; serve the same verbs over MCP | 0 |
run, bridge send | The only two verbs that spend | spends |
Every verb is a dry run until --apply. Every verb takes --json.
Wiring into agents
bb wire --apply installs three things per agent detected on the box: an instruction block between markers, hooks where the agent supports them, and an MCP server entry. bb unwire removes only its own blocks. Claude Code, Codex CLI, Gemini CLI, Cursor, GitHub Copilot, OpenCode, Cline, Roo, Windsurf, Aider, and any command at all through lanes.custom_command.
One file, holding only what you changed
Configuration
{
"lanes": { "agent": "claude", "max_parallel": 4, "daily_budget_usd": 20 },
"budget": { "max_tokens": 160000, "churn_factor": 2.4 },
"bridge": { "enabled": true, "window_guard": true, "daily_budget_usd": 5 },
"monitor": { "plan": "custom" },
"cookbook": { "default": "alex" },
"kernel": { "gates": { ".": { "quick": "npm test" } } }
}
Every default lives in src/core/config.js beside the reason for it. Measured values live in .bundlebox/var/calibration.json and are written by read-merge, because a daily calibrator that overwrites a probed value silently regresses every lane budget after it.