A lens has one charter, the one this pipeline ships; what a repository
knows that is not general to the lens - its platform and layers, the
record it keeps its decisions in, what its users already have, the
calibration entries that are its own - goes in a file the config names
for the lens (lenses.<name>.section), and Charter appends it after one
blank line. The charter sha in every reuse key covers the combined text.
The config refuses a path outside the repository - absolute, or
climbing out with `..` - by the check slots.dir had, now one both
settings share; a file missing or one the run could not use (a dangling
link, unreadable, not UTF-8) is a problem too, checked through the
reader Charter uses (Prompt::ReadSection), so a section is never
dropped in silence, and a name that is no lens is the unknown key it
always was. Nothing lists a directory, so what lies beside the files is
no concern of the config's: a directory listed would have to tolerate or
refuse every foreign entry. The run logs which charters got one.
The better-solution charter gains a consumer's general entry
(2026-10-31: a mechanism reworked a second time in one submission is the
signal to derive the fix at every layer the data crosses). The procedure
names the section wherever it names the charter: among the config's keys,
in the lens inputs, in the reuse rule (an edit to either re-runs the lens)
and in the feedback loop, which routes a repository's own lesson to its
section and a general one to the shipped charter.
Beside the push gates, crafter-review runs the pre-send review of a kernel
contributor's workbench: --mode series over a format-patch export with its
cover letter, --mode reply over a mail draft. The inventory partitions a
cover letter, a plain mail and the camera review's shape; ten kernel lenses
join the table with their gates (structure, code, artifact truth) and the
persona gate - one maintainer-persona instance per --reviewers entry, its
whole pass gating its blocks and pruning the units that are not theirs. A
series' code units are keyed on the code like the push gates'; every text
unit on its text, the references it cites and the anchor when it is dated.
The references an artifact cites are prefetched through utilities/lore.py
into the gate-state, with a digest in the run layer, and a persona's
calibration map is its lore listing. The kernel schema carries the persona
vocabulary, the kernel note kinds, the predicted review and the not-mine
list; the matrix the persona holes, the thin and the modelled units; gates
clean without a persona pass is GATES CLEAN. A reply's gate-state sits
beside the draft and is read once; a kernel mode needs --subsystem,
defaults the effort to max and the trees to work/linux,work/milos-linux,
and excuses no unit from the holes.
The kernel lenses and the common text ship verbatim; the table lists them
after the push gates' lenses and the config template holds them off, so a
repository that never turns them on changes nothing. Prompts, keys and plans
were checked byte for byte against the pipeline these modes replace, on its
own artifacts: two replies and a series, every system prompt, user prompt,
job and reuse key identical.
A directory can review repositories other than itself: a workbench whose
config, env file and records serve several, freezing each with --repo and
--out. The run took the working directory for the repository under review:
the prompt named it, the calibration was kept under it, and --trees fell
back to it. The run now reads the repository from the artifact's
freeze.json - the prompt names it and the calibration is kept under it -
and --trees defaults to what the record says: the worktree, else the
repository when it is not the working directory, else `.`, the same rule
that builds the printed run line. The working directory keeps its roles:
the config, the env file, the slots, the lens processes' cwd and every
logged path. A damaged record stops the run at preflight.
Jorijn: "all lenses off by default and opt in only". Config::Defaults()
gives every lens run: false; a config that turns on none - a missing
`lenses` key included - has the one problem that names the lenses, so
config, freeze and run all stop on it; the template ships every lens off,
init says to turn lenses on, and the "no lens runs" preflight names the
cause. A consumer config without a `lenses` key now needs one, as the CI
smoke test's does.
Proven with the lens commit before it: the suite (26 passed), and the
dry-run comparison recorded there.
Every claim in every commit message, and in the documentation the range
adds or changes, proven or gone. The lens's block kinds are text, doc and
slot: a message unit is keyed on its text, the base and the code up to its
commit (MessageDeps); a documentation unit on its file, its normalised
text, the base and the code of the whole range (DocKeyParts); a diffstat on
its text alone; whole:order and whole:absent are asked in slot passes of
its own, never stored. A documentation file's diff header and a part the
commit writes nothing in are a mechanical n/a. Budgets 60/40 for a text or
doc block, 50/35 for a slot; one read. Gate 4, after the proofs, so the
messages and documents are read once the code they describe has settled;
the owner's gate is 5.
The goldens that follow from the lens table are derived by
tests/Goldens/derive.py, which asserts that the recorded table still
reproduces the recorded entries before it writes.
Proven, with the opt-in commit after this one: the suite (26 passed); a dry
run of a scratch range (2 commits, a README with sections) by the previous
binary and this one with the three static lenses on and the rest off, 25
prompts and 4 system files byte for byte; with repo-facts on as well,
those files unchanged and 10 passes and 1 system file added.
The lens's firing proof in this pipeline is owed (the owner's go).
Jorijn: "there is no need to do a global cap anymore, the cap should be
128 per available account". `--concurrency` no longer defaults to 128:
the limits are each OAuth account's 128 (and the local login's), every
driver's counted, an API seat's own REVIEW_GATES_API_CONCURRENCY, and
free memory. `--concurrency N` still caps the machine when given; a value
below 1 is a usage error.
The dump's args.concurrency is null then. The board's driver settings
read `128 per account`, which every driver's `seats` shows as that
driver's width, and the `seats` header says a width is that driver's cap.
The Scheduler test runs 300 passes on two accounts: 256 at once, 128 each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anthropic is removing the 5-hour limit in stages (from 2026-10-31), and
an account without it reports the weekly window alone. Until now the pool
took such an account for an unprobed seat: always eligible, ranked after
every other, never held to the weekly cutoff, with no cap on its passes.
- The burst window is the 5-hour one, else the weekly one
(Seats::BurstWindow). An account whose burst window is the weekly one is
ranked by it and admitted under the 95 % cutoff with the passes in
flight projected on it, at the measured share per pass (0.04 at xhigh,
0.125 otherwise; REVIEW_GATES_LENS_COST_7D_PCT), and waits for the weekly
reset when over it.
- Every OAuth account and the local login takes at most 128 passes at once,
every driver's counted (REVIEW_GATES_OAUTH_CONCURRENCY): every run
measured with 149 or more in flight on one account drew usage-limit
rejections, none of the 119 with 139 or fewer.
- A 'concurrents' rejection pauses the seat for 20 s or its retry-after,
not until a window's reset (a seat sat two hours at 10 % on one).
- Board entries, the folded foreign record and the board's view carry
cost7d, so the drivers see each other's weekly load.
- `seats` and the log table say `no limit` in such an account's 5-hour
column; the board's others-line names its weekly projection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An independent check of every behaviour AGENTS.md was to describe:
- `--units ,` and `--only ,` skipped their empty items and so had no
filter left: a typo started a run of every unit. They are usage errors
now (exit 3).
- Two fixes the plan lists were missing: a dry run now clears the prompts
a dry run before it left in the dir they share (the report counted
them), and `config` reports an absolute functional.mcp.command that is
no program here - /usr/bin/npx inside the Flatpak, whose /usr is the
Sdk's.
- A patch that is not UTF-8 stops `run` at preflight (exit 3), as the
plan has it; it exited 1.
- An exception no command caught aborted the process (134), e.g. a home
that holds no lenses: it ends with its message and exit 1. An error
escaping `run` or `freeze` now also clears the signal handler's run and
sweeps what is left below the subreaper.
- `config -h` printed "unrecognized arguments" (exit 3): it prints the
usage, exit 0.
- An API seat's .claude.json is linked into place, so a second writer
never replaces the first's file and what claude wrote to it since.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found compiling the environment-variable table for AGENTS.md:
- A REVIEW_GATES_MEM_RESERVE_MB that is no number threw
std::invalid_argument, which nothing caught: the process aborted where
it should end with a ValueError, exit 1. nan and inf were an unchecked
cast. They raise ValueError / OverflowError now; a value past int64
saturates.
- An env file that is not UTF-8: `run` ended with exit 1 where its
preflight should stop it (exit 3), and `seats` parsed the raw bytes. Both
read it as strict UTF-8 text now.
- A broker address with a port past 65535 was cut to 16 bits; it is an
OverflowError now, uncaught (exit 1).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its subreaper status makes a daemon a git call started its child; at
exit it goes, as freeze's do. Never a process the run did not start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Runs test drives this binary as crafter-review on a scratch repository,
with a /bin/sh claude that answers every pass from its system file and
prompt: a dry run; a run SIGKILLed in its last gate (every pass goes with
it through its supervisor); the next run resuming the first gate from the
checkpoint, salvaging the killed run's outputs and buying only the pass that
never answered; a run with nothing left to buy; a FIX FIRST with
--no-reuse --no-salvage; a stop by the stop file and by SIGTERM, each
leaving no process behind; a stale stop file dropped by the run that claims
its dir; a stop between gates.
Two fixes it led to:
- A stop that lands while a gate's pool is set up is no longer lost: the
stop is checked under the same lock that publishes the pool.
- A run never takes the number of a dump on record. Past a killed run that
left no dump, a later run that bought nothing could have its number
taken again and its dump written over.
Drivers gains the --no-reuse store check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StoreRules (23 checks), StacksMemory (24) and ReviewFixes (19), each a
test of its own.
Run and SlotsPlan now have seams that start as the real ones: appReady,
probe, query, bypassesRls, migrationsAtHead, migrationsDrift, treeDrift,
promptFor, runPass, resumable, instrumentPreflight, and a Machine for
SlotsPlan. MigrationsAtHead is split out of MigrationsDrift for it; the
HEAD side is still worked out once per tree and base.
The Postgres stand-in and the raw broker call move to tests/Common, shared
by PgWire and ReviewFixes. ReviewFixes runs this binary as `sql`, and a
real server that resets every connection makes the startup message either
fail to go out or never get its answer - a DbError either way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run: gate 0 (the artifact, the trees, the inventory, the run lock, the
scratch dir, dispositions, reuse state, resume, reads per round, seats,
the board), the stack and migration preflights, the prompts, reuse of
whole and block verdicts, salvage, a gate on the pool, checkpoints, the
dump, the dry-run report and the unit-state store; slots_plan and
app_ready beside it. The CLI parses run's 28 options, types and choices
included.
A full run takes its lifetime lock beside the board before it joins it.
A signal - or the stop file `crafter-review stop` writes, looked at
every 2 s - stops the run once gate 0 is through, and the stop stays in
force for the gates after; before that it ends the process with its
descendants.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>