Skip to content

chore(lab): scope the bench to the repository and remove the campaign - #301

Merged
luuuc merged 2 commits into
mainfrom
chore/remove-campaign
Aug 20, 2026
Merged

chore(lab): scope the bench to the repository and remove the campaign#301
luuuc merged 2 commits into
mainfrom
chore/remove-campaign

Conversation

@luuuc

@luuuc luuuc commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Nothing in the Sense binary changes. This is the bench instrument, and it gets
one idea smaller: a repository is the unit of work. Benching one now takes a
single command with no setup decision in front of it, and the spend ceiling
that was written down but never enforced now actually stops a paid run.

Problem

sense-lab carried a container above the repository called a campaign, and it
required one before anything could happen: sense-lab repo mastodon/mastodon
refused with -campaign names the run tree the index artifact belongs to. That
is a decision demanded before it can be informed, in the run path, on a crank
that is supposed to run unattended.

The container held no fact of its own. Its spend ceiling was never enforced
(budget.Ceiling had no caller outside its own test), its arm matrix drove
nothing (plan.Expand has one caller, which prints a table), and its judge pin
has no consumer. Everything it grouped was already per repository: the run
tree, the authoring cycles, the position, the verdicts, the attempts. What was
left was a directory name, and it made the instrument harder to explain than
the thing it measures.

Summary

Removes the campaign and scopes the bench to the repository: its own run tree,
its own arms, its own spend ceiling of 40 paid runs, which probe now refuses
to spend past.

Changes

Admission and position

  • sense-lab repo drops -campaign. Run trees live under runs/repos/<id>,
    reached by -runs with a default nobody needs to type. The original
    invocation now works: sense-lab repo owner/name resolves, clones, pins,
    scans and records.
  • sense-lab status drops -campaign for -runs and reports every repository
    under it, each with its own spend against its own ceiling.

The bench matrix

  • plan.Campaign becomes plan.Bench, losing Key and Repos. One
    repository, its subjects, its arms, its pinned judge.
  • sense-lab plan -campaign <key> becomes sense-lab plan -repo <id>, reading
    lab/benches/<id>.json instead of lab/campaigns/<key>/campaign.json.

The spend ceiling, wired

  • 40 paid runs per repository over its lifetime, refused by probe before
    anything spawns. It is read off the tree on every call rather than counted in
    memory, so a loop resumed after a crash cannot spend the budget twice.
  • budget.ErrCeiling is a sentinel, so a refusal is tellable from a tree that
    could not be read. probe answers with its refusal code rather than its
    error code.
  • No flag raises it, and status lost its -ceiling flag: one constant feeds
    the page and the refusal, so the page cannot state a ceiling nothing holds.

Correctness found on the way

  • The handoff phase declared reads: campaign.json, a file that has never
    existed in any run tree. It now declares attempts/, which is what the plan
    actually reads.

Architecture Highlights

  • budget.Read is unchanged. It always walked whatever root it was handed, and
    only its argument moved from a campaign to a repository, which is the sign
    this was a naming convention rather than a mechanism.
  • Records stay global at runs/records/ rather than moving under a repository.
    A finding is about a Sense surface, not about the repository it was seen in,
    and scattering findings per tree is the exact failure the record package was
    built to close.
  • The bench matrix is its own file next to the repository file, not a block
    inside it. The two have different authors and lifetimes: what a repository is
    gets written by admission, how it is measured gets written by a person.

Breaking Changes

⚠️ Local only, and the lab ships no repositories or campaigns, so there is
nothing to migrate in this repo.

  • sense-lab repo -campaign <dir> is gone. Drop the flag.
  • sense-lab status -campaign <dir> becomes sense-lab status (or -runs).
  • sense-lab status -ceiling <n> is gone.
  • sense-lab plan -campaign <key> becomes sense-lab plan -repo <id>, and
    lab/campaigns/<key>/campaign.json becomes lab/benches/<id>.json with
    repo in place of key and no repos list.
  • An existing run tree at runs/campaigns/<key>/<repo> moves to
    runs/repos/<repo>, and its records/ moves to runs/records/. No code
    migrates it, and nothing reads the old path.

Test Plan

  • A probe past 40 paid runs is refused with the refusal code, before a cell
    directory is created
  • A neighbouring repository's 40 paid runs do not stop a repository that
    has spent nothing
  • Spend is reported per repository: two paid runs on one and one on
    another, with mini-bench runs excluded as unpaid
  • A ceiling refusal is identifiable via errors.Is, so it cannot be
    confused with an unreadable tree
  • status reports the default root without being told, and rejects
    -ceiling
  • A bench file naming a different repository than the one it is filed under
    is refused, as is an unknown field in it
  • sense-lab repo -show mastodon/mastodon resolves, prints the position
    and exits with the shown code, with no campaign anywhere
  • go test ./... passes
  • make ci green: per-file coverage gate passes, 96.1% total, 0 lint
    issues, complexity ledger at 0
  • qlty check reports no issues, with no smells in any changed file
  • sense and sense-lab binaries build cleanly

The campaign held no fact that was not either a decision about one repository
or a directory name. Its spend ceiling was never enforced (budget.Ceiling had
no caller), its arm matrix drove nothing (plan.Expand fed a printer), and its
run tree was a folder above the repository's own. Everything it grouped was
already per repository: the run tree, the authoring cycles, the position, the
verdicts.

- `sense-lab repo` no longer takes `-campaign`. Admission answers to the
  repository alone, which is what the loop is: `sense-lab repo owner/name`
  clones, pins, scans and records. Run trees live under `runs/repos/<id>`.
- `sense-lab status` reports every repository under `-runs`, with each one's
  spend against its own ceiling. `sense-lab plan -repo <id>` expands that
  repository's bench, declared in `lab/benches/<id>.json` rather than
  `lab/campaigns/<key>/campaign.json`.
- The spend ceiling is now wired: 40 paid runs per repository over its
  lifetime, refused by `probe` before anything spawns, read from the tree on
  every call rather than counted in memory. `budget.ErrCeiling` makes a
  refusal tellable from a failed read, and `probe` answers with the refusal
  code. There is no flag to raise it, and `status` lost its `-ceiling` flag so
  the page cannot state a ceiling nothing enforces.
- `budget.Read` is unchanged: it always walked whatever root it was handed.
- The handoff phase declared `reads: campaign.json`, a file that never existed
  in any run tree. It now declares `attempts/`, which is what the plan reads.
- LAWS: the spend law is scoped by repository rather than campaign, and ONE
  REPO AT A TIME records that its gate is NOT BUILT, since the row it was
  going to be read from is gone.

Local run trees were moved by hand (`runs/` is gitignored): the repository to
`runs/repos/<id>`, `records/` up to `runs/records/`, because a finding is
about a Sense surface and not about the repository it was seen in. Stale paths
in record JSON were repointed; raw transcripts were left untouched.
@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.66667% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lab/internal/cli/probecmd.go 81.81% 1 Missing and 1 partial ⚠️
lab/internal/status/status.go 89.47% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

The page and the crank worked the same tree out separately, and they
disagreed. `status` derived the cycle from the numbered directories itself, so
a repository admitted and scanned but with no cycle directory yet read as cycle
0, reached none, awaiting none, with no resume line, while `position` (which
the crank routes on) read the same tree as cycle 1 awaiting author. Measured on
mastodon straight after admission.

status.Repo now embeds position.Position rather than re-deriving it, so there
is one reader. Its own walk, its artifact check and its index lookup are gone
with it.

Two things the page gains from the position it was not carrying: the standing,
in the words position gives it, so a PAY nothing will spend, an agent that
misbehaved and an agent that died are visible rather than absent; and a resume
line gated on Ready rather than on "not parked and awaiting something", which
is what let a parked repository read as awaiting a phase.
@luuuc luuuc self-assigned this Aug 19, 2026
@luuuc
luuuc merged commit 5ffc26e into main Aug 20, 2026
7 checks passed
@luuuc
luuuc deleted the chore/remove-campaign branch August 20, 2026 06:50
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