Skip to content

fix(benchmark): make the instrument honest, then cut direction's 38.7% cost share - #192

Open
TinDang97 wants to merge 18 commits into
mainfrom
fix/tamper-syntax-agnostic
Open

fix(benchmark): make the instrument honest, then cut direction's 38.7% cost share#192
TinDang97 wants to merge 18 commits into
mainfrom
fix/tamper-syntax-agnostic

Conversation

@TinDang97

Copy link
Copy Markdown
Collaborator

Why

The pay1–4 campaign (n=1, 2026-07-26) produced a result worth taking seriously: spec-kit shipped the payments track perfectly at 1/5 of ADD's cost ($4.20 / 12.7 min vs $22.15 / 67.8 min). Folding ADD's own transcripts into a call-stack flamegraph located the spend — direction is 38.7% of billed context and 45.7% of wall-clock, and only 6.8 of those 31 minutes is reasoning. The rest is structure: 7.3m of strictly serial Reads, 4.9m building one PLAN.md through 45 successive Edits, 3.9m of harness bookkeeping. Across 209 direction turns, ZERO emitted more than one tool call. The engine itself is 1.1%, so trimming verbs buys nothing.

Then the instrument turned out to be lying, in both directions.

Part 1 — the instrument

  • spec-kit never ran spec-kit. prompt_wrapper = "raw", and specify init without --integration defaults to Copilot in non-interactive sessions, so its commands landed in .github/prompts/ where the runner's agent never looks. Zero specs/ artifacts in every automated campaign. 493 tests were green throughout, because nothing compared an arm's setup against its wrapper. Every comparative number published was "ADD vs a competent agent with no method", labelled as a method. gsd carried the identical shape.
  • tests_weakened could only read pytest-style asserts — ADD's wm6 had 0 bare asserts and 189 self.assertX, scoring 0.0 against spec-kit's 175/0.
  • Six oracle surfaces asserted a bare JSON array for collections whose prompt never fixed the serialization — the collection-shape guard enumerated checklist.py and was blind to oracle/*.py.
  • Records could not be dated. Archived campaigns hold results from at least three different meters and nothing said which; meter_version is now an md5 over the scoring modules' content, because a version someone must remember to bump is the turn_ceiling failure mode.

Part 2 — the direction-velocity milestone (8 of 9 tasks, all gated PASS)

  • read-batching — the guide mandated "ONE silent draft" for the write side of direction and said nothing about the read side. All three skill trees now carry a "Batch the sweep" clause, and BATCH_CLAUSE reaches the measured arm so the benchmark scores the method people actually run. raw and spec-kit are asserted clause-free.
  • direction-one-shotadd.py draft --from <bundle> writes §1+§3+§4 in one call, all-or-nothing. --run-red makes the engine observe the red suite; a suite that PASSES at freeze time is a refusal. Argv list, never shell=True; bounded timeout; no retry — a suite run is not idempotent-cheap.
  • invariants-publish · invariant-inherit — a §3 may publish invariants, each citing a resolvable test or the freeze refuses; dependents inherit them as a view, never a copy.
  • design-at-build · edge-rigor — a node that binds its neighbours owes them a DESIGN.md; an enumerated [edge] row must resolve to a real test or carry a stated reason.

The last four tasks were authored by draft itself — one call per direction phase, against the 45-Edit baseline.

What this does NOT claim

Two exit criteria are deliberately unticked, because both need a run and not code:

  • read-batching's effect is unmeasured. The clause ships and is guarded; whether a prose instruction changes tool-call shape is Gate A's job. It may prove inert, in which case the honest read is that direction-one-shot carries the milestone.
  • algo-workload is not started.

No cost claim in this PR is re-measured. The comparison arm is only now honest, so the next campaign is the first one whose numbers are publishable.

Evidence

2917 tests green across add-method/tooling/ and benchmark/tests/, including the nested fresh-checkout run. Four engine twins byte-identical, ENGINE_MD5 re-aimed. direction-one-shot declares risk: high; the engine refused its auto-gate and it was recorded by a human.

Four defects in this work were caught by existing guards rather than by me — a for/else that made new-task print the orphan nudge for every attached task, a template guard demanding gitignored dogfood twins exist, a duplicated parity assert, and an unmirrored engine_pin.py. The duplicates were removed; no ceiling was raised.

TinDang97 added 17 commits July 26, 2026 21:44
…d flag is the surfacing ceiling

Across three amb1 reps ADD surfaced exactly 1 of 7 planted ambiguities EVERY
time — never 0, never 2 — while its PLAN.md template asks for one "Least-sure
flag surfaced at freeze", singular and ranked lowest-confidence first. Two
readings fit that data:

  a) the singular flag is a CEILING — ADD noticed more and reported one;
  b) ADD noticed one.

This arm separates them by adding ONE clause to the wrapper: at freeze, list
every choice the source spec does not settle, not just the least-sure one. If
(a), the rate rises; if (b), it does not and the flag design is exonerated.
Either outcome is informative, which is what an A/B needs.

Why this and not gate infrastructure: the earlier claim that ADD's disabled
human gate was the benchmark's largest distortion does not survive checking.
The frozen §3 contract in all three ADD runs contains no trace of the two gaps
every build shipped — no ownership, 403, permission or caller-scoping vocabulary
in ~3000 characters of contract each. A human at that gate would have approved a
document in which both decisions were invisible, because a gate reviews what was
written down and a silent gap is by definition not written down.

Also checked and NOT acted on: MARKERS contains "clarif", documented as catching
spec-kit's NEEDS CLARIFICATION token, while ADD's ⚠ idiom is absent — a real
asymmetry in the detector's construction. Adding "⚠" (and an "overlap" anchor)
changes NOTHING on these runs: the one sentence it newly matches attributes to
A-conflict-response, which ADD already surfaced. A change that would have
favoured the method this benchmark belongs to was tested and dropped.

Isolation is asserted mechanically, not by eye: the variant minus the clause must
equal the baseline byte for byte, so a second smuggled difference fails the suite
(verified by mutation). The clause is checked to leak no planted anchor, no
domain vocabulary from this workload, and no quota that could be padded.

Registered as EXPERIMENTAL: selectable by name, absent from ARM_NAMES, so no
default campaign changes cost or composition.

Not yet run — a live A/B costs roughly $2 per rep and is the human's call.

benchmark/tests: 411 -> 423 passing.

author: Tin Dang
…d arm was unrunnable

The first launch of add-enumerate failed instantly on `unknown_arm` with 423
tests green. score.py validates arm names against ARM_NAMES independently of the
loader and the CLI, and the new suite covered the loader and the CLI only —
tested unit, untested seam, the same shape this codebase keeps producing.

Cheap this time: the run died before spending anything. It would not have been
cheap inside a multi-rep campaign.

The guard is now enumerative rather than remembered: no module outside tests may
validate against ARM_NAMES, the DEFAULT campaign set. A fourth gate added later
fails in the suite instead of at the start of a paid run.

author: Tin Dang
…ntation

add-enumerate's first run reported 3 of 7 surfaced against a baseline of 1 of 7.
Reading the evidence spans, two of the three were junk and the "rise" was
entirely spurious. Three distinct defects, all found by auditing a result that
moved in the direction I wanted:

D1 ANCHORS MATCHED INSIDE LONGER WORDS. "position" is a substring of
   "composition", so a sentence about image composition surfaced
   A-position-ordering. Anchors are TERMS; they now match on word boundaries.
   This also corrupted ATTRIBUTION, which ranks on anchor-hit count — a phantom
   hit could outrank and steal a genuine surfacing from the item that earned it.
   Markers stay unbounded deliberately: they are stems (assum -> assumption).

D2 A CLOSING XML TAG ACTED AS A MARKER. "assum" is a substring of
   "</assumptions>", so the tag ENDING ADD's assumptions block marked whichever
   sentence followed it — live, the opening line of the contract body. Simple
   tags are now blanked before matching, with EQUAL-LENGTH spaces so every offset
   (including edit_pos, which indexes the untouched transcript) still points
   where it did. Only well-formed tags: a greedy <...> strip would eat
   "start < other.end AND end > other.start", which is prose about a boundary and
   frequently the very thing being surfaced.

D3 THE ARTIFACT BUDGET READ SHIPPED DOCUMENTATION. _workspace_artifacts took the
   first 40 prose files in sort order. An ADD workspace holds 302, of which 256
   are the vendored personas-teacher library — so the budget was consumed
   entirely by ADD's own product documentation while PLAN.md, the agent's actual
   reasoning, sorted at index 270 and was never read at all. The live false
   positive came from design-image-prompt-engineer.md, whose boilerplate reads
   "Avoid ambiguous language that could be interpreted multiple ways". That
   sentence ships in every ADD workspace; crediting it scores an arm for the
   contents of its installer.

   Artifacts are now the documents the agent WROTE, per its own tool calls —
   arm-neutral by construction, since it asks what the run produced rather than
   where a method files things. Fails CLOSED: no transcript means no artifacts,
   never "read everything", because reading everything is what caused this.

D3 is the original `artifacts = ()` bug wearing the opposite sign: that one read
none of the agent's documents, this one read everything EXCEPT them. Both hid in
the same place — tests exercised classify() directly and never the seam that
chooses what to feed it.

RE-SCORED, corrected detector:

    add (baseline)   0.143   [0.143, 0.143, 0.143]   unchanged
    spec-kit         0.095   [0.143, 0.143, 0.0]     unchanged
    add-enumerate    0.143   [0.143]                 was 0.429

The experiment's apparent lift is gone. Every surviving verdict rests on a
readable sentence that names the contradiction.

Two tests from the earlier artifact fix are AMENDED, not weakened: they now
record the Write in the transcript, while the payload still omits the
recognition — the realistic shape, since an Edit records a slice and the file on
disk ends up saying what no single payload did. Each still passes only by
reading the file.

benchmark/tests: 425 -> 435 passing.

author: Tin Dang
…scoring every unittest arm 0.0

`_assert_fingerprints` walked for `ast.Assert` and nothing else. Census over
the archived wm1-wm6 campaign:

    add wm6:       bare `assert` statements = 0     self.assertX(...) = 189
    spec-kit wm6:  bare `assert` statements = 175   self.assertX(...) = 0

ADD's frozen contract targets `python -m unittest discover -s tests`, so all
189 of its assertions were invisible to the detector and only whole removed
test FUNCTIONS could ever score. spec-kit's asserts were counted in full.
The metric compared assertion syntax, not test discipline — and it happened
to flatter the method this benchmark belongs to, which is the direction a
defect is least likely to get questioned. It was about to be published as
"ADD 0.267 vs spec-kit 1.333".

Both syntaxes now normalize to ONE fingerprint, so a pure
`assert a == b` -> `self.assertEqual(a, b)` migration scores zero rather
than trading this bias for its mirror image. An assertX with no operator
equivalent (assertRaises, assertAlmostEqual) fingerprints as itself.

Auditing the re-scored numbers then found a SECOND distortion: identity is
`<relpath>::<fn name>`, so a rename reads as a removed test. Three of the
four counts in spec-kit's largest case were renames under tenant isolation
(test_list_returns_all_bookings -> test_list_returns_only_own_bookings).
`compute_tests_weakened`'s definition is frozen in its §3 contract, so the
rename-immune accounting lands as a NEW metric, `compute_assertions_lost` —
a snapshot-wide multiset difference. Both are reported; they answer
different questions.

Matched-campaign result on the honest metric (3 campaigns, shared
transitions only): add 1 assertion lost, spec-kit 11.

Tests: 26 new, all five/three mutants killed per function. Two mutants
survived the first draft (the assertX capital-letter guard and the trivia
exclusion) and two rename probes passed vacuously on fixtures named `t.py`
that the detector never collected — both fixed before the green was taken.

author: Tin Dang
…hind an enumerative well-formedness guard

The hard-case track built 2026-07-15 on feat/bench-hard-cases never ran
against a real arm. It forked at 40c5548, before ten meter fixes landed,
and its diff touches 113 files — but all 60 conflicts are in the ADD engine,
which main has since superseded. Only the 35 workload files are ported.

RENAMED hv -> pay. `benchmark/tests/test_prune_deadweight.py` guards a human
decision of 2026-07-10: the WV2 hostile track (hv1-hv3) was run at n=1,
returned "NO ARM GAMED — hypothesis fails at this temptation strength", and
was pruned as dead weight. That track was the BOOKINGS domain, a base pair
for wm1. The branch's hv1-4 is an unrelated payments ledger that merely
reuses the family prefix. Retiring the guard to make the port fit would have
erased a standing decision to make a build pass; renaming the new track
costs nothing and keeps both. The `family` seam is a directory prefix, so
the rename needed zero engine changes.

Fixed on the way in: 8 sites across pay1/pay3/us1 asserted a BARE JSON array
for list endpoints (`isinstance(body, list)`), the same envelope-shape defect
already fixed three times elsewhere. The enumerative collection-shape guard
caught all 8 the moment the files landed — the guard earning its keep on code
written before it existed.

NEW test_every_track_wellformed.py. test_oracles_red.py is parametrized over
[1, 2, 3], so wm4-wm6, amb1 and the entire payments track were never checked
for the defect it exists to catch: an oracle that passes on an EMPTY
workspace measures nothing. Enumerating workload/* instead covers a track the
day it lands. All 12 tracks confirmed red-on-empty with unique requirement
ids; suite 485 green.

NOT RUN. A pay1-4 head-to-head is a spend decision, not a code decision.

author: Tin Dang
…assertions_lost beside tests_weakened

Two defects found while preparing the pay1-4 launch, both the same shape as
bugs already fixed this session.

1. `--family` carries a hardcoded `choices=("wm", "hv", "amb")` in pilot.py
   AND report.py, with nothing tying either to benchmark/workload/. The pay
   and us tracks existed on disk and were unrunnable. This is the
   `unknown_arm` defect again: a second allowlist that drifts from the real
   source, invisible to a green suite, discovered only by paying for a launch
   that fails on argument parsing. test_family_choices_cover_disk.py
   enumerates the directories so the NEXT track is covered too, and pins the
   two CLI surfaces to each other — a family runnable but not reportable is
   the same trap one step later. `hv` stays an accepted choice with no
   directory: the pruned track's archive still scores through
   `report --trust --family hv`.

2. `compute_assertions_lost` existed but nothing called it, so a pay campaign
   would have recorded only the rename-blind count and needed post-hoc
   recomputation. Both counts now come from one seam, `_add_tamper_metrics`,
   written from the same snapshot pair under the same guards. Absent
   snapshots yield NEITHER key rather than 0.0 — "nothing was lost" and
   "nothing was measured" must not be the same record.

Tests: 8 new, red first. Suite 493 green.

author: Tin Dang
… and gsd both ran raw

The arm labelled `spec-kit` ran `specify init --here --force` and was then
handed the bare workload prompt (`prompt_wrapper = "raw"`). Across every
automated campaign it produced ZERO `specs/` artifacts — no spec, no plan, no
tasks. `gsd` carries the identical shape. Both were, in effect, `vanilla` plus
a directory of unopened templates, and every comparative number this benchmark
has published was "ADD vs a competent agent with no method" wearing a method's
name.

A second, compounding defect: `specify init` without `--integration`
"default[s] to Copilot in non-interactive sessions", so the slash commands
landed in `.github/prompts/` — a directory the runner's agent never reads.
Every archived spec-kit workspace carries `"ai": "copilot"` in
`.specify/init-options.json`. The arm had no path to its own method at all.

493 tests were green throughout. `test_arms.py` checks pins and fairness
fields; nothing compared an arm's setup_steps against its prompt_wrapper.

FIXED
- spec-kit installs `--integration claude` and drives its own documented cycle
  through a new `spec-kit` wrapper: specify -> plan -> tasks -> implement.
- gsd declares `scaffold_only = true` with a written reason, because its
  command surface is unverified for this runner. Declared beats silently
  implied; fixing it properly is its own task.
- vanilla is untouched — `setup_steps = []` is scaffolding-free by construction.

GUARD (the actual deliverable) enumerates `arms/*.toml` rather than naming
arms, so an arm added later is covered the day it lands: scaffolding + raw +
no scaffold_only -> `silent_method_arm`; scaffold_only without a reason ->
`unexplained_scaffold_only`.

ADD: task arm-honesty under milestone direction-velocity, frozen @ v1,
gate PASS, target-hit yes. Red first — the new suite named both offenders
before any fix. Suite 493 -> 514 green.

author: Tin Dang
…er version

A scored number you cannot date is a number you cannot trust. The archived
campaigns hold records produced by at least three different meters — before
the reading-probe fixes, after them, and after `tests_weakened` learned to
read unittest assertions — and nothing on a record said which. Telling them
apart meant reading git history and matching dates by hand.

benchmark/meter.py derives the stamp from the CONTENT of the modules that
decide a score (score.py, tamper.py, ambiguity.py, trust.py,
workload/_oracle_lib.py), so editing a scorer moves the stamp without anyone
remembering to bump it. A hand-maintained constant is the `turn_ceiling`
failure mode: declared in every arm, asserted equal by a test, read by
nothing. A missing module raises rather than hashing nothing, so renaming a
scorer fails loudly instead of drifting the stamp toward a constant.

The stamp rides in `artifacts` because `REQUIRED_ARTIFACTS` is a subset check
while `REQUIRED_METRICS`/`OPTIONAL_METRICS` are a closed key-set.

Second defect, same concern: test_collection_shape.py enumerated
workload/*/checklist.py and was blind to workload/*/oracle/*.py. Six live
oracle surfaces asserted a bare JSON array for collections whose workload
prompt never fixed the serialization — a probe encoding an unstated
preference distorts scores exactly as a misdated meter does. The guard now
yields both globs; all six are fixed with the existing envelope-tolerant
`records()` reader rather than an allowlist.

Task meter-provenance: frozen @ v1, gate PASS, 520 tests green.

author: Tin Dang
The pay1-4 campaign (2026-07-26) folded ADD's own transcripts into a
call-stack flamegraph: across 209 direction turns, ZERO emitted more than one
tool call. 7.3 of direction's 31 minutes went to a strictly serial chain of
Reads, each paying a full turn's context for a single file.

The guide had already mandated "ONE silent draft" for the WRITE side of
direction and said nothing at all about the READ side. `### Grounding` now
carries a "Batch the sweep" clause: independent reads, greps and listings go
out in ONE turn, with the reason stated, and chaining reserved for the case
where the next path is genuinely unknown until the previous result lands.

`BATCH_CLAUSE` carries the same instruction to the arm under measurement, so
the benchmark scores the method people actually run rather than a private
variant — the dishonesty class `arm-honesty` closed on the comparison side. It
reaches `add-loop`/`add-loop-enumerate` ONLY; `raw` and `spec-kit` are
asserted clause-free, because lifting the controls along with the treatment
destroys the comparison in the other direction. It also tells the agent to
skip harness bookkeeping (task-tracker calls, sleep-polling) — 3.9 further
minutes that deliver nothing to the workload.

Two guards, both enumerative: the skill-tree guard globs the repo layout
rather than naming the three trees, so a fourth is covered the day it lands
(three hand-mirrored trees with no parity test is the lock-reclaim failure
class, and it has already cost this project a publish); an empty-root test
proves the enumeration cannot pass vacuously.

What this does NOT claim: that a prose clause changes tool-call shape. That is
Gate A's measurement, one re-run away, and the §3 least-sure flag says so.

Task read-batching: frozen @ v1, gate PASS, 2868 tests green across both floors.

author: Tin Dang
…e call

The pay1-4 flamegraph fold (2026-07-26) measured 4.9 of direction's 31 minutes
spent building a single PLAN.md through 45 successive Edits — 45 round-trips to
write one file. `advance --fill` already batched ONE section for ONE crossing;
a direction bundle is three sections and a freeze.

`draft --from <bundle>` writes §1 + §3 + §4 in one call, all-or-nothing. It
reuses `_fill_and_advance`'s snapshot-and-restore verbatim in structure: any
refusal on any path — SystemExit from `_die` included — restores the original
bytes before re-raising, so a rejected draft never leaves a half-written bundle.
The bundle is delimited by its own `## <n> ·` headings and parsed by the ONE
canonical scan, so no second grammar enters the codebase.

`--run-red` is the harder half. "The red suite ran red before the build" has
always been a discipline the agent ASSERTS; the engine now OBSERVES it, and a
suite that PASSES at freeze time is `red_suite_green`, a refusal. Design for
failure: an argv list and never `shell=True`, bounded by `--red-timeout`
(default 300s), and NO retry — a suite run is not idempotent-cheap, so a
retried timeout could double whatever side effect hung it. Anything that
prevents an honest verdict is a refusal, never a silent skip.

`--freeze` chains cmd_freeze rather than reimplementing it, so
contract_not_drafted, unflagged_freeze, boundary_unfilled and the scope floors
all still decide. `advance --fill` is untouched and keeps its own contract.

Three corrections the plan did not foresee: section bodies bound at the next
`## ` OR bare `---` (heading-to-heading would have swallowed the rules between
sections); `_bundle_sections` keeps out-of-range headings so a §9 is REFUSED
rather than dropped; and `engine_pin.py` is a four-way twin in its own right.
The last was caught by test_tree_parity, and test_corpus_slim's census caught
that a twin-parity test of mine duplicated a strictly wider existing sweep.
Both were removed rather than accommodated — no ceiling was raised.

Skill cookbook gains the verb at a NET -118 bytes, funded by compression.
Engine mirrored to all four twins; ENGINE_MD5 re-aimed (ENGINE_PKG_MD5
unchanged — add_engine/ was not touched).

GATE PENDING: the task declares `risk: high`, and the engine refused the
auto-gate (unguarded_high_risk_auto) — a human owns this one.

2882 tests green across both floors.

author: Tin Dang
risk: high routed this gate to a human, and the engine refused the auto-path
(unguarded_high_risk_auto) exactly as designed. The scope tripwire then caught
the commit-message file under tmp/ — a consumed, gitignored transient genuinely
outside the task's write-set, so it was reverted rather than covered by
widening §5 Scope to include bookkeeping.

Gate: PASS, reviewed by Tin Dang, target hit. 2882 green across both floors.

Three lessons filed: a shelling-out verb needs argv/timeout/no-retry · an
anti-sprawl census that refuses a new test is usually right (delete the
duplicate, never raise the ceiling) · a fixture assertion must anchor the way
the parser anchors it.

author: Tin Dang
…unproven ones

PROJECT.md carries project-wide `invariants:` binding every task, but a TASK had
no way to publish one of its own — so a downstream node inherited nothing, and
"the payout timeout is bounded" lived in prose no build was answerable to.

§3 gains an OPTIONAL `Invariants (published):` block. Each entry must cite a
proving test — `(proof: `path::test_name`)` — and the freeze refuses one that
does not, naming which invariant is unproven. A citation whose file does not
resolve under the project root is refused identically: a citation to nothing is
the same failure wearing a citation, and this project has already paid once for
`turn_ceiling` — declared in every arm, asserted equal by a test, read by
nothing. An invariant no test can fail is a comment.

Grandfathered by absence, copying boundary_unfilled's shape exactly: a §3 with
no block freezes as it does today, and the template's line carries a
placeholder so it deliberately does NOT match the parser — a freshly scaffolded
task still freezes untouched. The floor is opt-in; opting in is what binds you.

Path resolution is fail-closed with is_relative_to, mirroring _declared_scope.
Slotted after unflagged_freeze so the cheaper structural refusals fire first;
validate-then-write, so a refusal writes nothing.

DOGFOOD: this task's own direction bundle was authored by `add.py draft --from
... --run-red --freeze --cross` — the verb its predecessor shipped. One call ran
the suite, observed it RED, wrote §1+§3+§4, froze, and crossed to build.

The grandfather test earned its keep immediately: it caught that four sibling
tests were passing on contract_not_drafted rather than on the floor under test,
because the fixture only partially replaced the template.

Engine mirrored to four twins, template to four twins, ENGINE_MD5 re-aimed.
2892 tests green across both floors. Gate PASS.

author: Tin Dang
…t a store

A published invariant needs a consumer, or publishing one is pointless.
`new-task --depends-on <a>` now prints every invariant the new node inherits,
attributed to the node that owns it, walking the TRANSITIVE depends_on closure
so a grandparent's invariant binds too.

It is a VIEW. Nothing is copied into the new task's PLAN.md and no key enters
state.json — the moment an inherited invariant is copied it can drift from the
ancestor that owns it, and the copy is what the builder reads. `graph --signals`
already settled this project's answer: the graph is a view, not a store.

Fail-soft: an ancestor whose PLAN.md is missing or unreadable contributes
nothing and never blocks the new task. A broken neighbour must not stop a
healthy new node, and the guard for an unproven invariant lives at the
ancestor's own freeze. An empty inheritance prints nothing at all — a header
with no rows is noise on every task that inherits none.

TWO DEFECTS OF MINE, both caught by existing guards rather than by me:

1. for/else. The print loop first landed between the `if milestone:` body and
   its `else:`, which binds the else to the FOR — it then fired on every loop
   completing without break, so new-task printed BOTH "linked to milestone" and
   the orphan nudge. Valid Python, silently wrong; test_v8_1_orphan_guard
   caught it. The loop now sits after the whole if/else with a comment saying
   why it must.

2. Fresh-checkout twins. invariants-publish's template guard demanded all four
   twins EXIST, but two are gitignored dogfood trees absent from a fresh
   checkout. It passed locally and failed only in test_ci_tooling_mirror_gap's
   nested run. Now follows test_tree_parity's exists-skip convention: the
   git-tracked pair must always be present and identical, a dogfood twin is
   compared only if it is there. That guard had already gated, so this is a
   post-gate repair to a shipped guard, recorded as a TDD delta.

Direction bundle authored by `add.py draft --run-red --freeze --cross`.
Engine mirrored to four twins, ENGINE_MD5 re-aimed.

author: Tin Dang
Tamper tripwire fired first: §4 declares `Tests live in: add-method/tooling/`,
which makes the whole engine directory a protected test surface, so the
legitimate build edits to add.py and engine_pin.py read as tamper. Resolved via
the sanctioned re-cross, and filed as a delta — three engine tasks in this
milestone have now each paid that toll.

author: Tin Dang
…ts DESIGN.md

PLAN.md deliberately persists the interface and nothing else — "reason
everything else in-context, don't write essays". That is right for most tasks
and wrong for exactly one kind: a node that publishes an invariant its
dependents inherit. Someone will later be told "you must not break this" with
no way to learn why, and the reasoning is gone with the conversation that had it.

So the floor is proportional rather than universal: if you bind your
neighbours, you leave them the reasoning. Publish nothing and nothing changes —
a non-publisher gates exactly as before and no DESIGN.md is created for it.
"Every task writes a design doc" is the ceremony this method spent whole
milestones removing.

Written at BUILD, not direction, on purpose: the design that survives is the one
the implementation actually took. Demanding it before the build is how you get a
document describing a plan nobody followed.

design_missing names the path to write. design_empty closes the touch-a-file
bypass — a touched file is not reasoning. Both fire after _tamper_guard, so a
cheat still outranks a missing document, and both apply to RISK-ACCEPTED as well
as PASS: a waiver must not launder a missing design. Both reads are fail-soft;
an unreadable file never becomes a traceback at a gate.

What this does NOT do is judge the reasoning. A DESIGN.md saying "TODO" passes,
and §1's assumption says so: imposing a shape on prose is the move that produces
filled-in templates instead of thought. The floor makes an omission visible at a
human seam; if audits later show empty designs, the fix is a reviewer prompt in
the gate report, not a stricter parser.

This task declares `Tests live in:` as the FILE rather than the directory, and
gated with no tamper trip — confirming the lesson invariant-inherit paid a
re-cross to learn: naming the engine directory as the test surface makes every
legitimate build edit read as tamper.

2908 tests green across both floors. Gate PASS.

author: Tin Dang
…fore the gate

§4's Rigor rule already separates GATED rows from prose build-guidance, and it
has one hole: an author can ENUMERATE an edge case — visibly promising it — and
never write the test. The row reads as coverage to every later reader and
nothing ever disagrees with it. Enumerating a case you do not cover is worse
than not enumerating it, because it buys credit for work that was not done.

At a completing gate, every `[edge]`-tagged row must name a test present in the
§4-declared suite, or carry `[edge — waived: <reason>]`. A blank waiver is
refused: the escape hatch costs a sentence. Both refusals apply to
RISK-ACCEPTED as well as PASS, and both fire after the tamper and design floors
so a cheat still outranks a bookkeeping gap.

Deliberately narrow. `[GATED]` rows are NOT policed here — green-ness is the §6
evidence floor's job, and double-enforcing would refuse on a naming mismatch. A
§4 with no `[edge]` rows is untouched. An unreadable declared suite contributes
no names and never tracebacks at a gate.

What it CANNOT do, stated in §1 rather than implied: the engine does not run the
suite at gate time, so presence of the test name is the mechanical proxy for
"covered". A `def test_x(): pass` stub clears this floor — that is the cheat
class the earned-green refute-read exists for. This closes enumerate-and-forget,
nothing wider.

DOGFOOD: this task's own §4 carries three [edge] rows, all written, so its gate
ran through the floor it ships.

Also folds design-at-build's PLAN.md read into ONE read shared by both floors —
the first draft re-read the file for §4 behind a walrus and a stray `or True`.

2917 tests green across both floors. Gate PASS.

author: Tin Dang
The two open criteria both need a run, not code:
- read-batching's clause ships and is guarded across three skill trees, but
  "the parallel-turn count is greater than zero" is a MEASUREMENT — it needs a
  pay1-4 re-run (Gate A) against the 2026-07-26 baseline.
- algo-workload has not been started; it needs a paid head-to-head campaign.

Ticking either without the run would be exactly the dishonesty arm-honesty and
meter-provenance were built to stop.

author: Tin Dang
from __future__ import annotations

import os
import pathlib
Comment thread benchmark/score.py
from benchmark import judge, tamper
from benchmark.ambiguity import is_implementation_write
from benchmark.arms.loader import ARM_NAMES
from benchmark.arms.loader import ALL_ARM_NAMES, ARM_NAMES
…ution unearned

Gate A re-ran pay1-4 on the add arm (n=1) against the 2026-07-26 baseline:
$22.15 -> $9.94, 326 -> 163 turns, and R-payout-timeout-bounded — the one
requirement ADD missed in the baseline — is now covered. 18/18, zero
regressions, tests_weakened and assertions_lost both 0.

Three things this does NOT establish, recorded so nobody reads the table as
more than it is:

1. `add.py draft` was called ZERO times. The add-loop wrapper names
   `add.py freeze` and never learned the new verb, so direction-one-shot —
   the change this milestone rests on — remains unmeasured. That is
   arm-honesty pointed inward.

2. The parallel-turn exit criterion is UNMEASURABLE in this harness. A census
   of 138 transcripts / 7560 assistant turns — every arm, every campaign ever
   recorded — finds EVERY turn carrying exactly one tool_use. The
   '0 of 209 direction turns parallelized' finding that motivated read-batching
   was an instrument artifact, not a fact about ADD. The criterion is marked
   [~] with the census rather than ticked or silently dropped.

3. Direction's share of turns is not reported at all: the fold's seam detector
   never resets after the first freeze, and the campaigns used different verbs,
   so the 18.7%->54.6% reading is an artifact.

What Gate A does support: Agent 8->0 and SendMessage 7->0, matching
BATCH_CLAUSE's skip-harness-bookkeeping half. The rest is confounded at n=1.

author: Tin Dang
@TinDang97

Copy link
Copy Markdown
Collaborator Author

Gate A — 2026-07-27 (add arm, pay1–4, n=1, engine pin 4b6844ae)

metric baseline 2026-07-26 Gate A
cost, 4 WMs $22.15 $9.94 −55%
assistant turns 326 163 −50%
requirement coverage 17/18 18/18 R-payout-timeout-bounded now covered
oracle pass rate 0.9 / 1.0 / 1.0 / 1.0 0 regressions
Read / Edit calls 64 / 87 26 / 44
Agent / SendMessage 8 / 7 0 / 0 matches BATCH_CLAUSE's skip-bookkeeping half

Cost and coverage are real. The causal story is not earned, and three limits are recorded in MILESTONE.md rather than left implicit:

  1. add.py draft was called ZERO times. The add-loop wrapper names add.py freeze and never learned the new verb exists, so direction-one-shot — the change this milestone rests on — is still unmeasured. This is arm-honesty pointed inward, and it must be fixed before Gate B or Gate B measures nothing either.

  2. The parallel-turn exit criterion is unmeasurable in this harness. A census of 138 transcripts / 7,560 assistant turns — every arm, every campaign ever recorded — finds every turn carrying exactly one tool_use. The "0 of 209 direction turns parallelized" finding that motivated read-batching was an instrument artifact, not a fact about ADD. The criterion is marked [~] with the census attached rather than ticked or quietly dropped.

  3. Direction's share of turns is not reported. The fold's seam detector flips at the first freeze and never resets, and the campaigns used different verbs (9 freezes vs 4, plus 3 advance), so the 18.7%→54.6% reading is a detector artifact.

n=1, against measured within-arm variance (41/36/85/47 direction turns across four milestones) that already exceeds this effect — and the engine gained four gate floors in the same window. The comparison arm is also still un-rerun, so no ADD-vs-spec-kit claim is refreshed here.

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