Skip to content

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 19 Sep 06:09
· 90 commits to main since this release
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