Skip to content

bench(lab): the authoring loop, the miner and the candidate chain - #280

Merged
luuuc merged 9 commits into
mainfrom
lab/06-02-findings-and-candidates
Aug 17, 2026
Merged

bench(lab): the authoring loop, the miner and the candidate chain#280
luuuc merged 9 commits into
mainfrom
lab/06-02-findings-and-candidates

Conversation

@luuuc

@luuuc luuuc commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Why this PR exists in this shape

Cycles 05 and 06 were built as eight stacked branches, one per pitch, each with its own PR. Merging that stack deleted each base branch as it went, which closed three of the PRs pointing at it and left the rest merged into intermediate branches. Only #272 reached main.

Every commit survived on this branch. This PR carries the remaining seven pitches onto main in one merge. The per-pitch review record stays readable on the original PRs: #273, #274, #275, #276, #277, #278, #279.

Nothing was rebuilt or squashed. The commits are the ones those PRs described, in order.

What lands

Cycle 05, the authoring loop

  • The levers and the ceilings (bench(lab): carry the rejection forward and bound the loop #273). A re-entry that carries no table is refused; nothing is deleted on re-entry; re-anchoring is its own decision and never a side effect of a re-question. The authoring ceiling parks after the sixth cycle and only a deliberate resume re-enters it. Spend is recomputed from the run tree rather than accumulated, so a restart cannot spend the budget twice.
  • The plan files (bench(lab): write the method down as the phase plans #274). Eleven plans, one per phase, each declaring its input artifact, its output artifact and its verdict enum, checked mechanically against the graph in both directions. The precedents survive. The five killers move to lab/KILLERS.md.
  • Status (bench(lab): report where a campaign stands from its run tree #275). sense-lab status, derived entirely from the run tree. The uncomfortable rows lead: incomplete cells, burned arms, orphaned run directories, parked repositories, spend against the ceiling. No hand-maintained resume file.

Cycle 06, miner, candidates and replay

Test Plan

  • make ci green on this assembled branch: build, tests, per-file coverage floor with no new exception, zero complexity suppressions, lint clean, total coverage 96.0%.
  • All eight pitch commits verified present as ancestors before opening this PR.
  • Each pitch's own test plan is on its original PR.

luuuc added 9 commits August 16, 2026 19:10
Authoring is a cycle, not a line, and what happens on the second attempt
is where the method lives. Three verdicts send work back, and each is an
opportunity to lose the thing that made the previous attempt worth
improving. The instinct when a draft fails is to clear it and start
clean, and the cost of yielding to it is invisible: six unrelated
attempts look exactly like six iterations from the outside.

lab/internal/loop holds a repository's position and refuses a re-entry
that carries no table. Nothing is deleted: the attempt is filed with its
anchor, its draft and the rejection, and the next attempt answers every
rejection so far rather than only the last. Re-anchoring is its own
decision with its own verdict; a re-question never touches the anchor,
and a NO-ANCHOR records that one is owed without discarding the one that
failed.

Both ceilings refuse rather than warn. The authoring ceiling parks after
the sixth cycle and is re-entered only by a deliberate resume, which
hands a fresh cycle count and keeps the history. lab/internal/budget
reads spend from the run tree every time instead of holding a counter, so
a restart cannot spend the budget twice: the total is the size of a set
of run directories, which makes double-counting unrepresentable rather
than merely guarded against.

A campaign runs unattended for hours, so no phase may ask a human
anything. The lab boundary test now walks every production file for a
read of os.Stdin and allows it only where a stream is handed to a
command.
This is the method. Everything else in nine cycles is machinery that
runs it. The binary holds no prompt logic by design, so how a scenario is
crafted, how a mini-bench is read, how a session is expanded, how a pay
call is made and how a win is confirmed all live as prose a human wrote.

The risk is specific: a rewrite ships a cleaner engine on day one and
quietly loses the discipline, because the scoreboard stays green either
way. These files and LAWS.md are the only defence.

Eleven plans, one per phase in the graph, each declaring the phase it
belongs to, the artifact it reads, the artifact it writes and its verdict
enum. lab/internal/plans checks all four against the graph in both
directions: a plan naming a verdict the graph does not know cannot be
routed, and a plan silent on a verdict its phase can emit is a case with
no instruction, which is how a phase writes nothing and stalls a campaign
unattended.

Porting found a hole. The old expand plan emits REQUESTION when the
discriminator step cannot survive verbatim, and the graph gave expansion
AUTO alone. REQUESTION joins its enum, the re-entry table and the lever
list, because a lever a recorded campaign used and the table does not
carry is a hole in the test set.

The five killers move to lab/KILLERS.md, tracked beside LAWS.md, since an
instructions file outside the repository is one a fresh session does not
have.
A campaign runs unattended for hours and is picked up later, often by a
session with no memory of it. The retired instrument answered "where does
this stand" with a page assembled from a results tree, two state files, a
banked log and a ledger, plus a hand-maintained resume file that goes
stale by construction. A stale resume line is worse than none, because it
looks current.

sense-lab status derives everything from the run tree. Position is the
furthest phase of the latest cycle whose artifact is on disk and the
first whose artifact is not, so nothing hand-maintained can disagree with
what is there. The page carries the banner saying so.

The uncomfortable rows lead rather than being summarised away: incomplete
cells with their burned and unusable arms named, orphaned run directories
that spawned and recorded no terminal state, parked repositories, and
spend against the ceiling. A status that shows only progress is a status
nobody trusts twice.

Read returns a struct and Render takes one; the only consumer of that
string is a printer, and nothing else in the tree imports the package.
Parsing your own report is how a display format silently becomes a data
contract.

Deriving position found that indexing is per repository rather than per
cycle: looking for the index under every cycle would report every
re-entered repository as waiting on a scan that already happened.
A margin says Sense helped. It does not say what to fix, and a bench that
produces numbers with no route back to a product surface banks wins and
teaches the product nothing.

Three detectors over the runs that already happened: a gold row the
answer cited that a resolver was asked for and never returned, a symbol a
resolver answered with different file counts across runs, and a resolver
call that returned nothing. Each has a real recorded example, and four
more described in the retired tree are deliberately absent because a
detector with no real example does not ship.

Detection is post-run and the type says so. Every detector takes a
Completed, whose only constructor demands the terminal outcome the run
recorded, and a run that never reached one has no outcome to hand over.
So the miner cannot be consulted before spending and cannot become a
screen. That rule is not caution: a pre-run oracle reported 16 of 16 gold
dependents resolved and zero failures on the exact repository and anchor
where the miner found six cited-not-returned rows, 33 minutes apart.

The coverage report shares its input with the detectors and nothing else,
because one derived from what the detectors inspected would answer "what
did we look at" while appearing to answer "what was exercised". Every
surface appears, including the zeros: a missing row reads as a row that
is fine.

Fixtures are six real captures from the campaign whose misses were found
by hand, five models between them. Run over them, the miner names the
three misses that block reports.
The miner produces observations. Between one and a product change there
are three steps that otherwise happen in someone's head and in prose, and
the retired tree shows both failure modes: one campaign's miner output
sits in a log file with an empty findings directory beside it, and the
findings that were written up live as three loose markdown files under a
different campaign, connected to nothing. "Why was this change made to
Sense" is answerable only by someone who remembers.

Three records, each pointing at the last. A finding cannot be recorded
without a surface or without evidence. A candidate cannot be recorded
without a finding that is actually on disk, because a change with no
observed problem behind it is taste. A validation cannot be recorded
before its candidate wrote its hypothesis down, because a hypothesis
written afterwards is a description.

Evidence is a path into the run tree, never a copy: a finding that
embedded a transcript excerpt would drift from the transcript the moment
either was touched. And a finding inherits the state of its evidence.
Invalidating a run marks every finding that cites it, nothing is removed
from the evidence list, and a finding whose support is entirely withdrawn
is marked rather than deleted, because it was once observed.

Ids are derived from what a record is about, so re-mining the same corpus
updates rather than duplicates. It is a record and not a tracker: no
states, no assignment, no triage, no priority.
A candidate change to Sense has to earn its way in, and "earn" has a
definition: it moves the cells its hypothesis named, it does not regress
the ones it did not, and the movement is not confined to one model.

Three checks, and every result carries what qualifies it. The corpus size
travels with the regression result, so a pass reads as "four of four
held" rather than as "no regressions": four cells is thin enough that a
change can pass and still be wrong. A candidate whose movement was seen
on one model is recorded as single-model, never as confirmed. And the
blind spot is measured rather than assumed: when the corpus exercises
none of the surface a change touches, the pass says nothing about it and
the result says so.

A cell holds if it lands inside its own recorded spread, because a suite
demanding an exact number would fail on the instrument's own noise. A
target that did not move at all has not moved as predicted: a hypothesis
that survives "nothing happened" predicts nothing. And a corpus made only
of the cells the hypothesis named is refused, because that is a change
measured against its own intent.

Nothing here ships anything. The numbers decide the verdict and a human
decides to ship, and a rejection is recorded with its numbers rather than
discarded, so the next person with the same idea finds the measurement
instead of repeating it.
Everything measured so far has been measured against recordings. That
proves the pure layer reads the past correctly and proves nothing about
whether the instrument can produce a result: between a recorded
transcript and a live cell sit isolation, supervision, the tee, the
session, the judge and the verdict layer, and every one of them is new.

This is what surrounds the live run. The model is pinned, because a
banked number is a number for a model: one recorded cell measured +0.625
on one model and 0.00 on its successor, because the newer baseline closed
the axis the scenario tested, and replayed on the wrong model it looks
like a broken instrument while being a working one. An unreachable model
picks a different CELL, never a different model, and the cell that can no
longer be replayed is reported rather than dropped.

The pick takes the cell whose worst run is furthest clear of the bar, not
the highest margin. Against the real corpus that is mastodon at 0.775
with a spread of 0.050; discourse banked 0.708 with a spread of 0.417, so
its worst run lands at 0.291 and a disagreement there could not be told
from noise.

Agreement is the cell's own recorded spread in both directions, and
landing outside it is kept separate from still clearing the bar. A
disagreement walks instrument, then environment, then a second pair,
then the world: one live pair is n=1 against a spread reaching 0.250, and
without that stopping point the ordering is a licence to debug forever.

And the pinned inputs are diffed, because "nothing was adjusted to make
it land" is a claim and a replay tuned until it agrees has proven that
tuning works.

The live paid run itself is outstanding and is a human trigger.
bench(lab): decide whether a candidate earned its way in
bench(lab): choose and judge the replay of a banked cell
@luuuc
luuuc merged commit 5a472f4 into main Aug 17, 2026
5 checks passed
@luuuc
luuuc deleted the lab/06-02-findings-and-candidates branch August 17, 2026 07:24
@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.16157% with 26 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lab/internal/record/record.go 92.80% 5 Missing and 5 partials ⚠️
lab/internal/status/status.go 93.04% 4 Missing and 4 partials ⚠️
lab/internal/mine/mine.go 97.26% 3 Missing and 3 partials ⚠️
lab/internal/replay/replay.go 97.14% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant