Repository navigation
v0.4.0
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).UserPromptSubmitnow runsbb pinpointon 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.PreToolUsethen 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.Grepis the declared tool andBashis where the
measured transcripts read and search, so both are guarded. -
bb pinpoint gaps/bb pinpoint next.bb scan,bb oversight scanand
bb auditor gateeach 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;nextmakes one the session's
active brief, which is the record the guards answer from.unprovennever collapses intofailed. 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 auditordeclares the bar before the first edit and
bb pinpointcarries 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 inheritedPATH; a definition digest, so
editing aCHECK:invalidates the evidence thatCHECK:produced; an
abandonment that exits non-zero and reports a handoff instead of dropping the
gate. What bundlebox adds is the join:bb finish initderivesGATES.mdfrom
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 stopis 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 noGATES.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-*.mdand running a regex per line.arccompiles 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 arc2.28 ms 1x — the process is the cost So the win is the FORMAT, not the language.
arccompiles;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, inbundlebox_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 initwrote 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 onebb 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, sobb envreports where it comes from — the SessionEnd
hook, or a hand — instead of promising to run it on a tick. -
Two skills, installed by
bb wireand removed bybb unwire.bb-finish
carries the ledger discipline;antislopcarries 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 slophas 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. ThePostToolUsehook 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"
rankedslop/index.js,tokens/ledger.jsandauditor/charter.jsfirst —
each declares a symbol called exactlyread,guardorbrief— and never
putsrc/wire/hooks.jsin scope at all.Two corrections. Inverse document frequency over the candidate set, so an
exact match on a name the tree uses everywhere stops outscoring a loose match
on a rare one. And path evidence, because a directory namedwireis a
deliberate statement about what a file is for and was worth nothing: a file the
path names now enters the ranking whether or not a symbol matched, which is
what had kept the answer out of the candidate set entirely. An explicit file is
PINNED rather than scored, since specificity and path together can beat any
fixed constant and losing an argument to the ranker is not something a human
naming a file should have to do. -
The grow loop's grep fallback fires on a NOISY index, not only a silent
one.sym.length < 3could not tell a real localisation from a statement
whose words are common symbol names here, which returns dozens of hits and
answers nothing. It now counts only the hits whose term is rare enough to be
about WHICH file. -
The cron tick rebuilds the page.
watchis the gear that rebuilds the
commandcenter, andwatchwas not in thefactorychain. Every thirty minutes
the tick folded the ledger, measured the window and rebuilt nothing, so the one
page this workspace has showed the state of whenever somebody last ran
bb commandcenter buildby hand.orientalso gained the arc index (directly
after the tables it derives from) and the located worklist;buckmastergained
LATHE-1, after the models it reads.unsafeVerbsstill returns empty for the
whole chain: nothing on an unattended tick can spend. -
The brief record is one slot per SESSION. A single
brief.jsonis wrong
the moment two sessions share a checkout, which is the normal case here and not
the edge one —bb uptakecounts sixteen sessions on this tree, and two others
were running while this was written. Session B's prompt overwrote session A's
brief, A's guards refused to answer from a record belonging to B, and the whole
benefit quietly stopped arriving. Nothing broke; it just stopped working. Swept
on session end, because age is the only signal a hook has for a session that
ended without reaching it.
Fixed
-
bb wire --applyshipped a settings file with the read guard missing.
addClaudeHooksstripped bundlebox's hooks once per ROW instead of once per
event. Invisible while each event had one handler, and wrong the moment one had
two: thePreToolUse(Read)row was installed by the first row and stripped
again by the second, with no diff to say so. -
rel()resolved a relative path against cwd, not the workspace root. It
usedpath.resolve, so the two agreed only while cwd happened to BE the root.
Anywhere else —bbrun from a subdirectory, a hook whose cwd the harness
chose, any test with a fixtureBB_ROOT—rel("src/a.js")walked out of the
workspace, failed the..test and came back as an absolute path into a
different tree. It surfaced as a pinpoint anchor pointing at a same-named file
in the real repository while the brief described the fixture. -
Every session ended by printing
[object Object]. The SessionEnd hook
stringified the objecttokens/session.jsreturns instead of reading the one
field meant for a person. -
Three false denials by the new search guard, each found by it refusing its
own author's search, each fixed with a rule rather than an exception. A
search restricted to a directory asks who CALLS a symbol, and a declaration
index has no rows for that. ONE identifier is the whole boundary of what an
index may refuse —grep "usage|process.argv|exitCode"was refused on the
strength of one of four alternatives, and a quarter of an answer is a wrong
one. AndparseBashsplit on|inside quotes, sogrep "a\|b\|c"became
three segments whose first looked exactly like a single-identifier lookup; the
shell tokenizer now respects quotes and lives incore/util.js, because two
copies of a parser are two behaviours. -
The sequence miner did not finish on real data. It counted each candidate
by scanning the whole corpus —|level| x |alphabet|scans per level — which on
sixteen sessions ran four minutes of wall clock for 4.8 seconds of CPU and was
still going. The cost was not the algorithm's shape but where the order was
read from: 1.4GB of JSONL parsed in full to recover one string per tool call.
ThePostToolUsehook now records the SHAPE when the command runs — forty
bytes, and never the argument, so the log cannot carry a secret somebody passed
on a command line — and the learn pass went from four minutes to 1.4
seconds. PrefixSpan with a projected database replaced the rescan. -
Six rows describing one habit. The pipeline here is fourteen verbs long and
MAX_LENwas 8, so every 8-item window had support 59 and every one of them
looked CLOSED — not because the data said so, but because growth stopped at the
cap and no 9-item extension existed to disprove it. -
cdwas the most frequent habit in the workspace. An agent prefixes almost
every shell call withcd <root> &&, so taking the first binary of each command
shaped the entire corpus ascd: 136 occurrences, lift 2.46. Shell grammar was
mined too —(node ; for i ; do curl ; break ; donewas reported as a habit
with a lift of 68. Neither is work a script could displace. -
out_of_scope -> out_of_scope, four hundred times over. The completion
table's prefix loop runs up to a name's own length, so a prefix that IS the name
completed nothing and was listed anyway. -
A derived ledger the checker refused to PARSE.
bb finish initleft every
EXPECT:blank, reasoning that a guessed success marker is an oracle that
cannot fail. Right about the risk, and it turned an unfinished ledger into a
malformed one and buried the instruction under three parse errors. A
placeholder no command prints parses, and the gate fails until somebody fills
it in — which is the honest state of work nobody has proved.
Corrected
-
Automating a surface must not make it unmeasurable.
wire.auto_pinpoint
runs the locator from a hook, and a hook leaves no turn in a transcript. The
change that finally made pinpoint fire on every task-shaped prompt would have
shown up inbb uptakeas a surface that still never fired — the verb whose
whole job is saying whether the wiring was reached for could not see the one
case where it always is. The per-session brief record is the evidence, and it is
the only evidence there can be. A separateautorow reports that case on its
own, because a session that calledbb pinpointitself and a session that had
it called for it are different facts. -
Spawning a faster implementation is not faster.
arc lookupanswers a
declaration query in 0.082ms against the markdown scan's 2.29ms, which is 28
times faster and completely irrelevant: the process costs 2.20ms, so end to end
the two were 2.28ms and 2.29ms. A guard that runs once per tool call cannot pay
for a process. The Rust binary stayed the compiler and the reference
implementation; the hot path reads its output in-process.