Skip to content

Releases: blackswanalpha/bundlebox

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 08:28
80415c2

v0.9.0 — 2026-09-27

The release where bb scan takes half the time on a large tree, and
bb sentinel runs everything free before it spends on an agent.

Added

  • bb sentinel (#82, #84). Runs every check that needs no model first,
    then hands only what is left to agent lanes inside the daily budgets. #84
    adds the Python policy, kernel ops, and links into lathe and arc.
  • bb pipeline tail and a console gears panel (#79). tail streams gear
    runs and stages from gear_runs.jsonl and episodes.jsonl until Ctrl-C
    (--gear, --verbose, --json). The console shows runs per gear over 24h,
    7d and all time, with failures, average seconds and the latest 15 runs.
  • Bench page changes and new tests for doctor, drivers, facts, packs, ratify,
    swebench and six expert modules (#81).
  • One-page pitch, deck and PDF under docs/pitch/ (#70).

Changed

  • Scan and pinpoint performance (#71 to #76). On a shallow clone of django
    (7,091 files) bb scan went from 22 s to about 11 s. Each file is hashed and
    counted once, five detectors stop rescanning the corpus per candidate, the
    kernel's dupes keys windows by interned line ids, resolve() memoises by
    spec, and a per-file cache keyed by content sha1 carries single-file
    derivations between scans. importGraph and dead-deps read import specs
    through that cache (#77, #78).
  • Findings noise (#80). Open findings on this tree went from 826 to 192. A
    window where half or more rows are a bare string literal is no longer a
    duplicate block, in JS and in the kernel. oversight:god-file and
    oversight:duplication no longer reach the findings board. bb findings
    defaults to 20 rows, most-committed files first within a severity.
  • The cut-file prompt suggests a pasteable bb pinpoint command (#83, #86).

Fixed

  • orient skips arc build when the arc binary is absent. An npm install
    ships no binary, and this was 104 of orient's 119 recorded failures (#79).
  • bb waits for stdout and stderr to flush on exit. bb findings --json | …
    stopped at 65,536 bytes (#81).

Known issues

  • test/doctor.test.js fails on Windows: the repository row reports warn
    for a workspace whose git lives in a subdirectory. The release gate runs on
    Ubuntu.
  • A kernel installed before this release reports help tables as duplicate
    blocks. bb kernel install fetches the new one.

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 18:48
2907a91

v0.8.0 — 2026-09-22

The release where a probability model watches the agent, and Jev's word reaches
the price of a lane.

Until now Jev was asked one kind of question, whether a contested shape is
deliberate, and only the ask queue read the answer. This release gives the
answer two more readers. Triage prices a finding with it, and a new verb,
bb foreman, asks Jev about the agent's run itself: is it stuck, off track,
finished, verified. Every Jev call now survives a slow reply.

Added

bb foreman — a responsibility policy over the coding agent (#64).
The design is thruwire/foreman's, ported
into the Python expert (expert/bundlebox_expert/foreman.py, stdlib only).
Six responsibilities own twelve yes/no checks: human escalation, repository
instructions, documentation (routed, asked only when the job calls for docs),
worker health, completion and verification. Jev answers every check in one
call. A deterministic policy turns the probabilities into one of continue,
steer, stop, verify, resume, finish or escalate. Thresholds and priorities are
thruwire's.

bb foreman [--job "..."] [--since <rev>] [--active]   score the run, pick one action
bb foreman verify [--cmd "npm test"]                   run verification against the current tree
bb foreman replay [--set <check>=<bar>]                re-decide the timeline under other bars
bb foreman label <#> right|wrong                       mark a recorded action for replay
bb foreman log | checks
  • Without a key the checks come from what the box already records:
    grapple's drift window for stuck and off track, git's changes for work done,
    and the last bb foreman verify run. Evidence cannot tell whether a job's
    requirements are met, so on evidence alone finish needs a passing
    verification.
  • A run's commits are its work (#65). A run is measured from a base
    commit: --since, else the base its first assessment recorded, else HEAD at
    the session's first tool call. Before this, a run that committed as it went
    looked like a run that had done nothing.
  • A verification is tied to the tree's content hash, built with
    git write-tree in a throwaway index. Committing a verified tree keeps it
    verified. Editing it makes the verification stale.
  • Every assessment is a row of .bundlebox/var/foreman-timeline.jsonl,
    with the scores and state it was decided on. replay re-runs any threshold
    change and counts candidate fixes and regressions against the labels.
    thruwire lists this calibration tooling as missing.
  • The PostToolUse hook steers a live session. Every foreman.hook_every
    tool calls (10) it assesses the session with the agent marked active, so
    only steer and stop can fire. It ships as foreman.hook: "observe": the row
    is recorded and nothing is injected. "steer" puts the steer or stop into
    the agent's window. Jev stays out of the hook unless foreman.hook_jev is
    set, and then gets one attempt, because the hook is killed at 15s.

Measured on this repository with Jev, one session's work, --since 2c5c471:
with the job stated as row 0's loose text the policy said resume
(implementation_complete 0.49). With a --job naming what shipped, the same
commits, tree and verification gave finish (0.92). Jev scores the run
against the job you state.

Changed

Jev's probability reaches the price of a lane (#63). Triage priced every
heuristic finding off one method constant, PRECISION.heuristic = 0.6. Jev's
p now enters expectedValue as a prior on that constant, weighted by the
code windows Jev read (at most 3, shrunk at n/(n+4)). p=1.0 moves a heuristic
from 0.60 to 0.34, not to 0. On a medium finding at 150k tokens, ev goes 0.8
to 0.51, under the 0.6 floor, so the lane does not open. A person's answer
retires the opinion for its shape.

The Jev call retries a timeout, a 429 and a 5xx (#67), up to twice after
the first attempt: 500ms then 1s, or the server's Retry-After up to 5s. A
refused connection or a 4xx returns at once. One bb foreman request timed
out at 8s and the same request answered in 2.1s moments later; before this,
that one slow reply dropped Jev for the whole assessment.

Fixed

  • The read guard denies only lines its quote can hand back (#68). A
    region's quote is capped at 6000 characters when recorded and 2600 when
    served. A region spanning src/wire/hooks.js:292-825 arrived as ~30 lines,
    and reads of lines 414-443 were denied with a quote that did not contain
    them. A ranged read is now denied only when the quote carries every line, a
    whole read only when every region arrives whole.
  • The foreman hook records a base for a session entry that has none (#66).
    Entries written before bases existed never got one, and the run fell back to
    HEAD: 0 commits, and a clean tree read as no work.
  • The MCP registry job waits ten minutes for npm, not one (054d5e5).
    v0.7.1's record went red on a 404 while npm was still processing the
    tarball.

v0.7.1

Choose a tag to compare

@github-actions github-actions released this 22 Sep 15:05

v0.7.1 — 2026-09-22

The release that draws the factory's own numbers and then puts the server where
something can find it.

v0.7.0 aimed the locate. This one answers the question that follows: is the
aimed locate worth anything, and can anyone install it. Seven figures now come
off the runs already on disk, including the one arm bundlebox loses. And bb mcp — nine tools that until now nothing outside this repository could
discover — has a registry record, a plugin manifest and a CI job that publishes
both.

Measured

SWE-bench Verified, 100 instances, both arms counting tokens over text on disk,
neither calling a model:

bare packed
context per task 8.7k 2.8k (−67.5%, 3.1x)
gold files in scope 24.6% (31/126) 64.3% (81/126)
instances with every gold file 26/100 61/100
files put in front of the model 10 read whole 6.8 in scope
assembling the window 246ms 8.9s

The last row is the arm bundlebox loses, and it is drawn rather than dropped.
6.6s of the 8.9 is the first-visit symbol space, which a workspace pays once
rather than per task; on a tree already scanned the same row reads 38ms against
1.6s.

Derived from those counts at vendor list prices, no request sent: $43.59
against $14.17 per 1,000 tasks on Opus 5.

Added

docs/benchmark/index.html — the runs as a page.
scripts/benchmark-page.mjs draws it from what is already on disk: SWE-bench
Verified at n=100, this repository's own open findings, the 142-run store and
the price table. A number it derives rather than reads says DERIVED where it
appears, and the prefill rate behind the only time estimate is a control the
reader sets. The trend shows savings falling from ~98% to ~67% on 2026-09-19
with the cause named — 1cdb60a re-modelled the bare arm to open one 80-line
range per hit instead of reading matched files whole, so the step is the
baseline getting stricter, not the tool getting worse. Served at
https://blackswanalpha.github.io/bundlebox/docs/benchmark/

A clock per arm. bb bench arm and runTask stamped one seconds across
both arms together, so the question a session waits on had no answer per arm.
bare_ms and packed_ms are carried through the SWE-bench rows and summed
into the totals.

The server is discoverable. server.json is the official MCP registry
record and package.json carries the matching mcpName, which is how the
registry verifies the package and the record have one publisher.
.claude-plugin/ makes the repository installable with
/plugin marketplace add blackswanalpha/bundlebox, carrying the MCP entry and
both skills. smithery.yaml declares the stdio start command, listed local
because bundlebox reads the repository it is started in. The README gains a
copyable mcpServers block for agents bb wire does not manage.

release.yml publishes the registry record. A new mcp-registry job, after
npm because the registry reads the published tarball to check mcpName. It
holds no secret: login github-oidc exchanges the runner's own token, and
GitHub grants io.github.<owner>/* to workflows in that owner's repositories.
The rule the file already had is unchanged — the job sits below release, so
its failure ends there.

Fixed

The locate baseline counted rows its own guard would refuse. It read
measured, recall 0 over 20 exception rows. Fifteen of those twenty were rows
nothing acts on: 8 files that did not exist when the brief was written, 5 test
files, 1 path outside the workspace, 1 artefact under .bundlebox/.
writeVerdict exempts the last three outright, so the denominator and the
guard the figure is read to decide disagreed about what a row is. excluded()
now applies the guard's own four rules, markCreated stamps each edit with
whether git says the file arrived after its brief, and the exclusions are
tallied and named so the smaller sample is auditable. The baseline reads
unknown: 5 rows over 2 briefs, under its own 12/4 floor.

An import edge can name a file. Two of the five surviving rows were never
named for "close the loop on a verified scenario" — neither word is in the
statement, so nothing lexical could reach them, while src/genesis/practice.js
was in the scope the ranker chose and imports both directly. neighbours
spreads a share of the leader's score along out edges and they return at
candidate 3 and 7. Named, never budgeted: rankDetailed reports them
separately, so the opening scope is identical to before at about fifteen tokens
each. A 24-instance SWE-bench A/B is identical in every cell, because those
instances are Python and this graph carries 4 Python import edges — it proves
no regression and nothing more.

The bench scenario cost one run per open finding. bench init derives its
suite from the open findings, so the corpus paid whatever this repository
happened to owe that week. At 12 findings a run took 37.7s against the corpus's
20s cap and the step that ran it twice took 75s — red on every main build since
v0.7.0. The suite is pinned at two tasks, 5.0s, and the step that re-ran the
whole thing for determinism is gone: ci.yml already asserts it on the full
suite across both arms with no timeout, and asserts more than the step did.
The corpus reads 58 passed, 0 red.

Checks

npm run lint          290 files, 0 errors
npm test              0 fail
npm run expert:test   71 tests, OK
cookbook run          58 passed, 0 red, 0 blocked, 0 empty
ablation gate         bare 218652, packed 28187, 87.1% over 12 task(s)

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 10:24
5e8bc49

v0.7.0 — 2026-09-22

The release where the locate is aimed, the work is labelled, and the factory is
put under its own microscope.

v0.6.0 measured the loop against itself. This one acts on what the measurement
said. The content search was returning an arbitrary twelve files and now
returns the best twelve, scored. Four fitted heads went in and every one of
them abstains until it beats its own holdout. bb intent replaced the fix
that every prompt was being labelled with in silence. And a fifth mainboard
category reads this box's own output, because bb mainboard gaps reported a
healthy queue while fifty-nine of its sixty-two units were the same finding.

Measured

grepHits, 100 SWE-bench Verified instances (78 django, 22 astropy), 126 gold
files, same instances each run, no model called:

arm in scope named packed ratio
before 73/126 57.9% 75.4% 333.7k 2.6x
scored grep, slice 6 82/126 65.1% 83.3% 336.3k 2.6x
scored grep, slice 12 81/126 64.3% 83.3% 283.3k 3.1x

MAX_FILES 6 -> 12 is a cost change and not a recall one: recall moves by one
gold file of 126 and the prompt is 16% smaller. Instances where a plain grep
reached a gold file the brief never mentioned: 2 before, 0 after. bb pinpoint
stays at 0 model tokens, 0.54s against a 0.78s baseline and a 1s gate — the
first version lowercased every file to search it and doubled the locate to
1.5s.

What each fitted head has to stand on, on this tree:

task-head         n=12, 10 of 12 fires led to an edit — no win over the regex yet
confidence prior  18 acted_on of 156 — the constant stays
symbol space      2,945 symbols x terms, stdlib SVD, 1.6s, 95 KB
locate aim        unknown, n=3 over 1 brief, against a floor of 12 over 4

Every one abstains until it beats its holdout, which today means none of them
steers. That is the result, not a step before it.

bb genesis --finding swept over all 945 open findings: the 28 that name a
route or a command each derived a capability, the rest derived the row saying
they could not, and every one derived a rule.

Round trips, from the shapes the sessions actually recorded: 1.04 tool calls
per turn, npm run -> npm test -> npm run the most common three-command run
here, and git status -> git diff recurring 17 times across the 18 sessions
that run either.

Added

bb intent — which kind of unit a prompt is. fix, verify, investigate,
build or write, decided from a table fitted offline rather than from a model at
prompt time. The kind picks the output reserve reserveFor reads, and the
shipped table reserves 12k for a fix against 40k for an investigation, so the
label moves ~28k of window between room-to-write and code-to-read. Until this
existed autoPinpoint passed fix for every prompt — a wrong answer on every
question that is not one, billed every turn. Five one-vs-rest heads rather than
one multiclass fit, so a kind the join never labelled falls back on its own
instead of taking the other four down, each carrying its own useful. Same
prompt plus same table gives the same kind every time, offline, and the
derivation comes back with the decision. Fitted at session end, the one budget
allowed to call Jev.

bb case — everything the board knows about one subject. Six subsystems
can each be right and leave nobody able to say what happened: a declared
service down, a corpus that cannot run, eleven spun sessions, tokens-per-turn
at 2.3x its ceiling, one detector owning 89% of the queue. Nothing here
collects anything — every row was already in the store — and a subject is
equality on a value a row already names: a file, or a service, stage,
session, detector, corpus, base, url, view or area in its
evidence. Deliberately not a connected component: the first build used one and
produced a single case of 622 rows, because a duplication finding bridges the
two files it names and enough bridges merge the tree. occurred.js dates a row
from git:last-touch or from the evidence itself and reports how many rows it
could place, rather than copying first_seen and turning every timeline into a
record of when the scans ran.

The proofhouse — FACTORY, a fifth mainboard category whose subject is this
box.
bb mainboard gaps reported "Agent: 62 unit(s) ready of 66" while
fifty-nine of those were one fileless finding scoped to the whole repository,
6.9M projected tokens, every part verdict FITS, and nothing in the tree could
say so. Four checks, each arithmetic over an artefact already on disk, each
naming what it read in its evidence. A check that cannot look reports blind
instead of nothing. It runs last in the tick, because a queue is worth checking
once it is full, and it is wired into the factory gear as an optional step.

bb genesis --finding <id> and --ticket <file>. Two inlets beside the
document: the row's path is the surface, its detail is the rule, the routes and
commands it names are capabilities, and everything it cannot settle is an
unknown row.

bb genesis practice. The round loop that sends packs, verifies what came
back, keeps or rejects each scenario and records the lessons. It resolves the
persona's base against services.json and starts what declares it — services
declared and none at this base is a mismatch it refuses to run at, no services
at all is a base a person keeps up. A round that keeps a red-under-a-rule
scenario compiles and routes what it merged, so it ends holding a budgeted unit
and a lane. Two stops: a ceiling re-read at the top of every round, and
coverage holding across two rounds.

bb gates run and bb situation. At 1.04 tool calls per turn every turn
is a full replay, so the cheapest saving is a verb that answers a whole
situation in one call. gates run runs each distinct gate once and stops at
the first failure; situation returns the tree, the gates, the stale
artefacts, the packed work and the last echos run.

bb grapple, asking Jev. A calibrated opinion on each contested shape
before the queue is ranked, behind TYPESAFE_API_KEY, off with BB_JEV=off,
and null on any failure. jev.probabilities() exposes the same call to a
caller that composes its own state, so the prompt path gets the same treatment
as opinions().

spends: true on a pipeline stage, refused by the runner unless
bridge.enabled, bridge.daily_budget_usd and lanes.daily_budget_usd are
all set, with the missing one named. bb cron install --spend writes the line
for it as a separate list, so the default install has no branch to get wrong,
and the three keys are read at the tick — unsetting one stops the loop without
touching the crontab.

over_by in bb tokens budget: the fold gap reported, not closed.

ui steps in the corpus: four verbs, a driver declared in persona.json,
no model between the step and the action. page reads a local HTML file,
playwright and adb are imported at run time and report their absence rather
than throwing.

Changed

grepHits scores before it slices. It scored nothing and stopped at the
first twelve files matching any of the four longest terms, in directory order.
It now scores every file by how many distinct long terms it carries, returns
the best twelve, runs on every locate rather than only when the symbol tables
went quiet, and weights its hits by that count instead of a flat 1.

ambiguity() can now be unresolved. Its eight signals were all presence
tests, so a locate that matched three irrelevant symbols reported nothing
wrong. PRECISION gains lexical and for_rule() becomes a ninth signal that
fires only on a measured locate under its own prior. locate-replay scores
only the EXCEPTION rows — an obedient agent edits inside the scope by
construction, so an in-scope edit measures obedience and not aim — and drops a
file changed and changed back inside one window first.

per_detector reads the open count, not the run. Four rows a run over a
hundred runs never tripped a per-run limit: 285 duplicate-blocks and 200
oversight:duplication rows sat on an 875-row board while every run stayed
inside its budget. A row whose actuator closes it is exempt; a plan- actuator
is not, because it writes the plan behind a decision and leaves the finding
open. 485 rows now report as counts and every suppressed count is printed
rather than dropped.

aggregate() splits by intent kind. Re-reading while investigating is
reading; re-reading while fixing is the waste the rule was written for, and
averaging the two hides both. buckmaster joins sessions to intent rows for
the label and prints the split only when more than one kind is present.
reserveSamples reads intent rows too, so the reserve table is checkable
against located prompts and not only against the sessions a lane ran.

bb lathe apply reads grapple's proposals. Every row past the bar is
reported as proposed with the step still missing. Nothing from grapple becomes
a file and the ledger is untouched, so reach and tombstone are unaffected. An
absent file reports present: false rather than that grapple earned nothing.

One similarity in janitor/heap.js over terms, files, prefix and title,
with an optional fitted table and the Jaccard as the documented fallback;
bench/gate.js delegates to it. converge.rs is kept on purpose: the arc is
the independent second opinion.

Fixed

The repository root is not a scope. A finding with no files whose only
location is . is about the workspace, not about code in it. Handing . to
context.evaluate expanded it to every file in the tree, reported SPLIT, and
returned one window-sized slice of the repository per part — twelve such
findings compiled to fifty-nine units holding 6.8M projected tokens. A
directory below the root still counts, because ui-generic files one row per
directory and those files are the finding. An empty scope is a state the brief
already na...

Read more

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 06:09
f3a859c

v0.6.0 — 2026-09-19

The release where the loop is measured against itself, and the box learns to
ask.

v0.5.0 gave every subsystem something to drive. This release asks whether the
driving is aimed: the promotion rule is replayed against the closures git can
prove, the scenario selector is scored against boards it already ran, the
locator's aim is measured per brief, and the packed arm of bb bench is
compared with 36 real agent runs rather than with a strawman. Where the
measurement contradicted a claim, the claim moved.

The other half is the handoff layer. A session that hits something the box
cannot settle used to guess. bb grapple records the question, asks it once,
stores the answer, and in its first phase changes nothing about a session
except what it writes down.

Measured

bundlebox-projects/bench, 2026-09-18: 36 Claude Code runs on
claude-sonnet-5, six seeded defects, three repeats, both arms on one 39-file
tree.

claimed by bb bench measured
tokens, packed against bare 13.3x fewer 1.36x fewer
time to first token — 47% slower packed
cost — 5.3% more packed

Three defects follow from the raw runs and each is a change below: the bare
arm read whole files no agent reads, two briefs shared 1,594 of 3,350
characters after the varying part so nothing cached, and the packed arm
acknowledged its own instructions at 34% more output per turn.

Closure labels, bb triage backfill over this repository:

 15  acted on      a commit in the window touched the path
 69  vanished      the path is gone
 30  unchanged     the window has commits and none touched the path
 87  unknown       the window has no commits; git cannot see a working tree

The rule refuses more than it answers on purpose. 128 of 201 closure windows
here contain no commit at all, and labelling those unchanged would fit a
policy that learns the findings people fix are the ones nobody touches.

Round trips, bb echos over 23 sessions: 4,391 calls across 4,269 turns,
1.03 per turn. One session made 452 calls across 452 turns without issuing two
at once.

Added

bb grapple — the handoff layer. What the box cannot settle, asked once
and stored. Six sub-verbs over an event log under .bundlebox/: observe
runs every detector and records what it would have done; ask is the queue,
capped at two per session because past that a queue is an interruption;
ratify batches yes/no rows into one decision moment; harvest reports the
labels nobody was asked for, or unknown below ten of them; promote turns
what the log has earned into lathe proposals; status prints phase, queue,
collisions, drift and labels. The scoring is expert/bundlebox_expert/grapple.py
with the base rates in priors.json. grapple.phase decides what leaves the
module: observe, the default, injects nothing; enforce adds one blocking
check and lets the queue reach the window; the phase moves when the bench arms
say the guard earned it.

The pre-write hook. PreToolUse on Write|Edit|MultiEdit|NotebookEdit,
bb hook pre-write: a write outside the scope list the standing brief
declared, at the one moment the agent has committed nothing. In observe it
records the verdict and emits no bytes, so the recorded verdicts are the base
rate enforce is gated on. Wired by bb wire --apply.

The narrative, put back after a compaction. src/wire/narrative.js
writes what the session was doing from the record the guards and hooks left
on disk, never from the summary: the task, the scope, the files edited, the
last commands, the gates still open, the questions nobody answered.
PreCompact freezes it beside the marker so the summary and the record describe
the same moment. SessionStart with source: "compact" puts it back first,
then the rules verbatim, each under its own cap, and the marker keeps the next
prompt from doing it twice. A harness with no PreCompact still gets the page
as it stands.

bb triage — the labels, and the rule fitted to them. backfill labels
the closures git can still prove, four ways, and refuses where git is blind.
calibrate hill-climbs the promotion rule over the labelled ones with the
cost of a token as an input, scored against the whole corpus's tokens so
"promote everything" cannot win. The shipped policy is the starting point and
ties leave it in place. --apply keeps the fitted rule in calibration.json
with its sample size, where bb doctor can show it. On this tree:

--cost 0.5   promote_at low    114 promoted, 15/15 caught, waste 0.80
--cost 2     promote_at high    20 promoted,  6/15 caught, waste 0.02

bb cookbook calibrate. A stored board records the realised state of
every scenario, so an alternative selector is scoreable against boards it
already ran, leave-one-forward, no request and no process. Four rules keep the
number honest, one test each: a selection is scored as a set, a green board is
excluded rather than scored zero, the shipped vector is candidate zero, and
board i is scored only by a policy that saw boards before it. On this
workspace it refuses: 8 of 9 boards are green, so 1 can be scored against a
floor of 4.

bb cookbook check refuses an undefined token. An unresolved {{token}}
was a red step about nothing that cost a session to diagnose. The check builds
the scope once from the persona, base, the setup's saves and the earlier
steps' saves, and answers from the code that already resolves tokens rather
than a second copy of the grammar.

Two echos. stray asks the question converge cannot: of the files a
session edited after being handed a scope, how many were in it, one window per
brief. batching counts calls per turn, every call and not only the ones that
produce an event, pooled and reported once.

Closure labels on findings. witness() hashes size and mtime per open
finding on every scan, a stat and never a read. When a finding closes it says
acted_on, vanished, unchanged or unknown, because a policy trained on
resolved alone learns that deleting the file is the most reliable fix.

pinpoint carries the edit. statedEdits reads backticked pairs out of the
problem statement and proposals emits a unified diff, only when the old text
occurs once, on one line, in one located region. Otherwise the section is
absent: a wrong diff costs the undo on top of the fix.

One log row per brief. activate() appends to var/brief/log.jsonl, one
line per brief, never overwritten, so a session that locates four tasks keeps
all four and the scope that had to be located again is the one worth reading.

The second cron line. bb cron install writes factory every 30 minutes
under the recom gate and full every 6 hours, ungated, for the gears that
declared on: cron while no installed line reached them. bb doctor reports
a gear declared but unreached as its own row.

The docs. Both pages carry a sidebar with named, iconed sections, and
docs/visuals/index.html lists all sixty verbs from the command table with
the help line, the usage line, the source file and the effect column, filtered
by text and chapter. docs/demo/ holds the four-beat demo and the script that
regenerates it.

Changed

One preamble opens every brief. pinpoint and a genesis pack share it, so
the first byte that differs between two briefs is the title and the shared
prefix caches. The "Do not" list is folded in as imperatives and the section
headers lost their trailing instruction clauses.

The bare arm of bb bench is an agent. It greps the tree, counts the
search output and opens one 80-line range around the first hit per file. The
measured agent read 16 tokens of fresh input across 18 runs; the old arm read
the top N files whole.

The intake gear runs bb fix before compile, with no flags, so a tick
reports what the actuators could close for free and edits nothing. Closing
them is bb fix --apply, by hand.

cookbook run gates on base_up, one TCP connect when the corpus
declares a base. A corpus run against a dead port read as twenty failing
scenarios rather than one absent service.

The command centre is the console, in the module, the verb, the alias,
the artefact path, the config key, the service name and the committed corpora
that execute it. Earlier changelogs keep the old name because they record what
shipped under it.

Fixed

The duplicate-search denial keyed on the words alone, so grep "^export"
over a second file was refused as already in the window. The key carries the
target now. Found on this guard's author's session, twice in one afternoon.

A bb on PATH older than its hooks was silent. Every event it did not
know was logged as unknown event and did nothing, and the log was the only
place it showed. bb wire status reads that log and says which events, and
that the fix is a reinstall.

bin/bb is a POSIX shell wrapper that locates a usable node, replacing
#!/usr/bin/env node, which exited 127 under the minimal PATH a hook, cron
job or systemd unit inherits. Four sessions of accounting were lost while
every command reported success; messages/crash.md is the reproduction.
bb session rm and bb session backfill recover the records from
transcripts already on disk.

The janitor's status verb wrote. emit writes only when applying; the
bare run was replacing 1.1MB of out/janitor/ while documenting itself
read-only.

An edit arrived as an absolute path and a scope is repo-relative, so
without rel() every edit read as strayed and oscillate saw one file under
two spellings.

bridge nested a brief assuming its H1 was line 0. It drops the title
wherever it sits and demotes the rest.

Verified on this box before tagging:

npm run kernel && npm run arc    bbk 0.6.0, arc 0.6.0
npm run lint                     0 errors
npm test                         596 pass, 0 fail
npm run expert:test              55 ok

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 22:04
c729935

v0.5.0 — 2026-09-17

The release where what this box measures drives something.

v0.4.0 moved the locating to the side that does it for nothing: bb pinpoint
in front of a search, the reference tables in front of the tree, and a gate
that says whether returned work is finished. Every subsystem underneath it
already had its data and its report, and nothing in between. bb lathe
proposed scripts nobody applied. bb bench printed the tasks where packing
loses and they stayed routable, so the next run packed them again and lost
again. bb uptake measured instruction lines nothing removed. Seventeen
detectors filed a finding and left it for a session, including the ones whose
fix is a delete.

So this release is the actuator layer, plus the two surfaces that were missing
from the loop it closes: a session that opens with no environment gets one, and
the ports this workspace is served by are a page rather than a guess.

Measured

Actuator coverage, bb findings --json over this repository today:

792  findings open
484  name an actuator that runs locally, for zero model tokens
 14  of the 22 actuators are named by at least one finding
before after
findings naming a local actuator 2 484 of 792
bb lathe proposals, and the habits under them 114 proposed 9 real
echo findings against this workspace 16, twelve of them the echo's fault 4
CI jobs that ran the arc/JS agreement test 0 of 9 every one

The lathe row is a subtraction, not a filter. commandShapes shaped heredoc
bodies as commands, so } catch, const n and Math.random() are on disk in
shapes.jsonl; it split pipelines on |, which made grep ; head the
seventh-strongest habit at 70 occurrences; and it counted wrappers, which made
timeout the most supported shape on the box. With those three closed, 114
proposals become 9 habits, and each one is a shape somebody ran by hand more
than twice.

544 node tests, 36 python tests, 0 failures, 260 files lint clean. 437 node
tests shipped in v0.4.0.

Added

bb fix runs 22 actuators. Each closes one mechanical case of one finding
and declines the rest by name, because a guessed edit is a wrong edit nobody
checks again. Six are the mechanical deletes: drop-dead-export,
remove-dead-dep, strip-debug-line, relock-npm,
resolve-identical-conflict, drop-dead-knob. Eleven more cover the detectors
that named none — four edit, seven write the plan behind a decision and leave
the finding open for the person who makes it.

Every actuator re-derives its detector's answer before it acts. A scan is a
photograph, and drop-dead-export acting on a stale one unexports a symbol
somebody started importing this morning; the test for that writes the caller
after the scan and asserts the decline. Each imports the detector's own pattern
rather than copying it — exportDeclRe, isWholeLineDebug and
conflictBlocks are exported now, so the rule that files a finding and the
rule that fixes it cannot drift apart.

drop-dead-knob is the first entry in DESTRUCTIVE, which was declared empty
and waiting for one. bb fix asks for --force before it runs. The edit is
reversible; what is not is that a knob somebody already set stops being ignored
and starts being unknown.

The rest of the actuator layer.

bb bench gate       a task the bench measured as packed-loses-bare is marked
                    `pack: false` by `bb route` and refused by
                    `bb monitor guard --for`
bb recom repeatable fact-records for the four surfaces that re-drove on a
                    schedule: the cron line, three pipeline stages, cookbook
                    boards, genesis coverage
bb wire trim        the instructions block is rows with ids, so a line
                    `bb uptake` measured nobody reaching for can be removed
bb lathe apply      writes the habits into scripts/ with a `bb recom` record
                    behind each; `reach` measures whether the habit recurred,
                    `sweep` tombstones what displaced nothing
bb proxy            pack, serve and sieve at the doorway, for an agent whose
                    hook system this box cannot write into
bb echos            spin, oscillate, drift, diminishing and converge over the
                    sessions already on disk
tokens calibrate    churn_factor, anchor_widen and reserve_by_kind fitted per
                    repo, on the `measure` gear's tick

bb wire trim refuses to trim all nine MCP tools: 0 of 19 across the whole set
is the server not being reached, and bb unwire is the verb for that. A
calibration factor with too few samples keeps its shipped value and says so.

A session that opens with no environment gets one. Found by running
bb env in a workspace somebody inited once and never wired: 2 of 9 artefacts,
bb wire status 0/11 files. Every surface this box installs assumes
.bundlebox is there, none of them says so when it is not, and the session
does what a session with no environment has always done — it searches the tree.
SessionStart runs bb init inline, because detection over the tree finishes
inside the 30s hook budget, and detaches the artefact build, which is minutes
of scanning. Measured on a fresh fixture: 2.5s inline, artefacts on disk within
6s.

The guard is asymmetric on purpose. Declining a real project costs one line of
advice a session can act on; initing a home directory, a mount point or /tmp
scatters a state directory nobody asked for and starts a background build over
a tree that is not a codebase. The obvious non-projects are refused by name and
the rest need positive evidence: a git worktree, or a manifest at the root or
one level down. wire.auto_init turns it off, and it does not switch off with
inject_context — a workspace that was just created still says so.

bb viewport serve — the ports this workspace is served by, as a page.
bb runbook status answers for the declared system. A workspace is served by
whatever is listening: a dev server somebody started by hand holds the port the
declared one wants, and runbook reports that service down without being able to
say why. The viewport reads the socket table and joins it to the declaration,
which produces three sets — declared and listening, declared and not, and a
listener nobody declared whose process works inside this tree. The third is the
one no other verb reports and it is usually the reason for the second.

Three socket readers, because the one that exists differs by box: ss, lsof,
netstat. how names the reader that answered, because a table with no rows
means "nothing is listening" under one reader and "no reader was available"
under none.

Two mainboard views. bugbash is the bar a screen has to clear, checked
against the rendered screen rather than the source: six mechanizable rows from
spinwish's board, and the fourteen that need a person reading screenshots are
not here. It pairs with designlabs, which measures the declaration and says
so itself (affordance.signified unknown — a parse cannot see a pixel), so the
two produce the comparison neither can make alone: the declaration passed and
the screen does something else. turntables replays the boards already under
.bundlebox/var/boards/ and files the two facts no single run holds — a
scenario that changed its mind between runs nobody touched the corpus between,
and a scenario that went green to red and stayed. It runs nothing; the corpus
was already paid for.

bb dotty keeps what a run showed, the other half of what bb recom keeps.
The PNG is for the person; the accessibility summary is for the session,
because an image costs vision tokens and cannot be grepped, diffed or asserted
on.

Two detectors. swallowed-errors — a catch that discards the error with no
comment saying why (79 here, and quiet on the ones this tree already explains).
dead-config — a shipped key nothing reads (9).

sieve.enabled defaults to true. The allowlist reaches log, probe and test
output, Read/Edit/Write are absent from it by construction, and an elided middle
spills to disk before the marker is written.

Changed

Three echo rules were measuring the harness rather than the work. Found by
working through what the echos filed against this workspace: sixteen hits, and
twelve of them were the echo's fault.

drift reported five sessions as having changed nothing. Every one had changed
plenty, through the shell — one session ran 217 bash calls holding 59
redirects, 42 heredocs, 11 git writes and 4 sed -i. bb uptake settled the
same argument for reading and states it plainly: a file opened with sed -n
counts as much as one opened with Read, because which tool a session uses is a
harness setting. writesFiles says the equivalent about writing. A shell edit
carries no hash and no path, so oscillate ignores it, which is honest: this
box cannot tell whether a sed -i put a file back to a value it already had.
Five hits become one.

diminishing fired on 15 of 15 eligible sessions here, at ratios between 1.62
and 3.25 around a median of 2.24. The docstring at the top of the file already
said why that is not a finding — every long session costs more per change
because the window is re-sent on every turn, and that much is arithmetic — and
the threshold was then set at 1.6, below the arithmetic. A rule that fires on
the middle of a distribution is describing the distribution.
diminishing_ratio is a multiple of this workspace's median now, which is the
move bb oversight already makes when it calls a file "2.6x this tree's median
of 135". Under five eligible sessions there is no median worth having and the
answer is unknown, never ok. Fifteen hits become one.

spin claims the same question cannot return a different answer. That fails
for a command watching something this box does not control — gh pr in four
sessions, curl in a fifth, git show in a sixth — ...

Read more

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 12:37
05ad31f

v0.4.0 — 2026-09-16

The release that stops recommending the box and starts running it.

bb uptake shipped in v0.3.0 to answer one question: of everything bb wire
installs in front of an agent, what does a session reach for? Sixteen
sessions later the answer was in. The MCP tools fired in 0. bb pinpoint
fired in 3 of the 13 sessions that opened five or more distinct files — and
those thirteen opened between 30 and 598 files each. The reference tables were
read in 5 of 16 sessions that ran a search.

Every surface was advisory, and an advisory surface loses to the model's own
habit about three times in four. So the work in this release moves the locating
to the side that does it for nothing, refuses to hand over what it already
found, and then makes it possible to say whether what came back is finished.

The completion half is not ours: the gate checker is
unlazy 2.1.0 (MIT, commit 1667149),
vendored and renamed rather than rewritten. src/finish/vendor/PROVENANCE.md
records every rename and why nothing else moved.

Measured

bb bench run — 12 tasks derived from this repository's own open findings. Both
arms are token counts over text on disk; neither calls a model.

bare    783.9k   search the tree, open the top 10 files whole
packed   32.7k   one pinpoint prompt per task
saved   751.2k   95.8% of the bare arm, 24x less context

bb bench swebench run --n 12 — SWE-bench Verified, 12 astropy instances, run
against a worktree at the commit before the ranker change and again after it.
Identical instance sets, verified by hash.

before after
gold files in scope (budgeted to be read) 2/16 — 12.5% 8/16 — 50%
gold files named (in scope, or pointed at) 6/16 — 37.5% 11/16 — 68.8%
bare arm (top 10 files read whole) 5/16 — 31.3% 5/16 — 31.3%
instances with every gold file in scope 2 6
packed context 35.8k — 91x less 50.5k — 64.5x less

Before the ranker change the packed arm localised worse than reading ten files
at random
: 12.5% against the bare arm's 31.3%. The brief was cheap and pointed
at the wrong files. The trade is stated rather than buried — packed context rose
41%, so the context saving fell from 91x to 64.5x, in exchange for four times the
localisation.

Not measured here: resolve rate, patch correctness, latency, tool calls, billed
cost. Those need the official harness, a model, and an A/B of real sessions.

Added

  • The enforcement axis (wire.auto_pinpoint, wire.serve_from_brief,
    wire.guard_searches).
    UserPromptSubmit now runs bb pinpoint on a
    task-shaped prompt instead of suggesting it — 0.47s measured against a 15s hook
    budget, because every input pinpoint reads is a stored artefact and it computes
    none of them. What enters the window is the map, about 420 tokens; the quoted
    regions stay on disk until a read asks for one, because paying for every
    located region on every prompt spends the saving on regions the session never
    opens.

    PreToolUse then answers from that brief. A read whose region is already
    quoted is DENIED and the quote comes back in the denial reason: measured on
    src/wire/brief.js, 392 tokens of region in place of 11,185 tokens of file. An
    exact duplicate read is denied. A single-identifier search the declaration
    index answers is denied with the rows. A file cut from the scope for budget is
    asked about rather than refused, because the brief's own instruction is to
    name it and why.

    A file in SCOPE is never fully blocked. Claude Code requires one successful
    read of a file before it will edit it, so blocking the scope blocks the change;
    the denial says to read the range, that read is allowed, and it costs the quote
    instead of the file. Grep is the declared tool and Bash is where the
    measured transcripts read and search, so both are guarded.

  • bb pinpoint gaps / bb pinpoint next. bb scan, bb oversight scan and
    bb auditor gate each produce a statement about the tree and then stop one
    step short of the window, leaving a session to spend its first turns finding
    out where the statement lives. Every open finding, oversight measurement and
    declared standard that failed or has no evidence now becomes a located,
    quoted, budgeted brief before a model sees it; next makes one the session's
    active brief, which is the record the guards answer from.

    unproven never collapses into failed. A standard nobody has looked at is a
    gap in the EVIDENCE and the brief says so, because a worklist that quietly
    promotes "nobody looked" to "this is broken" is the failure the auditor exists
    to remove.

  • bb finish — the acceptance ledger. The one axis this factory measured and
    never closed: bb auditor declares the bar before the first edit and
    bb pinpoint carries the gate into the brief, and then a session reported
    "done" with nothing here able to contradict it.

    The vendored checker already holds the parts that are easy to get subtly wrong
    — an approval binding the command, the expectation, the resolved cwd, the
    shell, the timeout and the whole inherited PATH; a definition digest, so
    editing a CHECK: invalidates the evidence that CHECK: produced; an
    abandonment that exits non-zero and reports a handoff instead of dropping the
    gate. What bundlebox adds is the join: bb finish init derives GATES.md from
    the active brief's scope, the detected gates and the open findings in that
    scope, because a ledger written from a blank page is a ledger nobody writes.

    bb hook stop is a fourth verification layer that executes nothing: it reports
    a declared gate still unmet when a session is about to say it is done, and is
    silent where there is no GATES.md.

  • arc/ — a Rust index compiler. The search guard asks one question per tool
    call: is this name declared, and where. It answered by reading 20,000 lines of
    symbols-*.md and running a regex per line. arc compiles those tables into
    one 86KB binary index — two sorted id arrays, one by lowercased name and one by
    the REVERSED name, so exact, prefix and suffix are each a single binary search,
    a suffix being a prefix of the reversal. 2,153 declarations, 20ms to compile.
    Dependency-free, like the kernel and for the same reason: it must build on a box
    with rustc, cargo and no network.

    per call
    markdown scan 1.80 ms
    arc index, cold 0.14 ms 13x — what a hook process pays
    arc index, warm 0.073 ms 25x — a CLI or MCP process
    spawning arc 2.28 ms 1x — the process is the cost

    So the win is the FORMAT, not the language. arc compiles; src/arc/read.js
    reads in-process, where the 13x lands; and a test holds both readers to one
    contract on every query shape, which is the only thing that keeps two
    implementations equal.

  • bb lathe — LATHE-1, the automation engine. Not a language model, and
    nothing in its path calls one: four count-based models over artefacts this
    workspace already produced. Closed, aperiodic, contiguous sequences over what
    sessions ran (PrefixSpan with a projected database and BIDE's
    closedness test, in bundlebox_expert/sequences.py); the expert's existing
    logistic outcome model, read rather than retrained; the janitor's decayed
    memory; and prefix entropy over the arc index.

    It emits what the workspace did by hand more than twice: tagged scripts a habit
    could become (@turns, @needs, and @safe false, because the model knows
    what ran and not whether running it unattended is safe), the regions the tree
    already repeats, the prologue a new file in each group starts with, and the
    prefixes that complete to exactly one name. Every row names the support behind
    it.

  • bb env — is the environment set up. That question had no answer.
    bb init wrote a config file and stopped, so a fresh workspace had no tables,
    no index, no findings and no page, and nothing said so — the first session
    searched the tree, which is the cost this box exists to remove. Nine
    artefacts, each with the verb and the declared gear that produces it, whether
    it is on disk and how old. Measured on a fresh workspace: 1 of 9 after
    bb init --apply, 8 of 9 after one bb env up --apply
    , commandcenter page
    included.

    One row is deliberately not a gear's to build. The janitor writes the memory
    files a PERSON wrote, so bb env reports where it comes from — the SessionEnd
    hook, or a hand — instead of promising to run it on a tick.

  • Two skills, installed by bb wire and removed by bb unwire. bb-finish
    carries the ledger discipline; antislop carries the fifteen prose rules. A
    skill is the only surface this box installs that costs nothing until it
    triggers — the instructions block arrives in every system prompt and is billed
    whether the session was about it or not.

  • Anti-slop on the axis it never covered. bb slop has always run over every
    brief, commit message and PR body this factory emits. What it never saw was the
    markdown the MODEL writes into the tree, which the next session re-reads and is
    billed for again, every time, until somebody deletes it — a document is the only
    artefact here that charges rent. The PostToolUse hook now lints a prose file
    the agent just wrote and names the rule, the line and the token cost. Advisory
    by design: a hedge is sometimes the honest word, and a hook that rewrites
    somebody's sentence unasked is worse than the sentence.

Changed

  • The ranker scores specificity and path, not just match quality. Enforcing
    a brief raises the cost of a bad locate, and the locator had two holes. "wire
    the pre-read guard so it denies a read the pinpoint brief already quotes"
    ranked slop/index.js, tokens/ledger.js and auditor/charter.js first —
    each declares a symbol called exact...
Read more

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 14:41
56fb5c6

v0.3.0 — 2026-09-15

The release that looks at the input axis, asks whether any of the wiring was
ever used, and stops publishing a green workflow over a red job.

Two verbs came from a second read of caliper
and Chisle — the first pass took an
ablation and a losses column from them, and walked past the half of each that
mattered more. docs/prior-art.md records both rounds, including what was
deliberately not taken.

Added

  • bb sieve — the input axis. Every other verb in this box shrinks what a
    session is asked to read. This one shrinks what it reads because it ran a
    command. A tool result is billed when it arrives and again on every later turn,
    because the whole window is re-sent, so shrinking it once is paid back every
    turn that follows. Three tiers: scrub (lossless — ANSI, blank runs, repeated
    lines), dedup (byte-identical to this tool's previous output in this
    session
    ), elide (head + tail, with error-looking lines carried out of the
    middle). Read, Edit and Write are absent from the allowlist by
    construction — their output is the text a later exact-match edit is written
    against — and the elided middle spills to disk so recovery is a grep rather
    than a re-run of a command that may not be idempotent. Installed by
    bb wire --apply as a PostToolUse hook.

    Off by default, and the measurement is why. bb sieve replay pushes real
    transcripts through the same pure transform for nothing. Across four
    workspaces on one box the saving ran 1.8%, 15.9%, 18.7% and 34.1% of all tool
    output — the largest sample, 29 sessions, is the 1.8% — and in all four it was
    almost entirely the lossy elide tier; scrub and dedup together never
    reached 3% of the win. So there is no lossless default worth shipping, and the
    lossy one buys a benefit that depends entirely on what a workspace's sessions
    run. The replay reports the split by tier; the default does not guess.

  • bb uptake — installed is not used. bb wire status reports that the
    instructions block, the MCP entry and the hooks are installed. The block is
    billed in every window of every session whether the agent obeys it or not, so
    a surface nothing reaches for is the most expensive kind of dead code this
    factory can write — and nothing could see it. This reads the transcripts for
    evidence that each surface was reached for, against the moment it was for:
    pinpoint only against sessions that opened five or more distinct files,
    tables only against sessions that ran a search. A file opened with sed -n
    counts exactly as much as one opened with Read, because which of the two a
    session uses is a harness setting and not a fact about the work. Heredoc
    bodies are stripped before a command is read — without that, every document
    this box writes about itself reads as a session that ran half the CLI.

    The three surfaces that arrive in the system prompt — the block, the
    SessionStart context, the read guard — are reported as not observable,
    with the reason
    , and never as 0%. A transcript cannot answer for them, and a
    number meaning "nothing was checked" is the one thing this box does not print.

  • docs/agents.md. Integrating bundlebox into all ten agents it knows,
    wiring one it does not, and driving agents as lanes. The surfaces, the six
    hooks, the nine MCP tools, the custom_command tokens, and what bounds a
    lane.

Fixed

  • Windows CI was red and could not fail a build. the kernel and the JS engine agree about a run step with body keys failed on all three
    windows-latest jobs for as long as the log history goes back, while
    continue-on-error kept the workflow green over it. Two defects, and the test
    was catching the first correctly:

    1. The two engines picked different shells. src/cookbook/engine.js
      hard-coded bash -lc; the kernel had already decided in
      kernel/src/gate.rs::shell that Windows gets cmd.exe, because hard-coding
      bash there turned every command into a spawn error rather than a verdict.
      Every GitHub Windows runner ships git-bash, so the JS engine found one and
      passed while the kernel used cmd.exe and failed — one corpus, two shells.
    2. The test was a probe of shell quoting. It built node -p "JSON.stringify({\"screen\":…})", and bash and cmd.exe do not agree about
      \". The payload now goes to a script file and the command is
      node emit.js.

    The shell a command runs under is one fact and now has one implementation,
    exec.shellCmd, mirroring the Rust line for line. It replaced four
    hard-coded bash -lc sites and one inline duplicate. Consequence, stated
    rather than discovered later:
    a piped command on Windows now loses
    set -o pipefail in the JS engine as it already did in the kernel — the
    documented gap, in both engines instead of in one.

  • src/bridge/index.js recorded a gate that never ran as a gate that
    failed.
    Its acceptance command hard-coded bash, so on a box without one
    spawnSync returned a null status, which read as accepted: 0 — the exact
    misreading kernel/src/gate.rs warns about in its own comment.

  • bb runbook up could not spawn on a non-Linux box. The no-cage fallback
    hard-coded bash. Its other bash -lc, the one handed to systemd-run, is
    deliberately unchanged and now says so: that branch is gated on
    useSystemd(), and a box reaching it has bash by construction.

  • bb failsafe run <op> --apply had the same hard-coded shell.

  • Tool results carry their tool. Every transcript reader — claude, codex,
    gemini, opencode — now records the producing tool's name and id on each
    result. A result was an anonymous blob before, which an allowlist cannot work
    against.

Changed

  • Windows gates. continue-on-error is gone from the matrix. It was
    best-effort while the shells disagreed; with that fixed, a Windows break is a
    failed build again rather than a green workflow over a red job. Every step
    after npm test — the scan, the doc-drift check, the corpus validation — ran
    on Windows for the first time in the process, because the job had never got
    past the tests to reach them.

  • A guard against the next hard-coded shell. The defect recurred four times
    because each caller reached for bash -lc on its own and nothing was
    watching. A test now walks src/ for the literal argv form and fails on any
    hit not allowlisted with a stated reason. It found the bridge site the
    moment it was written. The allowlist is checked for rot in the same test: an
    entry that no longer matches is an entry permitting nothing.

Corrected

  • One workspace is not a calibration. On bundlebox's own transcripts the
    ESTIMATE and MEASURED token figures for the sieve agreed to 0.2%, which
    read as a result about the estimator. Across three other workspaces they
    diverge by up to 58%. docs/prior-art.md records the correction beside the
    claim.

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 09:34
7a0b508

v0.2.0 — 2026-09-15

The release that measures itself. A new verb reports what packing a task is
worth with both arms measured, the corpus over bundlebox actually exercises
bundlebox, and five files that had grown past the point where a session reads a
region instead of a file are split.

Added

  • bb bench — the ablation. The same task twice: BARE searches the tree for
    its terms and opens the top --cap files whole, PACKED is the one
    bb pinpoint prompt for it. Both arms are token counts over text on disk, so
    neither calls a model and two runs on one tree return the same number —
    asserted in CI and in bb selftest. A task that costs MORE packed than bare
    is printed in the table, not dropped. bb bench init derives a suite from the
    open findings that name a file; bench init and bench run are stages of the
    measure gear, so the number refreshes on the same tick that folds the ledger.
  • The command centre reports it. Four stat cards, each tagged MEASURED or
    ESTIMATE so the two cannot be read as one kind of number, and two inline-SVG
    charts: bare against packed per task, and the share saved across every run. No
    CDN — a chart that needs a network is a blank rectangle on the box this page
    is designed for. New routes GET /health and GET /api/bench.
  • An ambiguity ledger on every brief. bb pinpoint now states what the
    brief does NOT settle — no gate detected, nothing in the symbol tables
    matched, no region located, no evidence on file, a scope cut to fit, a
    statement too thin to locate anything — scored, with the reasons kept separate
    from the score. Stated, never filled in by guessing.
  • anti-slop detector. Eight JS/TS patterns that discard evidence or pay a
    quadratic cost: adjacent eager filter/map, a reducer copying its
    accumulator per element, chained assertions, any in a contract, open
    dictionaries, mocked modules, Reflect escapes. One rule firing once is
    info and can never be promoted into work; three rules in one file is
    medium.
  • A corpus over bundlebox itself. Twenty scenarios, fifty-five steps — the
    CLI's dry runs, the MCP handshake, the command centre's four routes, the
    bench's own determinism — committed and run in CI against a live command
    centre. Coverage 96.6%. A corpus persona can now declare excluded: a
    capability it must not run, with a reason, leaves the denominator instead of
    being an open gap forever.
  • bb help is a table. Verb, effect, what it does, grouped by the question
    each group answers. The effect column reads spends / writes --apply /
    records / reads, so which two verbs can cost money is read rather than
    remembered.
  • docs/prior-art.md: seven projects read, what came back and what did not.

Fixed

  • bb cron install wrote a line that ran nothing. No --apply, so the
    unattended worker reported would-run every thirty minutes and produced
    nothing indefinitely while looking installed. It carries the flag now, and
    refuses to install if the gear reaches a verb that can spend — checked against
    the gear spec rather than assumed. A selftest holds both halves.
  • The coverage matcher compared the first token, so one step running
    bb scan reported all twenty-three bb * capabilities as covered and a
    one-step corpus read as 100%. It matches the whole command, understands
    node bin/bb.js scan as the same capability as the documented bb scan,
    treats a declared --apply as significant, and matches each segment of a
    shell line.
  • bb genesis re-seeding a corpus rewrote persona.json from a fixed field
    list and silently dropped every key not on it.
  • bb simulate merged findings only when a run produced some, so findings
    from a run against a service that was down could never be closed by a clean
    run. A clean run is the evidence that closes them.
  • The simulations frame read every run ever stored, so one bad run poisoned
    simulation-error-pct permanently. It reads the latest run per profile; the
    history stays on disk for bb simulate show.
  • A deliberate 404 probe was reported as a phantom call against an undeclared
    route. A step asserting absence is not evidence of a missing route.
  • World derivation extracted a formula (projected = overhead + ...), a cd
    and prose lines from fenced blocks as command capabilities, each of which
    stayed permanently uncovered.
  • .gitignore excluded directory contents rather than directories, because git
    cannot re-include a path inside an excluded directory — written the old way,
    the corpus would never have shipped.

Changed

  • Five files past a third of one session's payload are split along the seams
    they already declared, with each entry point kept as a barrel so no caller
    outside the directory changed: kernel/src/scenario.rs →
    scenario/{expect,steps}.rs; src/snapgen/tables.js →
    {walk,symbols,views,sources}.js; src/git/index.js →
    {repo,commit,forge}.js; src/pipeline/runner.js → {facts,report}.js with
    runGear split into decide() and execute(); src/genesis/index.js →
    {world,packs}.js. src/commandcenter/page.js and
    src/designlabs/scaffold.js crossed the same bar and are split too.
  • The workspace's runbook service is the command centre, which answers
    /health; bb runbook up starts it and the smoke profile runs against it.
  • CI runs the corpus, the simulation, the bench determinism check and the evals.

Measured

225 node tests, 23 expert tests, 25 selftest checks, 0 lint errors, 0 open
high-severity findings, 10 of 10 pipeline stages holding.

On this tree, twelve bench tasks: 700.3k bare against 34.6k packed. That
percentage is a property of the tree as much as of the tool, which is why
bundlebox-projects/tokenlab sweeps it across three tree sizes and three read
budgets and reports 78%–97%. What holds across all nine of those rows is the
middle column: the packed arm is constant. A pinpoint prompt costs what the task
costs; the bare arm pays for the repository.