Skip to content

feat(optimize): kind: optimization — factorial/response-surface campaigns - #302

Open
sriumcp wants to merge 148 commits into
AI-native-Systems-Research:mainfrom
sriumcp:nousko
Open

feat(optimize): kind: optimization — factorial/response-surface campaigns#302
sriumcp wants to merge 148 commits into
AI-native-Systems-Research:mainfrom
sriumcp:nousko

Conversation

@sriumcp

@sriumcp sriumcp commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Adds kind: optimization — a factorial / response-surface campaign type alongside the default reflective one.

Refs #120. Design spec: docs/superpowers/specs/2026-08-13-optimization-campaign-kind-design.md. Authoring guide: docs/optimization-campaign-guide.md.

Status: end-to-end working and validated by four real campaigns against inference-sim. An earlier revision of this description carried a "this lands the machinery, not a usable command" warning — that is no longer true and the section has been removed. See Validation for what was measured and What running it found for the defects that only real runs surfaced.

Why

The blog post "Discovery Is Easy, Composition Is Hard" benchmarked seven LLM-driven optimizers on the Certus cold-read path. Six beat baseline; five stalled near half the available headroom. The decisive fact was that batching cost 9.5% in isolation yet was required for the winning compound — so any search that prunes regressing candidates cannot reach the optimum.

That is a property of one-factor-at-a-time search, not of the problem. Factorial designs have no composition barrier because they estimate interaction terms directly instead of composing increments. A resolution-V fractional design over 8 factors gets every main effect and all 28 two-factor interactions unconfounded.

It also addresses the cost complaint: the per-configuration decision moves out of the model and into Python.

What lands

Five stages, one per iteration: buildverifyscreenrefineconfirm. Only build makes a model call; verify, screen, refine and confirm make zero.

build is opt-in and absent from the default order, so every campaign written before it existed behaves identically. Declare it first when the mechanism under study does not exist in the target yet and the campaign has to author it. Substantive model calls per campaign: ~3 without build, ~4 with it, against 60–90 tokenless benchmark runs.

A new orchestrator/optimize/ subpackage (12 modules, all pure functions over data), four artifact schemas, the optimization campaign block with twelve cross-field validation rules, kind-scoped gate defaults, and nous validate campaign FILE for authoring-time checks.

The architectural guarantee

orchestrator/iteration.py: 23 insertions, 0 deletions — one delegation branch plus one comment. A campaign with no kind, or kind: reflective, behaves exactly as before. tests/test_optimize_no_regression.py proves it structurally (AST-parsed, so formatting changes cannot produce a false pass), including the negative case that complexity_tier remains legal on reflective.

Full suite: 1709 passed, 1 skipped.

Correctness

Every campaign declares two mandatory check families per factor: a manipulation predicate (did the lever engage?) and native property/metamorphic tests in the target's own idiom (is the mechanism correct?). Nous checks the contract; the target's runner produces the verdict, so the harness needs no language knowledge and the tests outlive the campaign.

Relations are classified correctness (hard-fails the campaign) versus behavioral (recorded as a finding, campaign continues). That split is deliberate: a monotonicity break is a discovery — the motivating lever was −9.5% alone and required in combination — so hard-failing on it would make the campaign blind to exactly what it exists to find.

build makes no correctness judgement. verify remains the gate, so the stage that writes the mechanism is never the stage that certifies it, and certification is pure Python reading a real test runner's real output. verify is fail-closed: a declared native_test that did not execute counts as a failure, because "not run" is not evidence of correctness.

Three checks fire regardless of gate approval, since auto-approve is this kind's default: design-matrix fidelity drift, a held-out metric reaching a fitting input, and a correctness-relation violation.

Quality guards on the reported optimum

Three layers, added because a real campaign tripped each one:

  1. Hull check. confirm refuses a fitted stationary point whose coded coordinates leave [-1, 1] and replicates the best observed configuration instead. decide_after_refine already detected this and wrote "ranges were too narrow to contain the optimum — escalate before confirming" into findings, but Trigger is reported-not-acted-on, so confirm did it anyway.
  2. Best-observed comparison. confirmation.json records confirmed_is_best_observed and, when they disagree, the absolute and percentage gap. "The prediction reproduced" and "this is the best configuration found" are different claims.
  3. Diagnostics preservation. Full test output lands in runs/iter-N/test_output.log; a failed configuration's full stdout/stderr lands in runs/iter-N/failed_runs/failed_run_<i>.log with its row index and levels.

On the campaign that motivated these: the fitted optimum was 112.4997 while the campaign had already measured 182.2159 — 38% off, with replicate spread 0.0, reported as CONFIRMED. All three guards now fire on it.

Validation

Four real campaigns against inference-sim, with per-arm git worktrees and an independent third worktree carrying a separate reference implementation for ground truth. Full write-ups in the comments below.

Experiment Setup Result
1–2 simulated search strategies, then a target with one live factor non-discriminating; framing corrected in the comments
3 two real campaigns, existing flags only optimization complete at $0.082; reflective $5.451 and unfinished — 66×
4 both kinds must author --ceiling-curve in Go first optimization complete at $2.65, 100% of the true optimum; reflective authored a correct mechanism, then hung on #303 at $6.49

Experiment 4's per-phase split is the durable claim:

reflective optimization
one-time authoring design $1.62 build $2.44
per measurement iteration execute-analyze $4.77 $0

Authoring code costs roughly the same in both kinds. What this kind removes is the recurring per-iteration measurement cost — 33 real benchmark runs for zero tokens. The advantage therefore scales with how many measurement iterations a question needs, which is why the ratio ranged from 66× (pure measurement) to 2.5× (authoring-dominated).

Honest limits: reflective has never completed in these comparisons (stopped in #3, hung in #4), so every reflective cost figure is a lower bound. And no target has yet shown optimization finding a better answer than reflective on the same question — in experiment 4 it found the joint optimum while reflective isolated a mechanism, a difference in aim rather than in quality.

What running it found

Fourteen defects were found by running real campaigns, none by the test suite — 1600+ tests stayed green throughout, because they inject fakes at exactly the seams that were broken. The full list with consequences and fix commits is in the comments; the ones a reviewer should know about:

Defect Consequence
test_command had no caller; config_runner never wired every campaign aborted at verify or screen — the kind was unusable end to end
manipulation assumed targets echo their config back 115 configurations ran, every one failed its check
confirm replicated the geometric origin found 117.854, reported 73.476, silently
build edited the canonical checkout, not its worktree two "independent" arms would overwrite each other's mechanism; both trees build, both suites pass
REPORT extractor could not see runs.jsonl report announced a "storage/persistence gap in the apparatus" while 33 rows of telemetry sat on disk

The last two are worth dwelling on. For the worktree breach, cwd was set correctly, so the code looked right — nothing verified where the edits landed. For the report, _format_results_summary was added by #214 specifically to stop reports dismissing real data as "no data", and it was reintroducing that exact failure for a layout it did not know about.

Process note

Nineteen further defects were found and fixed during implementation, recorded with reasoning in docs/superpowers/plans/2026-08-14-optimization-campaign-kind-EXECUTION-LOG.md. Most were in the plan's own reference code. Three worth a reviewer's attention:

  • Two generator entries were labelled resolution V while delivering IV, so a campaign would have reported tight confidence intervals on confounded terms.
  • A scalar standard error understated main-effect SEs by up to 14.6%, biasing significance toward false positives — which decides which factors survive screening.
  • confirm silently re-ran the screen design while the guide claimed it reproduced the predicted optimum.

Six of the nineteen were in orchestrator/optimize/stage_runner.py, the one module without an independent author, and three consecutive review rounds each found another defect in it on a path no test drove. That file is still the one most worth a human read.

Known limitations

  • locked_parameters deviation is not a hard-fail on this path. validate._validate_locked_parameters exists but is reached only from bundle validation, and the matrix path has no bundle.yaml / experiment_spec to compare against. The stage_runner docstring says so rather than claiming a guarantee that does not exist.
  • build does not check the spec it is given. It takes the campaign's mechanism description as authoritative and makes one call. In experiment 4 it faithfully implemented a metamorphic ordering I had written backwards; reflective's DESIGN phase caught the error by reasoning about the algebra. An author's algebra error is a blind spot here, which is why the guide now requires a worked numeric example in any metamorphic statement.
  • build is one call, not a build-until-green loop. That is deliberate — it bounds cost and stops the model negotiating with its own gate — but it means a verify failure ends the campaign and needs a re-run.

Branch policy: this PR also updates CLAUDE.md and README.md to target main rather than the diverged reflective.

🤖 Generated with Claude Code

Update: compiled experimental policy (paper alignment)

Everything above shipped kind: optimization as a fixed build → verify → screen → refine → confirm schedule. A companion paper (../papers/nousko/paper.tex, "Estimate, Don't Search: Compiled Experimental Policies for Agentic Systems Optimization") describes a stricter version of the same idea: the epoch is not hard-coded control flow but a compiled, pre-registered state machinepolicy.json, hashed before the first measurement — interpreted by a pure step() function over a closed observation vocabulary. Measurements choose among registered branches; they never invent one. A semantic exception ends the epoch rather than crossing the boundary implicitly.

Design spec: docs/superpowers/specs/2026-08-16-compiled-policy-design.md (binding authority). Implementation plan: docs/superpowers/plans/2026-08-16-compiled-policy.md (16 tasks, TDD, executed via superpowers:subagent-driven-development — fresh subagent per task, independent task review, controller audit with direct mutation-testing after every task).

Status: all 18 tasks complete (16 planned + 2 inserted mid-plan), plus a final whole-branch review. Phases 0–5 of the plan, the build-stage oracles, systems-target readiness, docs, and a final cross-task review pass are all done. This is the final update to this section.

What Tasks 1–11 closed

Gap vs. the paper (pre-plan) What now exists
Hard-coded screen → refine → confirm ladder in Python control flow policy.json: schema-validated, content-hashed, compiled once at verify; step() interprets it. orchestrator/optimize/policy.py.
A stationary point was reported as the optimum, even when it was a saddle or excluded choice factors entirely decide.recommend() — exact enumeration/argmax over the valid candidate space, no model judgment. Fixes both bugs (Task 7).
No bound of any kind on "how much better could an unmeasured challenger be" certificate.model_regret_bound / terminal_regret_bound — Bonferroni-corrected simultaneous one-sided bounds, R_δ(x̂) = max_z U_δ(z,x̂), reported honestly as None when no pure-error estimate exists rather than fabricated (Task 8).
Final comparison rested entirely on the fitted model confirm now measures a shortlist of finalists fresh and compares them independent of the response surface — the paper's terminal discrimination. Full fallback ladder in report.json: certified → terminal_best → model → measured → baseline, plus known_valid_baseline support (Task 9).
Validator only warned about aliasing at low design resolution; no action taken foldover is a real policy state that spends a measurement block, but only when resolving the alias could change the recommended answer (decide.alias_consequential, quantified over the alternative resolution's ε-optimal set) — the paper's "registered augmentation" (Task 10).
No epoch concept; a semantic failure was an unhandled abort with no answer A semantic exception now writes epoch_end-<N>.json, and the campaign always returns an action (never an empty/aborted result) even when it hits one. A later fixed re-run recompiles cleanly into a new epoch (Task 11).

Every strict xfail(strict=True) test planted in Task 3 — one per gap above, each naming which future task would flip it — is now a genuine pass. tests/test_optimize_harness.py carries zero xfails for the first time since Task 3. Full suite: 1915 passed, 1 skipped, 0 xfailed (from 1787 at the start of this phase).

Two verified pre-existing defects, fixed early (spec §4)

  • D1: every tabulated resolution-IV screen crashed at fit (design matrix is singular) — fit_effects requested one column per two-factor interaction, and at resolution IV aliased columns coincide. Fixed by collapsing to one coefficient per alias class.
  • D2: one infeasible row silently NaN-poisoned every fitted coefficient while effects.json stayed schema-valid. Fixed by fitting on the complete-row subset and recording exclusions (fit_exclusions.json).

Genuine judgment calls made during implementation, each independently reviewed and (where warranted) mutation-verified

  • confirm's terminal-discrimination shortlist, under model rejection (Task 11). The plan's brief specified that when the fitted model is rejected (lack_of_fit), confirm's shortlist should be built from measured-valid rows only, never the model's ranking. Measured directly: on the bowl and sla oracle surfaces, a strict measured-only rule discards the exact interior optimum refine had already found (because it's a model prediction, not yet a measured row) and, on the constrained surface, caps the achievable answer at 6.12% off truth regardless of how much additional confirm budget is granted — a structural dead end, not a tuning artifact, because a measured-only shortlist can only ever re-measure what's already on disk. The shipped rule instead seeds roughly half the shortlist from measured leaders and fills the rest from the model's ranking — every finalist still gets freshly measured before it can win, so nothing untested is ever returned as the answer. Independent review concluded this is required by the paper's actual constraint (never return an unmeasured configuration — a report-time rule) rather than a shortlist-membership rule, and is correct paper fidelity, not a deviation from it.
  • Behind these two calls: a standing project-wide rule, added mid-plan: nous has not reached GA, so until the project owner says otherwise, achieving a piece of work's correct design outranks preserving legacy observable behavior anywhere in the codebase (now CLAUDE.md, top-level, repo-wide — not scoped to this plan or kind). Several of the fixes above changed observable output from what nousko did immediately before this phase; each is named and argued in its task's commit, not silently introduced.

What Tasks 12–16 and the final review closed

Gap vs. the paper (pre-Task-12) What now exists
No mechanism-drift detection: build could author code and nothing verified the campaign still measured what it claimed to mechanism.patch / mechanism.sha256 snapshot with hard-fail-on-drift inside the epoch, checked against the compiled policy's own recorded hash (Task 12; scoping fix Tasks 13.5/14.5, below).
A build stage's authored tests could pass trivially (never actually exercising the mechanism) and its control could silently differ from production Two more build oracles: declared tests must fail before build and pass after, or they prove nothing; known_valid_baseline must equal the pre-build control within tolerance (Task 13).
No common-random-numbers support: a noisy systems target (queueing, caching, autoscaling) needed an order of magnitude more runs to get a usable bound workload.seed_env / workload.seeds — every row in a comparison gets a deterministic seed, confirm's replicates pair by index across finalists, and terminal_regret_bound computes a tighter paired bound when CRN is genuinely in effect (Task 14).
No worked systems-target campaigns; the adapter contract (run_command/test_command, the seed-honesty check, SLA-as-validity) existed only in scattered docstrings Three full example campaigns (vLLM batching, Qdrant HNSW, Knative autoscaling) plus docs/targets.md, a ~300-line contract doc with the actual detection recipe for "did the target really read the seed" (Task 15).
The compiled-policy architecture (policy.json, the six states, the fallback ladder, the artifacts an epoch writes) was implemented but not documented anywhere a human or a future agent would read first A new "The compiled policy" section in the authoring guide, a complete artifact inventory in docs/data-model.md §7, and the policy summary in CLAUDE.md — all describing what the code actually does, not the spec's idealized names (Task 16, below).

Two production defects found live, mid-plan, by running the drift oracle for real (not by the test suite) — this plan's own version of the "run it and see" defects from the section above:

  • Task 12's drift oracle hashed the target's entire working tree by content, with no allowlist. Reproduced independently by two reviewers with zero build stage and zero Task 13 code involved: a .pytest_cache/ or run.log left behind by Nous's own pre-existing test-running machinery was enough to trigger a false "mechanism drifted" abort — the worst available misdiagnosis, since it reads as a real problem with the mechanism under study. Fixed via two inserted tasks: Task 13.5 adds an opt-in optimization.build_checks.mechanism_paths allowlist (default unchanged — the whole-tree behavior — so no existing campaign's recorded hash is invalidated); Task 14.5 closes the follow-on gaps (glob-shaped entries silently half-disabling the allowlist; no --smoke-time check that declared paths actually exist). A further gap (normalization-shaped entries like "src//mech.py", and ".." specifically — which makes git exit non-zero and disarms the drift oracle with zero error anywhere) was closed in Task 14's own scope. One residual (absolute paths) was found and carried forward but is lower-risk — an author is far likelier to write a glob than an absolute path.
  • A pre-build baseline measurement crashed the entire build-declaring campaign before its one substantive model call ever ran. Task 13's own hoisted baseline_runs call invoked the target's run_command against a tree where the mechanism didn't exist yet — verified by direct A/B execution against the pre-fix commit (mechanism authored: 1 before the regression was introduced, 0/crash after, 1 again post-fix). Fixed by degrading gracefully (a pre_unavailable marker) rather than crashing.

The final whole-branch review

Per-task review is scoped to one task's diff; it structurally cannot see interactions between an early task's assumption and a late task's different one. After all 18 tasks closed, a dedicated final review traced the compiled state machine end-to-end across the whole ~37k-line diff (109 commits) rather than per-task, specifically hunting for that class of gap. It found and fixed four real cross-task defects:

  • report.json's recorded path was not epoch-scoped — a campaign whose epoch 1 ended in a semantic exception and recompiled into epoch 2 would report a path that spliced epoch 1's transitions into epoch 2's, naming a state epoch 2 never actually passed through.
  • The foldover escape hatch (optimization.policy.foldover: false) was unreachable from any schema-valid campaign — the schema's additionalProperties: false policy block never declared it. Measured the actual cost of the alternative (deleting the dead branch, as an earlier reviewer had recommended) before fixing: on the shipped vLLM example at default resolution, foldover stays registered-but-never-fired and enumerate_paths reports 42 reachable-looking paths through it that the campaign can never take — exactly the harm the hatch exists to prevent. Declared the field in the schema instead of deleting the hatch.
  • A test-only harness dataclass held both regret bounds under one ambiguous bare name (residual_regret) that meant the model bound at one stage and the terminal bound at another. Renamed to residual_regret_model (zero external consumers).
  • The artifact-inventory table added in Task 16 billed itself as complete but omitted one sibling artifact from the same call site it already documented two others from.

It also found, and the controller fixed directly, one live correctness gap: confirm was the only spending state without a nan_response → exception rule, unlike screen/foldover/refine, which each register the identical rule in the identical position. A NaN response at confirm fell through to confirm's own self-loop default and would run until an outside-the-policy iteration cap cut it off — the one outcome the state machine doesn't register as reachable for a spending state. The fix completes an already-three-times-repeated pattern for the fourth state; mutation-verified (reverting it reproduces the exact self-loop).

Three items were investigated and explicitly left open, tracked rather than force-closed:

  • The build oracle's pre/post baseline is measured with no workload.seed_env awareness. Not unsound (pre and post are symmetric — both seedless — so the comparison stays internally consistent) but CRN's variance cancellation is unavailable in a hard-abort gate whose default tolerance isn't meant to police run-to-run noise. A workload-variance-dominated target combined with a build stage could false-abort and blame the mechanism. Recommended as a tracked follow-up issue before any real target combines build with workload.seed_env.
  • A handful of OBSERVATION_KEYS have producers but no consuming rule (mostly deliberate/documented; one, behavioral_violation, is fully orphaned).
  • No JSON Schema governs report.json/recommendation.json/confirmation.json — the "the two regret bounds must never be collapsed into one number" rule currently rests on a single test assertion rather than a schema constraint.

The branch_id field named in spec §3.9 but never implemented (every transitions.jsonl row carries policy_hash plus the full fired rule instead) was adjudicated as not a real gap — it appears nowhere in the paper itself, and what's on disk is more informative than a bare id would be. Recorded as a spec amendment, not a code change.

Full suite after the final review's fixes: 2009 passed, 1 skipped, 0 xfailed (from 1787 at the start of Phase 1). tests/test_optimize_harness.py — the file that has carried this plan's oracle gate since Task 3 — has carried zero xfails since Task 11 and still does.

Process note

Every task went through the same four-gate pipeline: fresh implementer → independent task review → fix round(s) where warranted → controller-performed audit (scope check, interface check, a mutation the implementer/reviewer hadn't already tried, cumulative full-suite re-verification of every prior task). That audit step caught real issues on its own — including several cases across the plan (Tasks 3, 4, 6, 9) where a controller instruction to an implementer was itself wrong and the implementer's pushback, verified by direct execution, was correct. Two tasks (13.5, 14.5) were inserted mid-plan to close a live production defect discovered organically during implementation, following the same TDD/review/audit process as every planned task. All rulings — every judgment call, every case where a controller instruction was overridden, and their recorded cost-if-wrong — are recorded chronologically in this plan's SDD ledger. Per the workflow, that ledger is deleted at plan completion; the git history, task reports, and this PR description are the durable record.

mtoslalibu and others added 30 commits June 15, 2026 09:27
…s-Research#289)

Reflects the reflective branch merge (AI-native-Systems-Research#279): SDK dispatcher, meta-findings,
lineage, observability, statistical modules, and operational hardening.

Co-authored-by: Mert Toslali <toslali@ibm.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…I-native-Systems-Research#290)

* docs: add design spec for style-customized visualization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs: add implementation plan for style-customized visualization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(viz): add --summary-md flag for summary override

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(viz): add style customization to /visualize-campaign skill

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(viz): pass style intent through /post-campaign to visualization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(viz): address PR review issues for style-customized visualization

- Add insights.json assembly step: combine restyled dead-ends, frontiers,
  interactions into bundled format expected by --insights flag
- Fix design spec: remove unimplemented --dead-ends/--frontiers/--interactions
  flags, document that --insights handles the bundled format
- Fix :: delimiter parsing: specify "split on FIRST :: only", handle empty
  style after ::, document campaign names must not contain ::
- Add temp directory lifecycle: rm -rf before writing to prevent stale files
- Add all-fail behavior: ask user instead of silently showing canonical
- Add error handling for --summary-md: existence check with clear error message
- Restore blank line between code sections in visualize_campaign.py
- Clarify "parallel" means independent tool calls in a single response
- Fix architecture diagram to show actual 5-call split

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* chore: remove plan and spec docs from feature branch

These were working documents used during development and are not
needed in the final merge.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* docs(wiki): document style intent syntax for visualize-campaign

Add `:: <style>` delimiter documentation for /visualize-campaign and
/post-campaign skills, including style examples table and behavior
description for styled vs unstyled rendering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…esearch#296) (AI-native-Systems-Research#297)

The runtime reads campaign.channels at every DESIGN/FINDINGS gate
(orchestrator/iteration.py -> orchestrator/channels.py) and POSTs a
markdown gate summary to each configured channel, but campaign.schema.yaml
never declared the property while setting additionalProperties: false at
the top level. So `nous run` aborted at pre-flight with:

    Campaign validation error: Additional properties are not allowed
    ('channels' was unexpected)

making the documented channels: feature impossible to use — the campaign
is rejected before the run starts.

Add a channels property whose item shape mirrors exactly what
orchestrator/channels.py reads: kind in {webhook, slack} (optional;
defaults to webhook), url (webhook), webhook_url (slack), and optional
headers (webhook). additionalProperties: false on each entry catches
field typos, matching the strictness of the rest of the schema.

Add regression tests in tests/test_schemas.py (accept / default-kind /
invalid-kind / unknown-field) so the schema and runtime can't drift
apart again.

Fixes AI-native-Systems-Research#296

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Adds a factorial/response-surface campaign kind so optimization problems
are solved by estimating a response surface rather than by sequential
one-factor-at-a-time search.

Motivation is the composition barrier documented in "Discovery Is Easy,
Composition Is Hard" (2026-08-04): on the Certus cold-read path, five of
seven LLM optimizers stalled near half the available headroom because
batching was -9.5% in isolation but required for the winning compound.
Any search that prunes regressions cannot reach that optimum. Factorial
designs have no composition barrier because they estimate interaction
terms directly instead of composing increments.

Design was revised against the local campaign corpus, which is largely
optimization-shaped already:
  * alert-threshold-robustness hand-rolls a 1350-cell grid that a
    resolution-V design covers in ~50 runs -> multi-level factors
  * ordering-theorem's headline finding IS an interaction (7.3x) ->
    resolution V by default, categorical mechanism factors
  * composite-sensitivity-boundary trades per-regime -> constrained
    multi-regime response, not a scalar objective
  * holdout-selection separates a held-out key -> mechanical leakage
    refusal

Token cost drops by moving the per-configuration decision out of the
model: ~3 substantive LLM calls per campaign against 60-90 benchmark
runs. findings/principles are projected deterministically from fitted
effects, following the pure-Python meta_findings pattern (AI-native-Systems-Research#155), so the
durable artifact set is preserved without per-iteration prose.

Correctness is bought with two mandatory check families per factor:
manipulation predicates (did the lever engage?) and native-idiom
property/metamorphic tests committed to the target repo (is the
mechanism correct?). Relations are classified correctness (hard-fail)
vs behavioral (recorded finding) so a genuine non-monotonicity is a
discovery, not a crash.

Structure is a parallel orchestrator/optimize/ subpackage with one
delegation point, leaving the reflective path untouched. Scopes the
AI-native-Systems-Research#159 tier ladder to the reflective kind, with the justification that a
pre-registered matrix strengthens rather than weakens the
anti-p-hacking property it protects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cleans up the campaign spec for end-user (and AI-author) ergonomics
after reviewing how the corpus actually declares knobs.

Factor types collapse from continuous|ordinal|categorical to
numeric|choice. The retired vocabulary asked the author a statistics
question; the new one asks a domain question they can answer directly --
"are values between these levels runnable?". decay_guard ∈
{off,0.004,...} makes the distinction concrete: 'off' sits in a list of
numbers, so it is a choice, and no author has to reason about how a
fitter would interpolate to find that out.

Adds `grid` to make the reported optimum runnable: refinement fits
continuously, then snaps the stationary point to the grid step, so
confirm always runs a configuration that exists and validates the
snapped point rather than the theoretical one. This closes the open
question about ordinal refinement -- restricting to declared levels
would forfeit interior optima, and plain rounding can report a config
the target cannot run.

Replaces the manipulation `assert: "== {value}"` mini-DSL with the same
{observable, op, value} shape already used by constraints and regimes,
so there is one comparison vocabulary in the spec and no bespoke parser.
Adds when/when_not guards, since a check is often meaningless at one
level. `{level}` is now the only interpolation token anywhere.

`apply` accepts a bare string as CLI-flag shorthand; screen_levels
defaults to first/last so the common two-level case needs neither.

Adds §11, a full worked example restating alert-threshold-robustness
(the corpus campaign that hand-rolled a 1350-cell grid) as ~40 runs.
Verified both YAML examples parse and self-conform to the spec's own
rules -- which caught severity_boundary declaring only a behavioral relation,
i.e. an example that would have failed its own validator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Campaign authors need to supply domain knowledge beyond knobs -- "the
connector must be P2P", "the workload is single-tier", "explore
shortest-job-first". Today that lives in target_system.description,
which the spec inherits unchanged and which remains right for narrative
framing.

But prose alone cannot carry it in this kind: screen and refine make
zero model calls, so a directive that exists only as prose is
unenforceable during exactly the two stages that spend the benchmark
budget. That is the AI-native-Systems-Research#221 failure mode -- honored by the phase that reads
prompts, ignored by the phase that does the work.

So author intent splits by who must act on it:

  design_space.invariants -- properties Python checks on EVERY config,
  in every stage, using the same {observable, op, value} vocabulary as
  constraints and manipulation. Rejected pre-run when statically
  determinable from the matrix row, hard-failed post-run when only
  observable afterwards. Distinct from locked_parameters (which pins an
  input) because "the connector is P2P" is a property of the resulting
  system, possibly emergent from several settings -- asserting the
  observed transfer path is stronger than pinning the flags believed to
  produce it. Distinct from response.constraints, where a violation
  means "this config is infeasible, keep exploring" rather than "the
  campaign left its design space".

  guidance -- two named prose slots, factor_nomination (read at verify)
  and interpretation (read at confirm), separated because blending them
  wastes tokens in both.

The authoring rule: anything you would be upset to find violated after
60 runs is an invariant, not guidance.

The worked example gains a lookahead-bias tripwire as I2, which is the
case that makes the argument concrete -- "don't peek past the OOS
boundary" reads as obviously-satisfied in prose and renders every
downstream number meaningless while still looking excellent.

Adds the invariant-violation row to the failure taxonomy, the
prose-instead-of-invariant anti-pattern to the guide outline, and
re-verifies both YAML examples parse and self-conform.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
13 tasks, 63 TDD steps, from the approved design spec. Full test bodies and
implementations for the four statistically load-bearing tasks (factors,
predicates, design generation, effect fitting); precise assertion lists for
the mechanical ones (matrix, relations, stage rule, artifacts, runner,
schema, wiring, regression, guide).

Every numeric assertion in the plan was executed and verified before being
written down, not assumed:
  * resolution V on 5 factors is 16 runs with generator E=ABCD and zero
    aliasing; resolution III on 7 factors is 8 runs with 21 two-factor
    interactions confounded onto mains (Box-Hunter-Hunter)
  * the closed form contrast/N equals numpy lstsq to machine precision and
    recovers planted coefficients, including the L5 sign flip (main -0.95,
    interaction +1.60) that defeated five of seven optimizers in the study
  * pure-error variance 0.00092 on df=4; rotatable alpha sqrt(2) for k=2;
    grid-snapped midpoints 9.0 and 0.85
  * float error is ~8.9e-16, so == is flaky and every assertion uses
    math.isclose

Self-review caught one dropped requirement: integrity_command was
declarable in the schema but nothing executed it, leaving one of the
spec's three guardrails inert. Task 9 now runs it per config through an
injected seam and treats a non-zero exit as rejected rather than
infeasible -- corrupt output is not evidence about the design space.

No new harness dependencies: orchestrator/optimize/ uses stdlib arithmetic
plus the already-declared scipy.stats. numpy stays out (it is currently
unused across orchestrator/), and hypothesis/rapid/proptest belong to
target repos, not to Nous.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Factors declare type: numeric | choice — a domain question ("are values
between the levels runnable?") rather than a statistics one. grid snaps a
fitted optimum to a runnable step so confirm never tries to run K=4.7.

Rejects the retired continuous/ordinal/categorical vocabulary, factors
without a manipulation check, and factors whose only relation is
behavioral (nothing would then catch a broken lever).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two Global Constraints were wrong in ways that would mislead every
implementer:

pytest is not installed in the project venv, so `.venv/bin/pytest` (28
references) does not exist. The working invocation is the system binary at
/opt/homebrew/bin/pytest; `.venv/bin/python` remains correct for running
Python directly. Discovered while independently verifying Task 1's test
claims -- the risk is an implementer reporting "tests pass" after running
nothing.

numpy IS importable inside the venv, pulled in transitively by scipy. The
no-new-dependencies rule therefore cannot be enforced by "it would fail to
import" and must be checked statically. The check must use AST import
extraction rather than grep: Task 1's optimize/__init__.py docstring names
all five forbidden libraries as a warning to future authors while importing
none of them, so grep reports a false positive.

Records the expected baseline (1374 passed, 1 skipped) so a regression is
visible rather than inferred.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
manipulation, response.constraints, response.regimes and
design_space.invariants share one {observable, op, value} shape, so there
is no bespoke assertion mini-language to learn or mis-type. when /
when_not guard which levels a check applies to.

is_trivial() flags predicates that cannot fail (> 0, != null): a lazy
check makes a broken lever look verified, which is worse than no check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…etection

Fix round 1 of code review findings:

I1: Add validation to reject predicates with both when and when_not guards,
as they are complementary level guards. The other three check families
(design_space.invariants, response.constraints, response.regimes) never go
through parse_factors, so this module is the single place that can catch
this error for all four check families.

I2: Add missing: bool field to Verdict to distinguish an absent observable
(telemetry never emitted) from a genuine comparison failure (telemetry
emitted but has wrong value). Task 9's runner needs this distinction;
adding it now prevents retrofit after callers depend on detail strings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Fractional designs use published generators (Box-Hunter-Hunter;
Montgomery Table 8.14) so the alias structure matches the textbook rather
than whatever the code happens to compute. Verified: 5 factors at
resolution V is 16 runs with E=ABCD and zero aliasing; 7 factors at
resolution III is 8 runs with 21 two-factor interactions confounded onto
main effects.

alias_pairs() returning [] is the resolution-V property -- every main
effect and 2-factor interaction separately estimable. That matters because
ordering-theorem's headline finding IS an interaction (preemption + FIFO
= 7.3x worse), which a main-effects-only screen inverts.

Center points are what make a lack-of-fit test possible; without them a
campaign cannot say whether its own model form is adequate.

Fixed two bugs relative to the plan's reference snippet, both caught by
its own tests: (1) fractional_factorial's "unachievable resolution"
error branch was dead code because min_runs_for's fallback made the
guard condition always true; (2) alias_pairs double-counted resolution-III
aliasing (42 instead of 21) by also reporting 2fi-2fi matches that were
purely transitive echoes of a shared main-effect alias already reported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Audited every _GENERATORS entry against its claimed resolution rather than
only the two the plan's oracles cover. The (7,5) and (8,5) entries were
labelled resolution V but are actually resolution IV: 3 and 9 two-factor
interactions respectively are confounded with each other.

This is the worst of the three plan bugs found so far because resolution V
is the feature's default and its entire justification. A campaign screening
7 factors would be told it has unaliased two-factor-interaction estimates
while 3 pairs are confounded, and the fit would report tight confidence
intervals on the aliased terms -- nothing downstream can detect that.

Corrected to the verified generators: 7 factors res V is n_base=6 with
G=ABCD (64 runs); 8 factors res V is n_base=6 with G=ABCD, H=ABEF (64
runs). Both re-audited as balanced, mains mutually orthogonal, zero 2fi on
mains, zero 2fi on 2fi. These match the literature -- 2^(7-1) and 2^(8-2).
The plan's claim of 32 runs for either was wrong; 32 runs cannot carry
resolution V at those factor counts.

The run-count increase is the true price of unaliased 2fi estimation, not a
regression. Task 10's max_runs rule is what surfaces that cost honestly to
a campaign author.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Auditing every _GENERATORS entry (not just the two spot-checked oracles)
found that (7,5) and (8,5) claimed resolution V at 32 runs but actually
had 2fi-on-2fi aliasing (3 and 9 pairs respectively) -- genuine resolution
IV mislabeled as V. A campaign screening 7-8 factors would have been told
its two-factor interactions were separately estimable when several were
confounded, with tight confidence intervals on the aliased terms hiding
the problem completely.

Corrected to the literature values: 7 factors res V is G=ABCD at 64 runs
(2^(7-1)); 8 factors res V is G=ABCD, H=ABEF at 64 runs (2^(8-2)). Both
independently verified: balanced columns, mutually orthogonal main
effects, zero 2fi-on-main and zero 2fi-on-2fi aliasing.

Added a parametrized audit test that regenerates the alias structure for
every _GENERATORS entry from first principles and checks it against its
claimed resolution, so a future entry is audited automatically rather
than trusted on a comment. The two original oracles (5 factors res V =
16 runs/0 aliases; 7 factors res III = 8 runs/21 aliases) are unchanged
and still pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…licit

min_runs_for returns 2**k for any (k, resolution) not in _GENERATORS. That
value is a conservative upper bound, not the true minimum -- a smaller
fractional design may exist but isn't tabulated here. The prior docstring
didn't say this, inviting a caller (e.g. a future run-budget feasibility
check) to treat the fallback as an authoritative minimum and reject a
campaign that a real design could satisfy far more cheaply.

Docstring now states the contract plainly and tells callers to check
`(k, resolution) in _GENERATORS` before trusting the value as a minimum.
No behavior change. Added test_min_runs_for_is_exact_only_for_tabulated_cells
to pin the fallback's documented semantics (exact for tabulated cells,
2**k upper bound otherwise) so it doesn't drift silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both failed against a correct implementation, so they were plan defects
rather than implementation bugs. Diagnosed from the failure output and
verified fixed before writing.

test_aliases_are_carried_onto_the_fit_as_a_caveat called fit_effects with
the default include_interactions=True on a saturated resolution-III
7-factor design. That design has 8 runs; the requested model has 29 terms
(intercept + 7 mains + 21 two-factor interactions), which is not estimable,
so fit_effects correctly raised "design matrix is singular". The property
under test -- that aliasing propagates onto the Fit as a caveat -- needs no
interaction terms at all. Now passes include_interactions=False and
verifies 21 alias pairs carry through. The singular-matrix guard stays as
is; refusing an unestimable model is correct and valuable.

test_strong_curvature_is_detected_as_lack_of_fit set all four center points
to the identical value 14.0. Identical replicates give pure_error_var == 0,
which trips the implementation's own `pe_var > 0` guard, skips the F test,
and leaves lack_of_fit_p as None -- exactly what the test then asserted
against. The center values now carry small distinct perturbations, so
pure error is real and the lack-of-fit F test fires as intended (verified:
pe_var 2.9e-04, F 1.1e+05, p 6.1e-08).

The `pe_var > 0` guard must not be relaxed: zero pure-error variance means
no independent error estimate, and dividing by it would yield an infinite F
statistic. Reporting that as significance would be fabricating a result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
For orthogonal +/-1 designs the OLS coefficient has the exact closed form
contrast/N, verified equal to numpy lstsq to machine precision -- so the
arithmetic stays auditable and numpy stays out of the harness.
Non-orthogonal (central-composite) fits use a direct solve of the normal
equations; the systems are tiny.

The load-bearing test plants the L5 sign flip -- main effect -0.95,
interaction +1.60 -- and asserts the fitter recovers both. That is exactly
the landscape that defeated five of seven optimizers in the motivating
study: a factor harmful alone and required for the winning compound.

Significance is left as None when there are no replicated center points,
rather than guessed. An unknown effect is not a null effect, and a
fabricated interval is worse than an absent one.

Two brief-provided tests were defective and were fixed rather than the
implementation weakened:
- test_aliases_are_carried_onto_the_fit_as_a_caveat requested 29 terms
  (1 + 7 mains + 21 2fi) from an 8-run resolution-III design; the singular
  matrix ValueError was correct, so the test now passes
  include_interactions=False, since the property under test (aliases
  carried forward) doesn't need interactions fit at all.
- test_strong_curvature_is_detected_as_lack_of_fit set all four center
  replicates to the identical value 14.0, giving pure_error_var == 0.0 and
  correctly disabling the F test (dividing by zero pure error would
  fabricate significance). Center values now carry small distinct
  perturbations around 14.0 so pure error is non-zero and the lack-of-fit
  F test fires as intended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…king

expand() turns a coded design row into a runnable config via each factor's
apply spec -- the seam that removes the LLM from the inner loop. {level} is
the only interpolation token.

Run order is randomized from a recorded seed, so time-ordered drift
(thermal, cache warming) cannot masquerade as a factor effect while the
campaign stays reproducible.

check_fidelity reports three violation classes -- level drift, missing
planned row, unplanned extra row. All three are hard failures: a silently
skipped cell changes the design's actual resolution, so tolerating it would
let the campaign overstate what it can estimate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 5's assertion 8 named `aliases` as a design_matrix.json field but never
said to populate it from design.alias_pairs(), so a faithful implementation
left it empty. Verified: a resolution-III 7-factor design has 21 alias pairs
per alias_pairs(), while its payload recorded aliases: [].

design_matrix.json is the pre-registered artifact a human or AI reads to
judge what a screen can actually estimate. An empty list on a heavily
confounded design does not merely omit information -- it asserts the
opposite of the truth, which is the class of defect this feature exists to
prevent.

Now requires both directions be asserted: res III carries a non-empty list
matching alias_pairs(), and res V carries an empty one, so the resolution-V
property is visible in the artifact rather than merely absent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nous checks a contract, never test code: each relation names a native_test,
the declared test_command runs, and the identifier must appear in the
results having passed. hypothesis / rapid / proptest / RapidCheck are the
target repo's business, so the harness needs zero language knowledge.

A relation declared but absent from the results is a FAILURE, not a pass --
otherwise a typo'd identifier silently disables a correctness gate.

classify_failures keeps behavioral violations out of the correctness
bucket: a monotonicity break is a discovery (L5 was -9.5% alone and
required for the winning compound), and hard-failing on it would make the
campaign blind to what it exists to find.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
matrix_payload's aliases key was always []. A resolution-III design
recorded aliases: [] even with 21 confounded pairs -- the pre-registered
design_matrix.json artifact silently asserted the opposite of the truth.

Populate it from design.alias_pairs(design), which already returns a
sorted list, so the payload stays deterministic across calls.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fit_effects computed one se = sqrt(pe_var / n) shared across every term,
where n was the total row count including center (and, on a central
composite, axial) points. Those points contribute 0 to a +/-1 coded
column's sum of squares, so they inflate n without adding information
about that term -- systematically understating every two-level term's SE
and CI half-width whenever non-corner points are present.

On the resolution-V 5-factor + 5-center design (16 corners, 21 rows) this
understated CI half-widths by sqrt(21/16) ~= 1.1456 (about 14.6%), enough
to flip real decisions: effect sizes 0.0190-0.0205 read as significant
under the buggy scalar SE but correctly read as not-significant once SE
is computed from each term's own column. Verified by temporarily
reverting to the scalar form and confirming the new regression test fails
with significant=True (false positive) before the fix and passes after.

Fix: se_j = sqrt(pe_var / sum_i x_ij^2), using that term's own column
(cols[idx]) rather than a shared n. This also generalizes correctly to
central-composite fits, where axial points contribute a different sum of
squares per column, so no single scalar could ever be right for all terms
there either.

The pe_var > 0 guard, None significance with no center points, and the
L5 sign-flip oracle (main -0.95, interaction +1.60) are unchanged and
still pass exactly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The reference fit_effects used one scalar se = sqrt(pe_var / n_total) for
every term. Centre points contribute 0 to a +/-1 coded column, so they
inflate n without informing a main effect: sum(x^2) is 4 for A/B/AB but 8
for the intercept on a 2-factor + 4-centre design, understating main-effect
SEs by sqrt(2). On res-V 5-factor + 5 centres it is 14.6% too narrow.

This was the most dangerous defect of the run because it changes decisions,
not just numbers. Verified: at true effect sizes 0.0190-0.0205 on that
design the buggy formula reports significant=True where the correct SE gives
False -- a false positive. dropped_factors keys off significant and the
stage rule keys off dropped_factors, so a campaign would spend refinement
budget on noise and fit an optimum over knobs that do nothing. All 15 tests
passed regardless, because no planted effect sat near the boundary.

Now computes se_j = sqrt(pe_var / sum_i x_ij^2) per term. Also documents the
limitation found while validating the fix against an explicit (X'X)^-1
inverse: that formula is exact only where a column is orthogonal to all
others, which holds for main effects and 2-factor interactions on every
design this module generates, but NOT for the intercept or pure-quadratic
terms on a central composite (exact 0.4208 vs 0.2887 on a 2-factor CCD).
Quadratic CIs are therefore optimistic; they describe surface curvature and
are never used as a significance gate, and solve_stationary_point consumes
the estimates rather than their intervals.

Adds the boundary test whose absence hid this, asserting the SE matches the
corner count and that a 0.02 effect inside the noise floor is not reported
significant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The per-term se_j = sqrt(pe_var / sum_i x_ij^2) formula equals the exact
sigma * sqrt((X^T X)^-1_jj) only when that term's column is orthogonal to
every other column in the model. That holds for every main effect and
two-factor interaction on every design this module generates -- the terms
dropped_factors and the stage rule actually gate on -- so those CIs are
exact. It does not hold for the intercept or the pure-quadratic terms on a
central composite, whose columns are mutually correlated: measured against
an explicit (X^T X)^-1 inverse on a 2-factor CCD, A^2/B^2 exact SE is
0.420813 vs. 0.288675 from the per-column formula, about 1.46x too narrow.

Documented rather than fixed with a full matrix inverse: quadratic terms
describe surface curvature and are consumed by solve_stationary_point as
point estimates only, never gated on their CIs. No behavior change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Between-stage adaptation is arithmetic on effect sizes, not a model call:
drop factors whose CI contains zero, refine when a multi-level numeric
factor survives, otherwise go straight to confirm. Iteration N+1 inherits
estimates and intervals rather than prose, which is a stronger form of
"use what N learned" than principle-passing alone.

Four triggers name the cases where Python cannot decide and the model
should be re-consulted: every factor within noise (wrong factor set),
significant lack of fit (wrong model form), stationary point outside the
declared hull (ranges too narrow), and a behavioral relation violation
(possible real non-monotonicity worth interpreting).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Task 7's interface list gave decide_after_screen(fit, factors, *, alpha) and
decide_after_refine(fit, factors, stationary) -- neither accepting relation
verdicts. So Trigger.BEHAVIORAL_VIOLATION was defined but impossible to set:
verified that it appears only in stage.py's docstring, and that
classify_failures (which produces behavioral failures) has no call site
there. A required escalation signal was dead code.

It is required. The design spec lists it as escalation trigger 4 in section
6.3 ("a behavioral relation violated -> possible real non-monotonicity worth
interpreting") and names the same case in the section 6.4 failure table.

Both functions now take behavioral_failures as an optional tuple defaulting
to empty, so existing call sites and the screen stage (which can run before
any native test run) are unaffected. stage.py stays a pure function of its
inputs -- the caller runs the target's tests and classifies verdicts; this
module only reports that the signal arrived.

Adds three assertions, including the one that matters most: a
behavioral-only trigger must still ADVANCE the stage. A monotonicity break
is a discovery -- the motivating case is a lever measured -9.5% alone yet
required for the winning compound -- so halting on it would make the
campaign blind to exactly what it exists to find.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BEHAVIORAL_VIOLATION was a dead enum member: nothing could set it, since
neither decide_after_screen nor decide_after_refine had an input carrying
behavioral relation verdicts (relations.classify_failures's second bucket).
The design spec's escalation trigger 4 requires it.

Add an optional behavioral_failures: tuple[RelationVerdict, ...] = ()
keyword to both functions. A non-empty tuple raises BEHAVIORAL_VIOLATION
and names the relation id(s) in the rationale, but never blocks stage
advancement -- a behavioral violation is a discovery worth interpreting,
not a reason to stop (the motivating case: a lever measured -9.5% alone
yet required for the winning combination). The empty default keeps every
existing call site and test unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ction

Four new schemas (design_matrix, runs_row, effects, relations) plus writers
using atomic_write. runs.jsonl is append-only so a crash leaves completed
rows intact.

project_findings() is what makes "zero LLM calls at screen and refine" true
without dropping the durable artifact set: a fitted effect with a
confidence interval already contains a claim, a direction, a magnitude and
quantitative evidence, so restating it in prose would cost tokens and add
nothing. Pure Python, following the meta_findings (AI-native-Systems-Research#155) precedent, and it
validates against the UNCHANGED findings schema -- so /post-campaign,
index-wiki, visualize-campaign and the registry keep working untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The append-only requirement said "a crashed run must leave the completed
rows intact" -- satisfied by never rewriting the file, but insufficient.
Verified: append 3 complete rows, then a torn final line simulating a crash
mid-write, and read_runs raises JSONDecodeError, so all three completed rows
become unreadable. They are preserved on disk and unusable, which
operationally loses them.

That defeats the guarantee append-only exists to provide. The realistic
scenario is a campaign dying partway through a 60-run sweep; it should refit
on whatever completed and honestly report the reduced resolution -- the
degrade-the-claim-not-the-data behaviour the spec asks for -- instead of
failing to read its own results.

read_runs must now skip a malformed TRAILING line (the only line a crash can
tear) and report the skip. A malformed line in the middle must still raise:
that is not a crash signature, and tolerating it would hide real corruption
rather than recover from an interrupted write.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
append_run writes runs.jsonl one line at a time, so the only way a line
can be malformed is a crash mid-write of the LAST line -- every earlier
line was already flushed by a prior, completed append_run call. Before
this fix, read_runs raised JSONDecodeError on that torn line, which made
every already-completed row unreadable -- operationally indistinguishable
from losing them, defeating the entire reason runs.jsonl is append-only
(a crashed run must leave completed rows intact and refittable).

read_runs now skips a malformed line only when it is the last non-blank
line, logs a warning naming the file/line number so the skip is visible
rather than silently swallowed, and still raises on a malformed interior
line -- a crash cannot tear an interior line, so that failure mode is
real corruption, not a crash signature, and must not be tolerated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sriumcp

sriumcp commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

Three commits pushed from a field test of this kind against a real target (BLIS / inference-sim), running kind: optimization and kind: reflective head-to-head on the same problem. Two of them fix defects the field test found in this PR's own machinery; the third documents the apparatus lessons.

fix(optimize): implement apply.kind config_patch, which applied nothing (2c06978)

config_patch was a documented, schema-valid apply kind that was never applied. matrix.py:_render_apply rendered it into a {"patches": [...]} payload and nothing consumed it — runner.py applies only cli_args and env.

Consequence: a campaign whose factors use config_patch runs every row at the baseline configuration while the design matrix, the runs, and the fitted response surface all look real. Neither nous validate campaign (0 errors, 0 warnings) nor --smoke caught it, because the run succeeds — it just measures the wrong thing. Only the run-time manipulation predicate noticed, on row 1 of 18, after a full build stage and ~50 minutes.

Now implemented with RFC 6901 pointers, per-run copies (the author's file is never mutated), a boundary-anchored command rewrite, end-to-end type preservation, and both silent-failure paths closed: validation rule 17 rejects a path absent from run_command, and --smoke verifies each patch actually reached the target.

feat(optimize): optimization.run_timeout_sec (a879fb8)

The run_command timeout was hardcoded to 600 s with no campaign override. This blocks a whole class of objective: one whose evaluation is itself a search. A capacity objective — "highest arrival rate at which the backlog does not grow" — is a bisection over ~5 target runs per point, so per-invocation cost is a multiple of a single run's, and the campaign had no way to say so.

Absent resolves to 600, so no existing campaign changes behaviour. resolve_run_timeout is public because --smoke and the epoch must use the same ceiling — a probe at 600 while the epoch runs at 5400 would turn the one contract check into a source of mismatches.

Also fixes a pre-existing artifact-governance defect found while declaring the field: design_matrix.schema.json has additionalProperties: false but stage_runner was already writing four undeclared fields (policy_hash, workload_seeds, paired, held_fixed). Verified against a real artifact — every campaign with a policy has been emitting a pre-registration record that violates its own schema. Nothing caught it because the schema test only validated a freshly-built payload, never an enriched one. All five now declared, with a test that drives a full synthetic campaign and validates the on-disk artifact. No field became optional.

docs(optimize): §7 pre-flight (0a5b800)

Seven wrong/right pairs, each a defect this field test shipped, following §6's existing convention. Highlights:

  • Size run_timeout_sec from the worst corner, not a typical one. Run order is randomized, so the slow corner may run first — you don't get a gentle warning. Timing the baseline (~330 s, comfortably inside 600) then dying on row 1 is exactly what happened.
  • --smoke checks that a lever engaged, not that it matters. Eight candidate factors, three unusable: two aborted the target, two were config-captured but consumed by no mechanism.
  • Measure the noise floor at the operating point. Apparent −6.8% factor effects in a queue-bound regime became 0.3–3.3% against a 4.8% floor at a healthy one — the large effects were queueing dynamics correlated with the config change.
  • Plus: objective fittability (a P99 moving +268% on one factor change while P90 moved +9%; a request deadline pinning three configs at ~300,000 ms), confirming the workload exercises the mechanism (a KV-cache campaign at hit rate exactly 0.000), fail-loud probe harnesses, and pointing certifying relations at tests that fail without the mechanism.

All three: 2085 passed, 1 skipped (from a 2068 baseline).

sriumcp and others added 7 commits August 21, 2026 18:47
…he slowest corner

The original §7.1 advice ("measure the two extreme corners, take the larger,
double it") is incomplete for a compound objective, and the gap cost a row in a
real campaign even after the ceiling had already been raised once from a corner
measurement.

When the objective is a search — a bisection over arrival rate, a ramp, a
convergence loop — each probe's cost depends on the VALUE being probed, not only
on the configuration. A capacity search at rate r over horizon T simulates r x T
work, so a probe at 24 req/s costs ~16x one at 1.5 req/s. The bracket's reachable
ceiling therefore sets the worst-case row:

    worst row ~= (max reachable probe / baseline probe) x baseline row cost

with max reachable probe = hi x 2^(expansion steps) for a doubling search. A
bracket of [0.75, 3.0] over 5 evaluations reaches 24 — 16x baseline — so a 330 s
baseline row implies a ~5,300 s worst row. A 2,400 s ceiling looks generous
against the measured corners and is still 2x short.

The perverse consequence is what makes this a trap rather than an oversight: a
BETTER configuration costs MORE to measure, because the search must climb higher
before it finds a failing point. The rows most likely to time out are therefore
the ones carrying the best candidates, and losing them biases the fitted surface
toward mediocre configurations while every diagnostic still looks healthy. The
guidance is now to bound the search explicitly — cap the bracket or the probe
value — rather than letting the timeout do it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… not a speed knob

Adds a measured lesson and de-simulator-ifies the whole of §7. Nous is not
tied to any one target, so the section now speaks in target-agnostic terms
("load level", "observation window", "an extreme tail statistic") while keeping
the concrete numbers that make each lesson credible.

New §7.6. Many objectives are computed over a window of observed behaviour — a
trend, a rate, a steady-state level. Such a metric has a SETTLING requirement:
the window must be long enough that start-up behaviour stops dominating it. Trim
it for speed and you do not get a noisier version of the same measurement, you
get a different and biased one.

Measured, same configuration and load and seeds:

  short window (2/3 length): 38-44 s/run, ~2540 samples, trend +0.1643 / +0.0625
                             -> verdict GROWING
  full window:               70-74 s/run, ~3875 samples, trend +0.0553 / -0.0247
                             -> verdict SUSTAINED

Opposite verdicts, and the short window is 1.8x faster — which is exactly what
makes it tempting. The short window still contains the cold-start ramp, and a
trend fitted across a ramp reads as growth.

The bias is not symmetric: a too-short window declares saturation early on EVERY
configuration, so every reported optimum shifts the same way. A 1.8x speedup that
moves every number one direction is not a speedup, it is a different experiment.

Validation recipe: measure a configuration you believe is comfortably inside
capacity at your candidate window and at 1.5x it. If the verdict or fitted value
changes, the metric has not settled. Lock the longer window into
locked_parameters so a later iteration cannot quietly trim it.

Also notes this is the same failure mode as computing the statistic over the
wrong REGION rather than the wrong LENGTH — in the same campaign a trend computed
over a window including the post-arrival drain reported "stable" for a system
whose backlog grew throughout the active phase.

§7.1 reworded away from a specific target's flag names, and its worked example
now says plainly that the 2400 s ceiling it describes killed two rows.
Checklist gains the window check and renames row 7 to worst-PROBE timing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…onfirm blocks

`kind: optimization` executed its design strictly sequentially with no way for a
campaign to request concurrency: `execute_design` was a plain `for row in rows:`
loop, there were no concurrency primitives anywhere in `orchestrator/optimize/`,
and no schema field. Measured on a real campaign, an 18-row screen where rows
take 5-40 minutes is several hours of wall clock on a mostly-idle machine.

This adds concurrency on the ONE axis where it costs the methodology nothing, and
structurally forbids it on the axis where it would not.

WHY NOT SIMPLY PARALLELIZE THE SCREEN. `stage_runner.py`'s run-order comment
explains that randomized run order exists to protect a factorial design against
drift confounding — a warming cache, a throttling machine, a background job.
Co-scheduled rows contend, so a row's response would depend on which other rows
ran beside it. For an objective that measures where a system saturates, that is
first-order. `resolve_max_parallel(opt, *, stage_name)` therefore returns 1 for
every stage except `confirm`, so screen/foldover/refine cannot opt in even by
declaring the field. Verified: a campaign declaring 4 resolves to 4 at confirm
and 1 at screen.

WHY CONFIRM IS DIFFERENT. `_confirm_rows` emits one complete replicate block at a
time, with the run-order shuffle INSIDE a block. Within one block each finalist is
measured exactly once, so contention is symmetric across precisely the things
being compared — which is what makes it harmless. Blocks are therefore a BARRIER:
rows are grouped by `ConfigRow.replicate` in first-appearance order (never sorted,
so the in-block shuffle survives) and each group drains before the next is
submitted. A pool kept full across the boundary would overlap one finalist's
replicate 2 with another's replicate 3 and reintroduce the asymmetric-neighbour
confound invisibly.

POSITIONAL ORDER, which is correctness-critical. `_finish_confirm` appends
measurements in row order and `terminal_regret_bound` zips them, so position i
must be replicate i for every finalist; a reordering would silently mispair the
paired-differences bound rather than fail. Guaranteed structurally: outcomes are
written into a pre-sized `results[pos]` slot per INPUT position, never appended
from a completing worker, with a defensive abort if any slot is unfilled. The test
scripts per-row delays so completion order is the exact reverse of submission, and
asserts the reversal really happened (otherwise it proves nothing), that indices
come back [0..5], AND that response values land against the right indices — so an
index-only fixup still fails.

`parallel_arms.run_units` was NOT reused. Its `max_parallel` is validated and then
ignored (the body is a synchronous append loop, so its "same order as units"
promise holds only because nothing is concurrent), it is typed to
`ArmUnit`/`ArmUnitResult` rather than `ConfigRow`/`RunOutcome`, and it has no
notion of a replicate block — which is the entire statistical basis for this
bound. Axis C (across arms) remains its job.

Threads rather than processes: every unit of work shells out and waits, so the GIL
is held only to spawn and parse, and processes would break the injected-callable
seam a test's in-process fake runner depends on. The measurement path was already
race-safe — `config_patch` materializes a per-run copy of every patched file.

`design_matrix.json` records the EFFECTIVE value beside `run_order_seed`, so a
screen matrix reads 1 even under a campaign declaring 4. A pre-registration that
claimed randomized run order while executing concurrently would assert a guarantee
it did not provide. Rule 19 WARNS when the value exceeds the validating machine's
CPU count, naming the count it compared against since nothing in the campaign
declares target cores.

The guide gives prominent placement to the axis this does NOT implement: the
biggest wall-clock win is usually independent probes INSIDE one objective
evaluation (target-side, in `run_command`), which speeds up every stage and
introduces no cross-row confound. `max_parallel` only helps confirm.

Invariants: no if/elif decides a next stage (the one `stage_name` comparison
returns an integer bound), and no model or dispatcher call is added anywhere.

Tests: 23 new behavioral tests. Full suite 2108 passed, 1 skipped (from 2085).

Known pre-existing defect found and deliberately NOT fixed here: a confirm
round's `design_matrix.json` has never validated against its schema on any
revision — it carries `finalists`, `round`, and `kind: "shortlist_replicate"`,
none declared. Verified against a clean tree. Worth its own change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`design_matrix.json` IS the pre-registration record — the artifact attesting what
was registered before any result was seen. On every prior revision, a `confirm`
round's matrix failed its own schema, so that attestation could not be relied on.

An audit (not a three-field patch — the same drift class was already found once on
this file) turned up NINE undeclared things, including two whole capabilities the
schema never permitted:

  round              -> integer, minimum 1 (the registered {"round": {">=": n}} guard reads it)
  finalists          -> array, minItems 1, closed items requiring key/levels/why
  kind "shortlist_replicate" -> added to enum
  kind "foldover"    -> added to enum: an ENTIRE design family the schema rejected
  folded_on          -> ["string","null"]; null means full foldover, not a missing value
  screen_iteration   -> integer, minimum 1
  alias_consequential-> array of 2-item string pairs, mirroring `aliases`
  role "confirm"     -> added to the row role enum
  apply.finalist     -> integer, minimum 0

`design.combine`'s `kind: "combined"` is deliberately EXCLUDED: it is produced at
fit time over two already-registered blocks and never reaches disk, so declaring
it would describe an artifact no stage writes. `build.py`'s `role="baseline"` rows
likewise — negative row_index, outside design-matrix bookkeeping.

The schema was NOT weakened to make this pass. `additionalProperties: false`
stands, and because `round`/`finalists` cannot be globally required (screen,
foldover and refine write neither) while a `shortlist_replicate` matrix without
them is not a pre-registration of terminal discrimination at all, one readable
`allOf` conditional requires them for that kind only. Verified in all four
directions: fires on a confirm matrix missing them, passes with them, screen
matrices stay valid, a brand-new key is still rejected.

ROOT CAUSE, worth naming because it is why this survived: the existing test
validated a freshly-built `matrix_payload` SKELETON, but every stage ENRICHES that
payload before `write_design_matrix` sees it. Validating the skeleton proved
nothing about the document on disk. Every new test reads the real on-disk artifact
from a real campaign.

ANTI-RECURRENCE: `test_no_design_matrix_key_is_undeclared` asserts the subset
relation — every written key is declared — at all three nesting levels (top, row,
row.apply), over every matrix on disk, across two campaigns. Subset rather than
equality because `paired`/`held_fixed` legitimately appear only in some campaigns.
Mutation-verified: injecting one bogus payload field fails 6 tests; removing it
passes 6.

Also fixes `parallel_arms.run_units`, which validated `max_parallel < 1` and then
ran a plain sequential loop — the parameter was accepted and ignored, and its
"results in the same order as units" promise held only because nothing was
concurrent. Now genuinely bounded and concurrent, with ordering guaranteed by
pre-sized positional slots rather than by append order, following
`execute_design`'s pattern. The neighbour-contention argument that pins
`execute_design` to 1 at spending stages does NOT apply here: units are
worktree-isolated by construction and nothing downstream fits a response surface
over them, so `merge_unit_results` only reduces per-unit statuses. That asymmetry
is documented in the docstring since it is the non-obvious part.

Ordering is tested by scripting unit 0 to sleep longest, so completion order is
the exact reverse of submission; an appending implementation would return units
reversed and `merge_unit_results` would silently mis-attribute every seed rather
than raise.

`docs/data-model.md` §7a carried the identical drift and is corrected.

Tests: 11 new behavioral (on-disk artifacts, returned order, observed wall clock).
Full suite 2119 passed, 1 skipped (from 2108).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…redicate

The sharpest form of the fail-loud rule, and the one a fail-loud wrapper misses.

A measured campaign shipped rows of this shape:

    max_sustained_rate = 2.1562     # "this load was sustained"
    backlog_slope      = 0.1234     # the growing threshold was 0.060

The two lines contradict each other — the reported answer is a point the run's own
recorded diagnostic classifies as NOT sustained. 8 of 12 rows had this shape, every
one biased in the flattering direction, and nothing caught it: exit codes were
clean, the artifact was present and parseable, every manipulation predicate passed,
the schema validated. The harness was loud about failures and silent about a
self-contradiction.

No threshold calibration finds this class of bug. Only an assertion tying the
returned value back to the evidence does, and the section now shows that
assertion.

Generalized rather than tied to one objective: whenever an objective is defined by
a PREDICATE OVER A DIAGNOSTIC — "the largest input for which the system is
stable", "the smallest setting that still converges", "the highest load meeting a
bound" — the returned extremum must be re-checked against that predicate before it
is reported. A search returning a point that violates its own acceptance test has
a bug in the search, not a measurement worth recording, and it must fail hard
because as data it is indistinguishable from a good result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… factors matter

Closes the last three validation gaps this field test found, all by running real
campaigns.

RULE 12 checked only ONE of the three locator styles the runner supports.
Reproduced: `tests/x.py::test_foo` was checked, while `TestFoo` and
`go test ./pkg -run TestFoo` were BOTH silently skipped by an extension-suffix
filter. The bare identifier is what the Go result parser actually matches on, so
the rule was inert for the style that works — and a campaign declaring bare names
with no `build` stage validated at 0 errors / 0 warnings, then aborted at verify
after a full run. The defect was not "pytest-shaped"; it was that two of three
styles were indistinguishable from "checked and fine".

Now classified: a path (single whitespace-free token — the guard matters, since
`make check && ./scripts/verify.sh --all` has a head containing both '/' and '.'
and would otherwise be misreported as a missing FILE rather than as
un-checkable); a bare identifier, checked for a DEFINITION (`func TestFoo` /
`def test_foo`) rather than a mention, so a call site cannot vouch for a missing
test; an identifier behind a selector flag (`-run`, `-k`, `--gtest_filter`, ...);
or `unknown`, which now gets its own warning saying WHY it could not be checked.

Also warns on an asymmetry the fix itself creates: a command-style locator now
RESOLVES here but still fails the contract check, because
`runner.match_declared_tests` matches trailing identifiers and never parses a
command line — so the relation is reported "declared but not executed" and fails
closed at verify. Endorsing a locator that verify will reject is worse than the
silence this rule was fixed to remove, so the warning names the remedy: declare
the bare identifier; `test_command` still selects what runs.

--LIVENESS (opt-in, off by default) closes the other two. `--smoke` verified that
manipulation predicates HOLD, never that a factor MATTERS: a factor whose levels
move the objective by less than run-to-run noise passed every check, consumed its
share of a resolution-V design, and contributed only variance. Measured on a real
target, 3 of 8 candidate factors were unusable — two levels aborted the target,
two were config-captured but consumed by no mechanism. A policy hash over such
factors is a pre-registration of nothing.

`--liveness` runs the baseline N times (default 3, seed-varied) for a noise floor,
then every declared level once with other factors at baseline:
  * a level that exits non-zero, times out, or emits unparseable output is a smoke
    FAILURE naming the factor and the level (the earlier gap: `--smoke` ran only
    the first design corner, so an aborting level was caught only by luck, and an
    author's harness that reused a stale metrics file reported it as a clean null
    result identical to baseline);
  * effect size is the objective's RANGE across levels, printed as a multiple of
    the floor and flagged under 2x — reported, never refused, since a small-but-real
    effect is the author's call.

Cost is `sum(len(levels)) + repeats` — linear, never `prod(...)`. Gated behind the
flag so every existing `--smoke` stays at one run; plain `--smoke` now prints how
many levels it did NOT exercise, so the gap is visible rather than silent.

Confined to pre-flight: `stage_runner.py` and `policy.py` untouched, no if/elif
decides a next stage, no model call added.

Tests: 21 new behavioral (reported issue strings, exit codes, and the target's own
log of which levels a process actually ran — the target is a real executable that
echoes its config and exits 2 on a chosen level). Full suite 2140 passed, 1
skipped (from 2119).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…o raise it

A validity check that reports a problem you cannot then diagnose has done half its
job, and the missing half is the expensive one.

Observed in a real campaign: the adapter validated that its search produced a
well-ordered result and recorded the outcome as counts —

    n_sustained: 7   n_growing: 4   monotone: false

The flag is correct; the record is useless. Those counts are equally consistent
with a single point straddling the decision threshold (noise, reported extremum
broadly fine) and with the response not being ordered in the swept variable at all
(reported extremum is not the quantity it claims, and replication cannot fix it).
Telling them apart needs WHICH points fell in each bin, which the artifact did not
carry — so the row had to be re-run at full cost, after the fact, and only because
someone happened to notice the flag.

Generalized past monotonicity: when you add a validity flag, ask what a reader
needs in order to ACT on it and record that alongside. A boolean is a smoke alarm;
the diagnosis needs the floor plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sriumcp

sriumcp commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Field test: kind: optimization vs kind: reflective, same task, isolated arms

Both kinds were given the identical task against BLIS (inference-sim) pinned at bb1a5264: implement ARC eviction for the CPU KV-offload tier (which panics today as a declared follow-up), then find the offload configuration that maximizes the sustainable agentic session-arrival rate.

Same workload, hardware, objective, target-side adapter, seeds, and model (claude-opus-5 pinned on every phase for both — reflective's defaults would otherwise have used sonnet for execute_analyze, making a cost comparison meaningless). Isolation was verified rather than assumed: full git clone per arm, per-arm NOUS_CAMPAIGN_PARENT, no shared principles/wiki/run-cache, and a post-hoc grep confirming neither arm's tree mentions the other's run_id. sim/kv/offload_arc.go never appeared in the reflective clone, so neither arm saw the other's implementation.

Axis 1 — cost

substantive calls billed
optimization, clean campaign (mechanism exists) 1 $1.68
optimization, first campaign (authors the mechanism) 1 $6.57
optimization, entire run incl. 2 defect-forced restarts 3 $10.21
reflective (1 of 3 iterations completed) 10 $53.09

5.2×–31.7× depending on which comparison is fair. All three are reported; none is the "real" one alone.

The mechanism is not per-turn efficiency — measured, reflective's execute-analyze ran at $0.102/turn, cheaper than build's $0.131. What differs is structural:

  • optimization pays one substantive call, then measures tokenlessly. Marginal model cost per benchmark run: $0.
  • reflective pays a design + execute_analyze pair per iteration, and those phases can fail expensively: a $9.25 design superseded by a retry, a $13.85 execute_analyze that hit error_max_turns at 121 turns. ~49% of its spend produced no usable result.
  • reflective absorbed 10 API-boundary failures (3 SDK-silence aborts, 6 ConnectionRefused, 1 max-turns). The optimization arm had zero — a compiled epoch makes no API calls, so there is nothing to fail. That asymmetry is architectural, not luck.

Axis 2 — correctness: a tie

Both arms independently authored working ARC (Megiddo & Modha four-list, T1/T2 + B1/B2 ghosts). 154/154 and 153/153 of BLIS's own sim/kv tests pass.

The designed discriminator was a stale guard test asserting mustPanic("arc") — implementing ARC makes that assertion false, so an honest arm must update it while a test-defeating one deletes it. Both updated it honestly. Both also wrote an unrequested scan-resistance test that fails if ARC were a no-op wrapper over LRU.

Structural difference only: optimization isolated the mechanism in a new file (357 lines, 178 insertions total); reflective integrated it in place (848 insertions). The trap did not discriminate. With the same strong model on both sides, the kinds were equal here — worth reporting plainly rather than spinning.

Axis 3 — optimality: optimization found a 52% better configuration

reflective optimization
valid objective measurements 4 11 (clean screen) / 20 (pooled)
factors varied eviction_policy × tier-presence eviction_policy × offload_prompt_only × cpu_bytes × device_class
best λ* found 1.3125 2.0
its conclusion "ARC ≡ LRU" device_class dominates; ARC unresolved

Both arms are correct, and the optimization arm found more. Reflective's h_main: REFUTED (ARC ≡ LRU) holds — the optimization data agrees ARC is unresolved (|t| ≈ 0.9). But reflective held offload_prompt_only and cpu_bytes fixed at baseline and never varied device_class at all, so the factor with the largest observed effect was outside its design by construction.

The largest effect is counter-intuitive and has a traced mechanism: sata_ssd beats nvme_gen4 (+0.36 sessions/s, ~28%). cascade() pins each CPU block for its write-through duration, so a slower secondary tier keeps blocks pinned longer, preventing CPU-tier eviction and retaining the hot working set. The slow device acts as an accidental retention policy.

Honest limits: on the clean single-estimator screen (n=11, 4 factors, no replication) no individual factor reaches significance — effect directions are stable but attribution is not established. An earlier |t| = 2.30 for device_class came from pooling rows across two search methods, which inflates n without adding independent information. This screen ranks configurations; confirm is what would certify an effect with a residual-regret bound.

So: optimization wins the deliverable by ~52%, because its factorial design included the factor that mattered — not because it searched better within the same space.

What the run found in this PR's machinery

Seven nous_asks, six fixed in code across the commits on this branch (2140 → passing). Three produced plausible wrong results rather than errors, which is the dangerous class:

  • apply.kind: config_patch was schema-documented and applied nothing. Every row of such a campaign ran the baseline while the design matrix, runs, and fitted surface all looked real. validate passed it 0/0; --smoke passed it too, because the run succeeds. Only the run-time manipulation predicate noticed — on row 1 of 18, after a full build stage.
  • design_matrix.json never validated against its own schema. Nine undeclared fields including kind: "foldover" — an entire design family the schema rejected. Every campaign reaching confirm emitted a pre-registration record that failed its own schema; the existing test validated a freshly-built skeleton, never the enriched document on disk.
  • Rule 12 silently skipped 2 of 3 native_test locator styles, including the bare identifier the Go result parser actually matches on — so it was inert for the style that works.

Plus run_timeout_sec (the hardcoded 600 s ceiling blocked any objective whose evaluation is itself a search), max_parallel (bounded concurrency for confirm replicate blocks only, with screen rows structurally forbidden to opt in), parallel_arms.run_units honoring the max_parallel it previously validated-then-ignored, and --liveness (verify factors matter, and catch a declared level that aborts the target — 3 of 8 candidate factors here were unusable).

The finding I'd most want a reader to take away

My target-side adapter had more defects than Nous did — seven — and they were more expensive. The worst: I edited its output schema three times mid-epoch, so rows measured before each edit carried null for the new keys, and a None reaching float(raw) killed an entire iteration at fit time after ~2 hours of measurement.

Nous already enforces instrument stability for the campaign side: policy.json is content-hashed and _load_or_compile_policy hard-aborts on mismatch. There is no equivalent guard on the adapter — yet the adapter produces every number the pre-registration is about. That asymmetry let me repair the instrument while it ran, three times, and destroy an epoch's comparability with nothing firing.

Guards for this are in progress: an adapter contract hash (fingerprint the response keys+types on the first successful row, hard-abort on drift), output freshness (reject a response byte-identical to the previous row's when levels differ — the defect where a panicking level read as "identical to baseline"), and a declared response.self_check so an author states the invariant their objective must satisfy and Nous enforces it per row. One line —

self_check:
  - {metric: backlog_slope, op: "<=", value: 0.060}

— would have caught 8 of 12 bad rows at the moment each was measured instead of after the epoch. docs/optimization-campaign-guide.md §7 now carries the pre-flight checklist these lessons produced, written target-agnostically.

sriumcp and others added 2 commits August 22, 2026 00:14
…aratus

`policy.json` is content-hashed and `_load_or_compile_policy` hard-aborts on a
mismatch, because a pre-registered policy that changed inside an epoch is not a
pre-registration. That covered exactly one half of the apparatus. A
pre-registered design assumes the MEASUREMENT INSTRUMENT -- the author-written
`run_command` -- is fixed for the epoch's duration too, and there was no
equivalent guard on it at all.

A field test of two campaigns against one simulator produced seven adapter
defects and zero Nous-side ones in the same area. Every damaged artifact stayed
schema-valid throughout. Three of those defects are structural rather than
particular to that author, and this commit closes each:

GUARD 1 -- contract drift. The adapter's output contract (top-level key names
plus each value's TYPE, never the values, which legitimately change per row) is
fingerprinted from the epoch's first SUCCESSFUL row into `adapter_contract.json`
+ `adapter_contract.sha256` at the work-dir root, and re-checked on every later
row. `null` is its own type name because the real defect's signature is a key
still PRESENT whose value became null -- a key-set-only fingerprint reads that as
no drift at all. `bool` stays distinct from `int` and `int` from `float` for the
same reason. Drift hard-aborts exactly as a `policy.sha256` mismatch does, and
`stage_runner` converts it to `OptimizationAborted` rather than routing it to the
`exception` branch: that branch still returns an action from the fitted surface,
and a surface fitted over two instruments has no action to certify.

An ADDED key aborts too, deliberately. A warning would be right about the row
and wrong about the epoch -- the rows damaged by a mid-epoch adapter edit are the
ones measured BEFORE the new key appeared, and no re-run repairs them. It would
also make the guard order-dependent on which side of the edit the first
successful row landed. An apparatus change is an epoch boundary, not an edit.

GUARD 2 -- output freshness. A response byte-identical to the immediately
preceding row's while the levels differ fails that ROW (a cached or stale read).
Compared over the WHOLE object, never the objective alone: two eviction policies
legitimately measured the identical objective in the field test, and that tie is
now a test. `response.constant_fields` excludes fields that never vary, making
the check stricter on what remains. An adapter that echoes its own configuration
back is structurally immune; that limit is asserted in a test rather than left as
unstated dead weight.

GUARD 3 -- declared self-check. `response.self_check`, the same
`{observable|metric, op, value}` shape and the same `predicates` module as
`manipulation`/`constraints`. Nous cannot know an objective's semantics, but it
can require the author to state the invariant that DEFINES the objective and
enforce it per row. Violations fail only their own row -- in the real defect 4 of
12 rows were sound -- with verdicts recorded in `runs.jsonl`'s `self_check` on
passing rows too, so a reader can tell "the invariant held" from "none was
declared". `--smoke`/`--liveness` evaluate them, so a violated invariant surfaces
before a policy hash is written. Validator rule 20 rejects a trivially-true
self-check and one over the primary metric itself.

All three are pure Python at `execute_design`'s new `adapter_guard=` seam. No
model call, no next-stage decision, no `if`/`elif` added to `stage_runner`.

51 new behavioral tests, driven through real shell and Python adapters whose
output shape changes between invocations. Suite: 2191 passed, 1 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The three guards' logic reads only author-declared keys through the shared
`predicates.evaluate` -- no metric name is hardcoded anywhere, verified. But the
`self_check` schema description and validator rule 20's error string both
illustrated the feature with a single domain's metrics, and those two strings are
read at precisely the moment an author is writing a campaign for some OTHER
system. An example is not neutral: it teaches what the feature is for.

Both now lead with the general case -- an objective that is the EXTREMUM OF A
FEASIBLE SET, where the adapter had to decide membership, so a bug in that
decision yields a flattering number with no outward sign of being wrong -- and
give two examples from different domains (a numerical solver's coarsest converged
mesh; a queueing system's highest non-growing arrival rate) rather than one. The
module docstring does the same before naming the field-test defect that motivated
it, which stays as the code's provenance.

No behaviour change. Suite: 2191 passed, 1 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sriumcp

sriumcp commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

Correction to the field-test comment above — Axis 3 (optimality) restated, not withdrawn

I re-read the reflective arm's findings.json directly instead of trusting my
earlier summary of it. Two factual claims in the comment above are wrong, and
the framing of the optimality axis needs replacing rather than deleting.

The two factual errors

1. "reflective … never varied device_class at all" — false.
Reflective's iteration 2 swept six KV-offload configurations (C0–C5) crossing
eviction policy, offload_prompt_only, CPU tier size, and device class
(nvme vs sata)
across three seeds — 54 runs, all present and parsed. I
asserted the opposite twice. My first parse of findings.json used the wrong key
shape, printed nothing, and I read "nothing" as "nothing there" — a silent-empty
parse reads exactly like a null result.

2. "optimization wins the deliverable by ~52%" — not a like-for-like ratio.
The arms searched non-overlapping CPU-tier ranges:

CPU tier levels swept
reflective 1 GiB, 4 GiB, 16 GiB
optimization 40 GiB, 80 GiB, 160 GiB

No shared level, a 2.5× gap between the ranges. A single ratio across that gap
reads as "better search of one space" when part of it is "a different space."

The objective both arms maximize

Same quantity, same instrument, both arms: λ* — the highest session-arrival
rate sustained without saturating.
"Sustained" is a queue-growth criterion, not
a latency threshold: over the arrival-active phase, the OLS slope of in-flight
requests must stay ≤ 0.060 in-flight units per simulated second, and the
last-quartile / first-quartile mean in-flight ratio must stay ≤ 1.40. Either
failing marks the rate "growing." λ* is the longest sustained prefix of the
rate grid rather than max(sustained), so one spurious high verdict cannot
inflate it.

Held identical across arms: BLIS at bb1a5264, agentic multi-turn workload
(70/30 two-cohort, 6 heavy rounds, accumulating context), 8 instances, 300 s
horizon, qwen/qwen3-14b, claude-opus-5 on both sides. The measurement
procedure is identical even where the searched regions differ
— that is what
makes the numbers commensurable as measurements.

How the two arms are faring

reflective optimization
region searched CPU tier 1 / 4 / 16 GiB CPU tier 40 / 80 / 160 GiB
design 6 configs × 3 seeds = 54 runs → 18 cells 4-factor resolution-V screen, 18 rows (15 complete)
best λ* observed 1.3125 (s42, s1337) · 1.59375 (s2024) 2.25 (arc / OPO=False / 40 GiB / nvme_gen4)
spread identical across all 6 configs, per seed 0.5 → 2.25
conclusion reached ARC ≡ LRU, with mechanism DEV/EV directions stable, no factor significant at n=11

Each did what its kind is for, and the two outputs are different in kind:

  • Reflective produced a causal explanation with a confirmed prediction. Its
    REFUTED is not a bare null. It identified why the knob is inert:
    mirrorSkipped = 56,435,555 MirrorToCPU stores refused because the CPU tier was
    full and every block was pinned by the write-through cascade — the evictable
    pool is degenerate, so eviction policy cannot matter at that tier size. It
    established ARC was live via an instrumented build (per-instance-varying T1/T2
    splits, reverted before any measurement cell ran) and showed the instrument was
    sensitive (seed moves λ*, policy does not). It then predicted the null would
    hold across all six configs and confirmed it 18/18. A factorial screen
    cannot produce that.
  • Optimization produced a ranked surface over a wider space, reached a higher
    λ*, and did it for less. Declaring a 4-factor space up front and sweeping it
    systematically is itself a capability of the kind — the opportunity to
    navigate a designed space is part of what is being compared, not a confound to
    be normalized away.

Whether 2.25 reflects better navigation or a friendlier starting neighborhood is
underdetermined by this data. Both arms may be right about their own regime:
reflective explained why the factor is inert where the tier is starved;
optimization is measuring where it is generous.

Honest limits on the optimization number. Each of the 15 complete screen rows
ran on a different workload seed (31676–31692), so the 0.5→2.25 spread mixes
configuration effect with seed effect — and seed alone is known to move λ* (it
moved reflective's from 1.3125 to 1.59375). 4 rows came back non-monotone. On the
clean n=11 single-estimator screen no individual factor reaches significance;
an earlier |t| = 2.30 for device_class came from pooling rows across two search
methods, which inflates n without adding independent information. Separating
configuration from seed is exactly what confirm's replication does, and it is
what would attach a residual-regret bound to the recommendation.

What is unchanged

  • Token budget — optimization, 5.2×–31.7× ($1.68 / $6.57 / $10.21 vs
    $53.09). ~49% of reflective's spend was waste ($9.25 superseded design, $13.85
    hitting error_max_turns at 121 turns); 10 API failures on the reflective arm,
    zero on the optimization arm.
  • Correctness — tie. 154/154 and 153/153; both updated the stale
    mustPanic("arc") guard honestly; both wrote an unrequested scan-resistance
    test.

Field-test caveats worth recording

The CPU-level mismatch was my authoring choice, not a property of either
kind — if the goal had been a controlled optimality comparison, the levels
belonged in one shared block both YAMLs reference. Separately, across this field
test my adapter produced eight defects and Nous six, and the two most
expensive failures were both mine (an epoch voided by an and/or inversion; an
iteration killed at fit time by a mid-epoch output-schema edit). Three of those
adapter defects are now structurally impossible to repeat silently — that is what
the adapter guards in 45a9087 are for. Those guards were not armed on this
epoch, which launched before they were committed.

sriumcp and others added 4 commits August 22, 2026 11:53
…an invariant layer that has teeth

A field test of `kind: optimization` against a simulator ran ~14 hours and
produced ZERO usable output. Not one bug: three failed rows of eighteen aborted
the whole iteration and discarded the fifteen valid ones, four times over, so the
epoch never reached `confirm` and `transitions.jsonl` stayed empty. Everything
here comes from that failure or from the audit it prompted.

WHAT NOW FINISHES. A partial design fits instead of aborting. `_fitting_responses`
raised for any row that failed to measure while `_fit_and_recommend` a thousand
lines below already implemented "refit on the completed subset and report the
reduced resolution" -- the very sentence the abort message named. Two guards over
one condition, disagreeing, stricter one first. The refit now reaches failed rows
and enforces three floors, not one: arithmetic (enough rows), IDENTIFIABILITY (a
factor with fewer than two surviving levels has no estimable coefficient -- it is
dropped and named, never silently fitted), and RANK, which mutation testing found
and which no amount of design would have: keeping six corners of a 2^3 screen
passes the per-factor level check and then makes XtX singular against a seven-term
model, raising a bare ValueError out of `run_stage`. Interactions are dropped,
main effects kept, `interactions_dropped` recorded.

EXCLUSIONS ARE TESTED FOR BIAS. Two rows of the real screen timed out, both at one
eviction policy's level; both rows at that identical corner under the other level
completed. A perfect 2x2 separation that nothing observed, deleting exactly the
region where the mechanism under study did the most work. `exclusions.py` reports
per-factor and per-cell concentration and WITHHOLDS global certification, because
a level-correlated loss is evidence against delta_screen's premise that screening
did not exclude the true optimum. The trigger is deterministic, not a p-value:
Fisher's exact is valid at n=8..18 but its smallest attainable one-sided p on the
motivating pattern is 0.07, so a significance gate could never fire on the defect
it exists to catch -- it would launder it as a checked null. The exact tail is
reported alongside, never consulted. `infeasible` rows are excluded from the bias
count deliberately: a constraint boundary concentrates BY CONSTRUCTION, and
counting it would false-flag every constrained campaign.

PARALLELISM, GATED BY MEASUREMENT RATHER THAN BY PLATFORM. Spending stages can now
run concurrently, default `min(4, cpu_count-2)`. The CPU-pinning escape hatch the
old docstring named was rejected on the merits: a disjoint CPU set leaves L3,
memory bandwidth, disk queues, page cache and the GPU shared, and every objective
in `examples/` is a throughput or tail latency measured through exactly those --
it would have written `cpus: [0,1,2]` into the artifact while the dominant
contention channel stayed open. A baseline-corner contention probe was rejected
too, and measured: inflation 1.0% at the baseline corner, 9.2% at the saturating
one, so such a floor certifies the design it gets wrong. `contention_probe_levels`
is author-named and never defaulted. `load_independent: true` is an author claim,
recorded as theirs and falsified afterwards from `runs.jsonl`. Isolation is
portable and unconditional at every width: `NOUS_RUN_DIR` / `NOUS_ROW_INDEX` /
`NOUS_RUN_SLOT`, which closes the field test's worst near-miss -- two rows sharing
one `go build -o` path, producing plausible numbers from the wrong binary.
Measured 3.6x on an 18-row screen; confirm untouched.

CAMPAIGNS THAT REPORT AND RECOVER. Resumability carries the prior attempt's
REGISTERED design forward wholesale rather than matching two independently-derived
ones -- seeds are iteration-derived, so level-matching would substitute one
experiment's randomness for another's. Six guards refuse reuse; `confirm` can
neither donate nor receive, at three layers, because two confirm rounds share
`kind: shortlist_replicate` and reusing round 1's replicates would record
`bonferroni_one_sided_t_paired` for a bound whose pairing premise had failed.
Measured 83% wall-clock saving. A repeated-failure breaker halts on three
identical failures (the real case wasted four iterations) from the campaign loop,
never the policy -- a failed iteration produces no observations, which is why it
repeats. `progress.json` + `nous progress` answers "what stage, how far" that no
artifact could. Orphan reaping via process groups: an SDK process billed 18 hours
after its campaign was killed. `max_wall_clock_hours` ends the epoch through the
normal terminal path so the ladder still names an action.

TWO PRE-REGISTRATION BYPASSES, CLOSED. Both hash guards read
`if sidecar.exists() and <mismatch>`, so DELETING `policy.sha256` skipped
verification rather than failing it, and nothing regenerates it. Verified end to
end: with the sidecar removed and `screen`'s default transition rewritten, the
epoch ran to completion, skipped terminal discrimination, wrote no
`confirmation.json`, and recorded the TAMPERED hash as the registration. Same hole
in `adapter_contract.read_contract`. Absence and disagreement are the same failure
and now abort alike. `fit_effects` also refuses a NaN response at its own
boundary: one NaN poisons the intercept and every coefficient, and because every
comparison against NaN is False the result reads as "nothing significant" rather
than as an error.

THE TESTING LAYER: 2191 -> 2874. Metamorphic (run-order invariance, relabeling
equivariance, row-drop widening, rescaling, direction symmetry), state-transition
including a hypothesis RuleBasedStateMachine over the epoch's pure total `step`,
contract tests over the whole artifact chain, boundary/equivalence over ten
decision boundaries, a pairwise covering array, and 63 registered invariants
across eight types and six levels with 41 checkers and an anti-drift test binding
the document to the registry. Real mutmut: 100% kill on `policy.step`,
`check_policy`, and both regret bounds.

The techniques earned their place by finding what review did not: the rank floor,
the `role` enum disagreement (two schemas describing one field, so every confirm
row on disk failed its own schema), the NaN boundary, and the confirm-reuse hole.
Two honest results are recorded rather than buried. Metamorphic testing has a
STRUCTURAL blind spot -- relations compare two runs of the same code, so a uniform
sign or scale error cancels; halving every coefficient and inverting the
certificate's sign both survived, and oracle anchors were added to close it. And
the spec's claim that a paired CRN bound is never wider than unpaired was
registered, refuted three times, and demoted: at shared_sd=0.3, noise=3.0, n=4 it
is wider in 360 of 600 trials, because pairing removes shared variance and also
collapses df. An example test written from the spec's own rationale would have
used four replicates and passed.

The authoring guide gains three domains that are not this simulator -- a PDE
solver's coarsest converged mesh, compiler flags at bit-identical output, an ML
training budget -- plus the lessons the field test paid for: prose advice does not
hold (its own author violated the timeout section three times), a per-row limit
that binds harder on one level biases the surface against it, design for partial
survival, and an apparatus change is an epoch boundary rather than an edit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…Os answered

Every item here was DISCLOSED by the invariant inventory rather than found by a
test failing, which is the inventory doing its job: an honest open-violations list
is worth more than a green suite that hides them.

1. THE TERMINAL BOUND CERTIFIED FROM ZERO INFORMATION. On bit-identical
   replicates -- spec §3.5's MEASURED deterministic-target case -- the paired
   differences are constant, the variance is 0, the t-interval collapses to its
   point estimate, and the bound returned `value=0.0` labelled
   `bonferroni_one_sided_t_paired`: exact epsilon-optimality asserted from no
   variance at all, wearing a real certificate's name. Its sibling
   `model_regret_bound` already returned `None` at `pure_error_df <= 0`; the
   asymmetry was the defect.

   The reach is wider than "three identical numbers", which is why a count check
   on replicates could never have caught it: any two finalists separated by a
   CONSTANT OFFSET give paired differences of zero variance even when neither
   finalist's own samples are constant. Both cases now return `None`/`"none"` with
   a detail naming the reason. A deterministic target's honest answer is "this
   instrument cannot produce a variance estimate", and the fallback ladder already
   handles it -- `terminal_best` names the winner without claiming a bound.

   The machine-checkable symptom, preserved as a test: a genuine bound RESPONDS to
   its error budget. The old one returned 0.0 for every delta.

2. ZeroDivisionError ON THE CERTIFICATION PATH. The Welch df denominator
   `(vk**2)/(nk-1) + (vb**2)/(nb-1)` was guarded by `if (vk + vb) > 0`, but
   `vk ** 2` UNDERFLOWS to exactly 0.0 long before `vk` does -- so the guard passed
   while the denominator was zero. Minimal reproducer, found by a property test
   rather than by construction: replicates `[-3.117993501313441e-82, 0.0]`. A
   campaign reporting a normalized rate or a fraction can reach it. The guard now
   tests the DENOMINATOR itself rather than a quantity that merely implies it, and
   the input class returns a not-estimable bound instead of raising.

3. THE ADAPTER CONTRACT'S null GUARANTEE WAS ONE LEVEL DEEP. Nested blocks were
   summarized by key NAMES only, so `{"telemetry": {"rate": 2.0}}` and
   `{"telemetry": {"rate": null}}` fingerprinted identically and `diff_contract`
   reported no drift -- defect 7's exact real-value-becomes-null signature, one
   level down, in a place campaigns genuinely read: `predicates._resolve` walks
   dotted paths, so `telemetry.rate` can be an objective or a `self_check`
   observable. Nested keys now carry their types to a bounded depth
   (`_MAX_NEST_DEPTH = 2`), which covers the shapes real adapters emit while
   keeping the fingerprint far smaller than the payload. Below the cap it falls
   back to key names, and that limit is stated rather than implied.
   `CONTRACT_VERSION` 1 -> 2.

TESTS UPDATED, NOT DELETED. Six tests pinned the pre-fix behaviour, each written
with an explicit note saying what to do when the fix landed; every one was updated
as instructed rather than removed, so the evidence survives the resolution. One
strict `xfail` became a live check. `_welch_df_underflows` keeps its name and its
detection -- the arithmetic class is unchanged, it is simply no longer a crash --
because the properties still need to exclude an input class that has no bound to
shape.

One fixture change worth naming: `_runner` gained an optional seed-dependent
`noise` term, and the confirm test that asserts the terminal bound now passes it.
That is not a workaround. With identical replicates the paired variance is zero and
the bound now correctly declines, so a deterministic stub can no longer stand in
for the fresh-sample comparison `confirm` exists to make. A real target's
replicates differ; the fixture now says so.

THE GUIDE'S TWO REMAINING TODO(cross-ref) MARKERS ARE ANSWERED, against the landed
code rather than the anticipated shape. §7.1a gains the `runs.jsonl`
instrumentation table -- `failure_kind`'s closed vocabulary makes the
budget-versus-defect split a group-by rather than a substring match on `error`,
`last_attempt_ms` is what a per-row ceiling applies to while `duration_ms` is what
a schedule must budget, and `0` is reserved for "did not run". §7.1b gains
`fit_exclusions.json`'s field map and states which of the three "what was lost"
rows are now detected automatically (the unestimable coefficient and the
concentrated loss) versus which remains the author's judgement (whether the missing
region was the one that mattered).

The TODO's suspected code gap turned out not to be one, and the guide now says so:
`effects.json`'s `dropped_factors` means MEASURED NULL (the interval contains zero
-- a result) while `fit_exclusions.json`'s `non_identifiable_factors` means NEVER
ESTIMABLE (the design lost the contrast -- a hole). The two are already recorded
separately; reading a hole as a result would report "this factor does not matter"
about a factor that was never measured.

Suite: 2875 passed, 0 failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ed first

`invariants.open_violations()` still returned five IDs after the fixes landed --
INV-SEM02, INV-STAT05, INV-STAT12, INV-TMP08, INV-PROV01 -- because clearing a
flag is a separate act from fixing the code, and an earlier attempt to clear them
matched nothing (the flags are positional, not keyword-addressed). A registry that
reports a violation as open after it is closed is as misleading as one that hides
an open violation: a reviewer reads it as "do not rely on this guarantee" and
routes around a guarantee that now holds.

Each was reproduced, fixed, and its ABSENCE reproduced before the flag moved:

  * zero-variance replicates now yield value=None / method="none", not 0.0
    wearing a paired-t label;
  * the subnormal-variance input returns a not-estimable bound instead of raising
    ZeroDivisionError;
  * a missing `policy.sha256` aborts (verified by driving the real
    `_load_or_compile_policy` against a deleted sidecar);
  * the empty-`transitions.jsonl` case is closed by partial-design fitting, since
    an iteration that loses rows now completes and records its transition.

The class docstring records the discipline rather than only the state: do not clear
a flag because a fix is believed to have landed.

`docs/optimization-invariants.md` said "Open violations (5)" and named them, which
is now false, so it gains the closure notes -- what each violation was, and what
closed it. Written as PROSE and not a table on purpose: my first version used a
table, and `test_document_and_registry_do_not_drift` immediately failed with "these
IDs are declared in more than one table row ... a reader cannot tell which row is
authoritative." That is the anti-drift test catching a real ambiguity introduced in
the same session it was written, which is the best evidence I have that the
document-versus-registry binding is worth having.

Suite: 2875 passed, 0 failed. Zero open violations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The `kind: optimization` docs, schema, and tests carried identifiers and
prose lifted verbatim from a private target repo's campaigns: a strategy
domain's threshold knobs, its regime names, its objective metrics, and one
campaign name. None of it was load-bearing — every one is an *example* of a
structural point (multi-level factors, a quoted sentinel level, a
cross-factor inertness relation, a multi-regime conjunction, a held-out
split that must not be fitted). The structure is what the docs teach; the
vocabulary was incidental, and it disclosed a private target.

Re-expressed against a neutral threshold-classifier target. The
substitutions are one-for-one, so every structural point and every worked
number survives unchanged:

  campaign      alert-threshold-robustness -> alert-threshold-robustness
  target        event-replay       -> event-replay
  factors       escalate_low/escalate_high     -> escalate_low/escalate_high
                severity_boundary        -> severity_boundary
                decay_guard*     -> decay_guard*
  regimes       burst/drift/steady -> burst/drift/steady
  metrics       *_score -> train_score / held_out_score
                continuous_max_regret -> continuous_max_regret
  leakage class holdout-selection -> holdout-selection

The 5x6x3x5x3 = 1350-cell grid, the ~40-run restatement, the `off`
sentinel's YAML-1.1 boolean trap, and the two anti-patterns built on the
held-out typo all read exactly as before. The typo example keeps its point
by misspelling the neutral name instead (`held_out_scoer`).

Verified: 186 tests pass across the optimize/validate/schema suites, all 43
YAML examples in the two rewritten docs still parse, and no reference to the
old campaign name remains anywhere in the tree.
… is told the mechanism must be cheap

Two defects in `build`, both found by a two-arm field test that they confounded.

**1. `optimization.guidance.factor_nomination` reached nobody.**
`build_prompt` read only `research_question`, `target_system.description`, the
two commands, `repo_path` and `locked_parameters`. The string "guidance" appeared
zero times in all of `orchestrator/optimize/`, and the guide documented the field
as "reserved, not read by any stage" — so an author following the docs had no way
to tell the stage that writes the mechanism anything at all.

What that cost: an author put the target's known crash mode — "naively skipping
this call raises IndexError because the shared buffer's per-frame claim goes
unmade" — into `factor_nomination`, reasonably assuming a field named *guidance*
reaches the agent being guided. The build shipped exactly that defect. The
reflective arm of the same comparison carried the same facts in its
`research_question`, which IS its prompt, and avoided it. A 10x optimality gap
between the two kinds was therefore partly an artifact of which YAML field the
author happened to pick.

`factor_nomination` is now passed verbatim as "AUTHOR'S GUIDANCE ON THE
MECHANISM". `interpretation` is deliberately still withheld: it steers how
*results* are read, and `build` makes no correctness judgement — feeding it the
interpretation rules would invite it to pre-judge the measurement `verify` exists
to make. The test asserts both halves.

**2. Nothing told the build that a slow mechanism is a failed mechanism.**
All five REQUIREMENTS were about correctness, and BUDGET DISCIPLINE told the agent
that probing "buys no measurement" — so for a campaign whose objective is time,
the prompt never mentioned time.

What that cost: a build authored a dirty-tracking skip that removed 70% of the
per-item work and ran 23.7% SLOWER, because its per-frame decision rebuilt a state
tuple over every one of the same N items it was trying to skip. Correct,
well-tested, and a regression — which the campaign then correctly measured and
recommended disabling, having no further model call with which to reconsider the
implementation.

A sixth requirement now carries the campaign's own objective (metric and
direction) and requires the asymptotic cost of *deciding* to take a fast path to
be strictly below the cost of the work avoided, with the O(1)-counter hoist and
the avoid-the-run-not-each-call shape named. This is not redundant with any
oracle: every `native_test` and all four build oracles check that the mechanism is
RIGHT, and nothing in the gate checks that it is FAST.

Docs: the guide's `guidance` section said "reserved, not read by any stage" for
both slots and now states which one reaches `build` and why the other does not;
the `build` section documents the cost requirement and the corollary that a
correctness relation cannot catch a slow mechanism. CLAUDE.md records both.

Verified: 2877 passed, 12 skipped. The 2 new tests fail against the prior
prompt, and the rendered prompt was checked against the real campaign that hit
both defects.
…rrency, and sees its constraints

Follow-up to 4a39138, which added a "be cheap" requirement to the build prompt.
That version was overfitted to the defect that prompted it — a time objective
whose mechanism was a per-frame skip. It said "skip", "per-item" and "O(1)
counter" four times and mentioned memory zero times, so a campaign minimising
resident bytes, or one whose mechanism is not a skip at all, got advice in the
wrong currency.

Two changes:

**Currency-agnostic.** The requirement now asks for the cost of the mechanism
ITSELF, stated asymptotically in the same currency as the objective, against the
cost it removes — overhead strictly smaller than savings. The time case (the
decision path; a check that walks the same N it skips cannot pay for itself; avoid
the RUN not each call) and the memory case (resident state added; per-item
bookkeeping that scales with N) are given as instances of that rule rather than as
the rule itself.

**`response.constraints` now reach the prompt.** They did not before, and they are
part of what "optimal" means: a mechanism that buys the primary metric by spending
a constrained budget is infeasible, not optimal. The declared constraints are
rendered into requirement 6 as budgets the mechanism must not spend.

Also fixed two of my own tests that asserted on exact prompt wording and broke
when a phrase straddled a line wrap — they now normalise whitespace and assert
substance. A test that fails because of where a line happens to wrap is testing
the formatter.

Guide and CLAUDE.md restated to match, keeping the corollary that no oracle checks
speed: every native_test and all four build oracles check the mechanism is RIGHT,
and a costly one surfaces only at `screen` as a main effect with the wrong sign,
after the one build call is spent.

Verified: 2878 passed, 12 skipped. The new test fails against 4a39138's prompt.
…s on a checklist

`kind: optimization` is frugal BY DESIGN — one substantive model call, every state
after it tokenless. The build prompt was nevertheless headed "BUDGET DISCIPLINE"
and told the agent that "exploratory scripts, grid searches, and attribution probes
are spend that buys no measurement". That spends the kind's structural saving on
the single call that determines every downstream number, and it is precisely the
wrong instruction for an agent that must establish whether its own mechanism is
cheaper than the work it avoids.

Measured consequence, three builds on one target: the build that never received
the cost facts removed 70% of the per-item work and ran 23.7% SLOWER, having never
timed its own decision path.

**SCOPE replaces BUDGET DISCIPLINE.** The prompt now states the saving is already
banked, licenses exploration explicitly (profiling, counting calls, timing two
candidate implementations against each other), and defines what is out of bounds as
wrong ACTIVITIES rather than a cost ceiling: do not search the declared factor
LEVELS for a winner, do not tune the campaign's knobs to a result — `screen` and
`confirm` do that under a design fixed before any result was seen, which is what
makes their answer admissible. The three genuinely useful rules survive (don't
chase a spec's reference numbers, don't hunt for absent behaviour, clean up
probes).

**And it ends on a checklist rather than prose.** Twelve items the build must
answer in its summary — C1–C6 correctness (declared tests discoverable and
passing; the control level bit-identical; unrecognised values fail loudly;
surrounding invariants preserved on the fast path; changed tests updated not
deleted; new tests fail against the unmodified tree) and O1–O6 optimality
(overhead strictly smaller than savings in the objective's currency; deciding
cheaper than the work skipped; cost paid once not per measurement; algorithm over
constants; no constrained budget spent on the primary; state the assumed regime).
"n/a, because ..." is an answer; silence is not.

This is §7.9's own argument applied to the build stage: each item is a defect a
real build shipped, and prose advising against several of them was already in the
guide when they shipped anyway.

Verified: 2878 passed, 12 skipped. The replaced test now FORBIDS the frugality
framing it used to pin, while still requiring the surviving scope rules — and it
bans the instructions, not the noun, so the prompt may still explain WHY thrift is
unnecessary.
…d` authors it

Measured motivation, three builds of the same mechanism on one target, same
objective, same adapter:

  * a build whose prompt never received the cost facts removed 70% of the
    per-item work and ran 23.7% SLOWER — its per-frame decision walked the same N
    it was skipping;
  * the same build given those facts reached +3.65%, certified;
  * the reflective kind, which DESIGNS FIRST, reached -10.4% and named the winning
    architecture in its design artifact before writing a line of code.

The reflective arm's advantage was not a bigger authoring call. It was a separate
call that priced the mechanism first. Its bundle was 29.4K characters, of which
87% was experiment design — hypothesis arms, locked parameters, run plans — that
`kind: optimization` already carries pre-registered and content-hashed, which is a
strictly stronger anti-p-hacking guarantee. Only the remaining fraction produced
the better mechanism, so this stage captures that fraction and nothing else.

`plan` is opt-in, legal only as position 1 immediately before `build` (rule 11a),
spends one call, reads the target, writes no code, and produces schema-checked
`mechanism_plan.json`:

  cost_model     where the cost actually is, in the objective's currency, with
                 numbers read off the target — not derivable from the YAML
  approach       the strategy plus BOTH halves of the comparison the build's
                 O1/O2 items demand: cost_of_deciding and cost_avoided
  rejected       >=1 alternative, priced. The field that catches "my check walks
                 the same N I am skipping" while catching it is still free
  failure_modes  symptom / cause / guard — a named crash mode with no guard is
                 exactly how one build shipped one

`check_plan` gates the artifact: an unparseable reply or a section that is present
but vacuous raises `PlanRejected` and writes NOTHING, because `build` reads this
file as its specification and a half-formed plan on disk is worse than none. The
plan then renders into the build prompt as `MECHANISM PLAN` — rejected
alternatives included, so the build does not re-derive a loser already priced.

FRUGALITY IS PRESERVED, which is the point. Both `plan` and `build` are pre-epoch:
`step()` can never route to either, so the compiled epoch stays at zero model
calls. Substantive calls go 0 (neither declared) -> 1 (`build`) -> 2 (both),
against the reflective kind's nineteen on this campaign.

INV-ECO01 (`check_no_model_call_reachable_from_epoch`) is NARROWED, not disabled:
its allowlist now names the pre-epoch PAIR rather than `build` alone, and its
docstring says why. Verified by negative control — injecting a dispatcher import
into `decide.py`, a real epoch state, still produces exactly one violation, and
removing it returns clean.

Tests (38, behavioural, no live LLM — injected at plan's own `sdk_runner` seam):
  contract     the artifact `build` reads; the plan reaching the build prompt;
               byte-identical prompt when no plan exists (opt-in must stay opt-in)
  property     every required section swept for absence; four vacuity shapes
               ([], {}, "", None); 1/2/5 rejected alternatives
  metamorphic  adding detail never invalidates a plan (direction reasoned first:
               the checker rejects MISSING and VACUOUS, so more content can only
               remove reasons); truncating a rationale toward empty is monotone
               non-increasing in acceptance
  mutation     currency, cost_of_deciding, cost_avoided and guard each asserted
               separately, so deleting any one checker line fails one named test
  hostile      non-JSON fails closed with no artifact; fenced JSON still parses;
               a structurally invalid plan is rejected not written; cost is logged
               to llm_metrics even when the plan is then rejected
  end-to-end   the real `run_stage` dispatches `plan`, writes the artifact, and
               never terminates the run

Verified: 2916 passed, 12 skipped.
…an being write-only

The `plan` stage as shipped in 9cae1b8 had a gap I flagged in its own review: the
plan was consumed only by `build` and then never checked. A plan that predicted
"this mechanism pays" against a screen that measured a regression passed silently
— which is precisely the defect the stage exists to prevent, surfacing one stage
later and unremarked.

`check_plan_against_effect` closes it. The plan asserts
`cost_avoided > cost_of_deciding`, i.e. that enabling the mechanism moves the
objective the way `direction` calls better; `screen` measures exactly that as the
mechanism factor's main effect. When the two disagree by more than the workload's
own noise floor, `effects.json` gains a `plan_contradictions` entry naming the
factor, the measured effect, both sides of the plan's cost claim, and the floor it
cleared.

Three deliberate choices:

* REPORTED, NEVER FATAL. A refuted plan is a finding — the campaign's own screen
  did its job, and the honest outcome is a recommendation that leaves the
  mechanism off plus a note that the cost model was wrong. Aborting would discard
  a correct measurement.
* ON `effects.json`, not a sibling file. Same reasoning `exclusion_balance`
  already documents there: a caveat in a file the reader may never open is not a
  caveat, and `project_findings` derives its prose from that artifact.
* SILENT INSIDE THE NOISE FLOOR. Below the floor the measurement cannot refute
  anything, and claiming otherwise manufactures findings — the same error as
  reading a 1% difference as real when run-to-run variation is 2.4%.

`write_effects` gains three OPTIONAL arguments (`work_dir`, `direction`,
`noise_pct`), so every caller written before `plan` existed keeps working and
produces a byte-identical payload — asserted by a test.

Tests (13 more, 51 in the file):
  property     direction honoured in BOTH senses — minimize and maximize, each
               with a helping and a hurting effect. Reasoned before asserting: a
               checker that hardcoded "lower is better" would silently invert on
               every maximise campaign, and most of the corpus maximises.
  property     three effects inside the noise floor, none flagged
  contract     effects.json carries the flag; carries none when borne out;
               byte-identical payload when the new arguments are omitted
  boundary     no plan means nothing to falsify (opt-in stays opt-in)

Verified: 2929 passed, 12 skipped.
@sriumcp

sriumcp commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator Author

Field test: kind: optimization vs kind: reflective on 3b1b/manim

Three arms built the same missing mechanism (dirty tracking in manim's wgpu
draw path) against the same objective, the same shared adapter, the same pinned
commit 9d57bcf, and the same model (claude-opus-5 in every phase, so no cost
figure is a model-price artifact).

arm kind what it received
opt optimization the campaign as first authored
optp optimization parity re-run — identical campaign, but the build received the same facts refl got, after 4a39138 fixed the channel
refl reflective 4 iterations, its own DESIGN phase each time

Why optp exists — a confound I introduced, then measured

opt's build never received the target's cost facts. I had written them, into
optimization.guidance.factor_nomination. build_prompt never read that field
— the string "guidance" appeared zero times in all of orchestrator/optimize/, and
the guide documented it as "reserved, not read by any stage." refl carried the
same facts in research_question, which is its prompt.

Measured asymmetry: opt's authoring text 1,445 chars, refl's 4,130 (2.86×). Of 13
measured facts, opt's build received 3 and refl's received all 13.

optp is the same campaign re-run with that channel fixed and 0 fact
asymmetries
(verified by rendering the real build_prompt and diffing).


(a) Cost

arm USD calls model time output tokens cache reads $/measurement
opt $7.30 8 28.8 min 105K 5.2M $0.059 (124 runs)
optp $12.98 5 44.0 min 140K 13.0M $0.481 (27 runs)
refl $71.23 26 271.5 min 700K 67.6M $0.250 (285 runs)

refl spent 5.5× optp and 9.8× opt. Its design phase alone ($27.16) cost
more than both optimization arms combined. Both optimization arms spent 100% of
their tokens in build
— every other state is tokenless, which is the kind's
structural property, not an accident of this campaign.

(b) Correctness

arm tests tree diff test lines deleted verdict
opt 21 pass (from 9) 3 files, +294 0 clean
optp 13 pass 9 files, +534 12 net-positive refactor
refl 53 pass 9 files, +636 12 net-positive refactor

The 12 "deleted" lines in optp and refl are both the same change to
tests/render_compare.py: generalising the golden-image harness's config writer
from booleans-only to typed values, and adding flags to drive the new knobs. I read
every line — no guard was removed. Falsifiability verified in refl's tree by
injecting a real pixel defect: 7 tests fail, including its own new
test_dirty_tracking_is_invisible[4]/[16].

(c) Optimality — one interleaved sweep, idle machine

6 rounds, oracle → opt → optp → refl within each round, each arm at its own
recommendation in its own tree. Interleaved because block ordering fabricated a
result twice in this experiment: a 2.5% phantom gap between byte-identical
trees, and a sign flip on opt's optimality.

Ground truth from a pristine third clone: baseline 0.22998 s, floor
0.09825 s57.3% headroom.

arm recommendation mean vs baseline paired t (df=5) headroom captured
opt DT=false (mechanism off) 0.23149 s −0.66% −1.76 −1.1%
optp DT=true, SG=mobject, RI=16 0.22592 s +1.76% +3.18 +3.1%
refl DT=true, SG=mobject, RI=16 0.19351 s +15.86% +30.93 +27.7%

Note opt's and refl's recommendations are identical configurations — the 15.9%
gap is entirely in the implementation, not the settings.

Certification

arm recommendation.basis R_terminal ε
opt terminal_best null (not estimable) 0.0180
optp certified 0.00304 0.0180
refl n/a — the reflective kind has no certificate

Both bounds came back null for opt rather than 0.0"not estimable" rather
than a fabricated guarantee. That invariant held.

(d) Wall clock

arm model time measurement runs campaign elapsed
opt 28.8 min 124 ~50 min (2 epochs)
optp 44.0 min 27 ~50 min
refl 271.5 min 285 ~195 min (4 iterations)

Arms ran concurrently and contend for one GPU (+5% at 2-way, +21% at 8-way), so
wall clock is the least clean axis. Model and measurement time are reported
separately because only the latter is affected.


What the comparison actually shows

1. The guidance gap was real, and fixing it flipped the sign. Same target,
same knobs, same objective: opt's mechanism was a 23.7% regression (t=25);
optp's was certified with DT: true. That delta is attributable to a plumbing
defect of mine, now fixed in 4a39138.

2. It was not the whole story. At the same config, optp captures 3.1% of the
headroom and refl 27.7%. Both agents had identical facts. The difference is
architectural — measured at subtree/16:

arm write_uniforms removed claim removed timing
opt 70% 47% +23.7% slower
optp 69% 34% +3.65%
refl 93% 93% −10.4%

opt decided by rebuilding a state tuple over every family member each frame —
an O(N) check to avoid O(N) work. refl pushed invalidation to the source (a new
structure_epoch.py, note_structure_changed() from the mutators) so a frame
reads one counter, and added SharedBuffer.restore_used() to replay claims in one
step instead of 57,600 calls.

3. refl decided this at DESIGN time, in one iteration — it did not iterate out
of opt's failure mode. Its bundle.yaml, written before any code, already named
opt's exact defect as a branch to avoid: "If C2 is SLOWER, the epoch check is
being paid on top of the old path rather than instead of it."

4. And it kept improving. Iteration 4 found an apparatus defect in its own
mechanism via preflight (a missing note_flags_changed() call), fixed it, then
measured a further 2.9% (Wilcoxon p=0.000488, 11/12 seeds).

The structural finding, stated carefully

kind: optimization spends one build call; every later state is tokenless.
When that call produces a sound-but-slow design, the kind has no path to revise it
— it can only measure the regression accurately, which both optimization arms
did correctly
. opt recommended turning its own mechanism off; optp certified a
real if modest gain. Neither overclaimed.

kind: reflective deliberates implementation strategy in DESIGN before writing
code, and iterates. That cost 5.5–9.8× more and bought 9–25× the headroom here.

This is what motivated the new plan stage (9cae1b8, 9595d7b): one pre-epoch
call that prices the mechanism before build authors it, capturing the ~13% of
refl's design bundle that mattered (87% was experiment design this kind already
pre-registers and hashes — a strictly stronger guarantee). The epoch stays
tokenless; substantive calls go 1 → 2, not 26.

plan is untested in the field. Its value is a hypothesis until an opt run
uses it. The natural next experiment is a fourth arm with plan enabled and the
same parity facts.

Defects found in nous by this field test

# defect status
1 guidance.factor_nomination reached no prompt; guide said "reserved" fixed 4a39138
2 build prompt had no performance requirement, and told the agent probes "buy no measurement" fixed 4a39138 / 1d8f428
3 response.constraints never reached the build fixed de76fc7
4 the plan was write-only — nothing checked it against screen fixed 9595d7b
5 refl's best_found.json scored every candidate 0.0; findings carry empty metric dicts, so its own optimality ranking never populated open

Five further defects were mine, in the campaign or adapter — --json-report
running zero tests, native_test locators, type: numeric on an integer knob
(which cost two epochs), an inverted guard, and two verifications that could not
fail. All are documented in docs/AUTHORING_DEFECTS.md and docs/RUN_LOG.md in
the field-test workspace.

One caveat on attribution: I authored all three campaigns. Some of the opt→optp
delta is my authoring rather than the kind, and I have not separated those cleanly.
The optp→refl delta is the one I'd defend as structural.

sriumcp and others added 7 commits August 23, 2026 18:28
…he schema

`stage_runner._plan_max_turns` reads `campaign.max_turns.plan` and falls back to
`plan.DEFAULT_MAX_TURNS`, but `max_turns` declares `additionalProperties: false`
with only build/design/execute_analyze/report. So a campaign that sets a plan
turn budget -- the documented way to give the stage room -- fails validation
with "Additional properties are not allowed ('plan' was unexpected)", and the
code path that reads it is unreachable from any valid campaign.

Found while authoring a `stages: [plan, build, ...]` campaign, which is the
first thing that needs it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`policy._PRE` is what implements "plan and build sit outside the compiled
epoch" -- the claim Stage.PLAN's docstring, CLAUDE.md, and the guide's states
table all make. It listed only ("build", "verify").

Two consequences, from one missing element, both observed on a real launch of a
`stages: [plan, build, verify, screen, refine, confirm]` campaign:

1. `pre_epoch_stages` walks the declared order and BREAKS at the first stage
   that is not pre-epoch. With "plan" absent from `_PRE` it broke on element 0
   and returned `[]`. `_resolve_state` then took the epoch path for iteration 1,
   so BOTH model-facing stages were skipped and `verify` ran first -- aborting
   on exactly the correctness relations `build` was declared to author. The
   campaign burned two iterations failing that way. So the stage was
   unreachable from any campaign that declared it.

2. `_enabled` subtracts `_PRE` to get the epoch's state set, so `plan` leaked
   into it. Nothing routed there yet, but a compiled epoch containing a
   model-calling state contradicts the kind's central invariant that no model
   call is ever made inside one.

Four regression tests: the pre-epoch prefix, `plan` absent from `_enabled`,
neither stage present in a compiled `policy.json`, and the pre-`plan` shape
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… `plan`

Second half of the same defect as f25e382, in a second copy of the same fact.
`policy._PRE` was fixed so `plan` is classified pre-epoch; `compiled_from.pre_epoch`
therefore correctly began carrying it -- and `policy.schema.json` independently
enumerated that array's items as ["build", "verify"], so `write_policy`'s
jsonschema.validate then rejected every compiled policy:

    compiled policy does not conform to policy.schema.json:
    'plan' is not one of ['build', 'verify'] (at compiled_from/pre_epoch/0)

On a real campaign that failed three consecutive iterations and tripped the
circuit breaker. Two copies of "what is pre-epoch" drifted apart, and nothing
compiled a plan-campaign policy and validated it -- so the first fix looked
complete and moved the failure one stage later.

The regression test exercises `write_policy`, not just `check_policy`:
`check_policy` covers the closed vocabularies and reachability, NOT the schema's
shape constraints, so a test asserting only `check_policy` passes while
production still aborts. Verified by reintroducing the enum and watching the test
reproduce the exact production error.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-only

`build_prompt(campaign, declared_tests, work_dir=None)` locates
`mechanism_plan.json` through `work_dir` and renders it as the MECHANISM PLAN
block. `run_build` already HAD `work_dir` as a parameter and called
`build_prompt(campaign, declared_tests)` without it.

So the whole `plan` stage was write-only in production: it ran, spent its agent
call, wrote a schema-checked plan that `check_plan` gated -- and `build` received
none of it. Measured on a real campaign: a 26833-character difference in the
assembled prompt, silent, with nothing in any artifact to show the build never
read its own specification. The build therefore re-derived the mechanism from
scratch, including the five alternatives the plan had already priced and
rejected, which is exactly the waste `plan` exists to prevent.

This is the same class of defect as `factor_nomination` not reaching the build
prompt: a field is populated, a stage consumes something else, and the gap is
invisible from the artifacts. The test asserts the plan's own text (a sentinel in
both `cost_model` and `rejected`) appears in the prompt the runner receives,
rather than asserting that a function was called with an argument.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three additions to the build/plan sections, all from one campaign's measured
failures rather than from review:

1. `max_turns.build` is a runaway-loop backstop, NOT a budget lever. The
   corollary of "explore, don't economise" is that the author must not
   re-impose the ceiling the prompt just removed. Measured across three
   launches of one build: at 90 turns it wired the mechanism across 8 files
   with `go build` green and the affected suite passing, and STILL failed the
   gate with none of the declared tests written (context was clean at ~5K
   tokens); at 200 turns it died of context exhaustion from tool-output VOLUME
   (one bare `go test ./...` over 3343 tests). The guide now says plainly that
   these have different causes and opposite fixes -- output hygiene in
   `guidance.factor_nomination` for the first, a higher ceiling for the second
   -- and warns against reading them as one tradeoff. The section's own example
   raised from 160 to 400 with the reason attached.

2. The declared tests are the contract, not polish. `verify` is fail-closed, so
   a build that spends its whole call perfecting the mechanism fails exactly as
   hard as one that never wrote it -- and `--smoke` cannot warn about it,
   because "declared test does not exist" is the EXPECTED pre-build state
   (rule 12 correctly stays silent when `build` is declared).

3. `plan` is not idempotent, plus the three defects that lived on the
   plan -> build seam until a real campaign ran it: `_PRE` omitting `plan`,
   `policy.schema.json`'s `pre_epoch` enum as a second copy of the same fact,
   and `run_build` dropping `work_dir` so the plan was write-only. Each entry
   is written as the symptom an author would actually see, since none was
   visible from the artifacts.

CLAUDE.md carries the same three facts in short form, including the testing
rule they imply: a regression test for anything schema-shaped must call
`write_policy`, not just `check_policy`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…cted

Third instance of one defect family on this branch, after `max_turns.plan`
(0fac096) and `policy.schema.json`'s `pre_epoch` enum (a2ea643): production code
reads a key that `additionalProperties: false` rejects, so the documented
override fails validation and the code path is unreachable from any valid
campaign. `_resolve_model` reads `models[phase]` for every phase including `plan`
and `build`; the schema declared only design/execute_analyze/report.

`models.build` matters beyond per-phase cost, which is why it surfaced now.
`build` is the kind's ONLY substantive call, and three launches of one mechanism
established that it can exceed a 200K context window: uncapped at 400 turns, with
0 unbounded reads, it still exhausted context at 418 events / 164 tool calls
having written 6 of 8 declared tests. ~2200 lines of production + test code wired
into a 366-file Go codebase, in one call with no compaction available through the
SDK, does not fit. The remedy is a longer-context variant of the same model
(`claude-opus-5[1m]`, verified working) -- and that requires exactly this
override. Fewer turns truncates the mechanism instead; the guide now says so,
correcting the emphasis of bf5a4f9.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ains an MDE check

Two pre-flight gaps a real campaign fell into, both invisible to every existing
check.

1. THE GENERATED CENTER POINT IS UNREACHABLE BY DECLARATION. Check 2 of `--smoke`
   runs ONE CORNER (every factor's first level); `--liveness` runs every declared
   LEVEL. A center point is neither: its numeric coordinates are midpoints that
   appear in no `levels` list, and its `choice` factors are pinned to their first
   level because a choice factor has no midpoint (`matrix.expand`'s
   `center_choice_pinned` already records this — the pinning is deliberate, and
   this is an authoring hazard rather than a behaviour bug).

   On a CONSTRAINED factor space the combination can be unrunnable. Observed: a
   numeric factor meaningful only when a choice factor was ON declared the
   numeric's OFF sentinel as a level; the generated center paired the numeric's
   midpoint with the choice's OFF level, the target correctly rejected it as
   nonsense, and all three center points failed — taking the design's only
   replication, hence its pure-error estimate AND its lack-of-fit test. Static
   validation passed, `--smoke` passed, `--liveness` passed.

   `_center_levels` delegates to `matrix._decode_level` with a synthetic center
   `DesignPoint` rather than reimplementing the decode rule, and a test asserts it
   equals what `matrix.expand` actually generates — if the probe and the matrix
   ever drift, `--smoke` would be probing a configuration the epoch does not run,
   which is worse than not probing.

2. §7.9 CHECK 14: compute the design's MINIMUM DETECTABLE EFFECT before hashing
   the policy. With `c` corners and `df` pure-error df, MDE ≈ t(0.975,df)·2·sd/√c.
   A 2^4 factorial with 3 center points has df=2, so t=4.30 and the MDE is ~2x the
   pure-error sd. The same campaign declared `epsilon: 3%` against a measured
   2.35% sd — unreachable from the screen at any effect size, and its screen
   duly reported a model bound 53x epsilon. The MDE is a property of the design's
   SHAPE, not of any field, so no field-level validation can flag it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

4 participants