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.

npm ci MIT
# 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.

Genesis
A document or a prompt becomes a world model: surfaces, actors, the rules the prose states, and every capability it names.
0 tokens
Situation
Sixteen detectors over what is on disk, the services that are up, and the playbook entry for anything failing.
0 tokens
Agent
The judgement half, and only that half: the scenarios a set difference says are missing, packed to the smallest brief that can be acted on.
spends
Scenarios
The corpus run against the running system by the Rust kernel. Red steps become findings with the rule they contradict.
0 tokens
Simulation
The same request at rising concurrency, against a budget derived from the floor measured in that run.
0 tokens
Pinpoint
One problem, one window: the located regions, the evidence, the scope, the gate, the budget.
0 tokens
Agent
A packed call to whichever agent this box has, gated on what the current billing window has left.
spends
Ship
Commit with an explicit path list, a draft PR whose body is the findings it closed, and a merge that has to pass every gate.
0 tokens

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.

Nothing in 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.

RuntimeOwnsWhy there
Rust
bbk
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.
Python
bundlebox_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.
NodeEvery 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.

StepWhat it settles
scopeWhat is in, what is out, and where the boundary runs. Work that leaves it stops and says so.
standardsWhat good looks like inside that boundary — chosen by what the tree is, never by ticking a list.
governanceWhich findings hold a release, who can accept a risk, and who signs.
assuranceWhat 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
BeforeAfter
Opening window per spawned session, before any work44.3k tokens29.2k (−34%)
The same findings, planned5 sessions, 1.3M projected2 sessions, 351k (−74%)
One pipeline tick226 agent turns133 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 showedWhat was wrongNamed recall after
Test files ranked first on almost every issueA 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 floorThe 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 scopeRanked 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 onesEvery 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

RowHow it is known
usedMeasured to the token from the transcript, deduplicated by message id
saved: cacheMeasured — those exact tokens billed at the cache-read discount
saved: wireMeasured from the proxy's own counters, when it is up
saved: automationAn 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

VerbWhat it answersCost
genesisA document becomes a world model, a corpus and the briefs that fill it0
cookbookA persona's week against the running system; red steps become findings0
simulateThe same request at rising concurrency, against a floor-relative budget0
mainboardSix views over one ledger, and which pipeline stage does not hold0
runbookIs it up, what broke since the last call, what is it costing0
auditorThe bar before the work — scope, standards, governance, assurance — and the gate that checks it after0
framesA dataframe over the factory's own data, and the evals that read it0
failsafeWhat is failing, why, and the operation that closes it0
monitor, consoleWhat the block has left; one page for the whole workspace0
scan, findings, explain, fixSixteen detectors with evidence and expected-value triage; local actuators that write a patch before a file0
compile, route, context, gatesUnits packed to a window with quoted regions; lanes with conflicts as affinity; does this scope fit0
snapgen, pinpoint, oversightFingerprinted reference tables; one problem to one budgeted brief; god files, bloat, duplication0
snapgen skeleton, blast, callersA file's declarations without its bodies; what a diff can reach and what reading it costs; who imports a symbol and who merely names it0
bench, bench swebenchThe same task packed and bare, both counted; and the same two arms on public SWE-bench Verified instances0
pipeline, buckmaster, scriptsGears with gates and fingerprinted skips; episodes, signals, rules, model, memory0
gitCommit, push, draft PR, review to findings, gated merge, with guards0
wire, mcp, hookInstall into every agent on the box; serve the same verbs over MCP0
run, bridge sendThe only two verbs that spendspends

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.