Let sweep axes override bound params (bound value = default) #246

Closed
opened 2026-07-12 22:35:09 +02:00 by Brummel · 1 comment
Owner

Design record (in-context entry)

This issue seeds the removal of the closed/open starter-blueprint pair by changing the sweep-axis semantics. The design was settled in an in-context discussion on 2026-07-12; this records it.

Context. aura new ships two starter blueprints (#241): signal.json (closed — the aura run target) and signal_open.json (fast.length left unbound — the aura sweep target). The pair exists because an axis can today bind only an OPEN param: PrimitiveBuilder::bind (crates/aura-core/src/node.rs) removes the param from the blueprint's param_space(), so a closed blueprint is refused with "this blueprint is fully bound; nothing to sweep" (blueprint_sweep_family, crates/aura-cli/src/main.rs). The pair was flagged as inelegant when it shipped (see the reservation recorded on #241).

Fork: how a bound param relates to a sweep axis → a bound value is the param's DEFAULT; a sweep axis that names a bound param re-opens it and binds it per cell. The second starter blueprint disappears (signal_open.json leaves the aura new scaffold).

Basis: user decision, 2026-07-12 — the blueprint keeps one meaning (values are values, owned by the strategy author); what the experiment varies is the campaign's statement, which is where experiment intent lives (contract C25). Chosen over (b) a default field in the blueprint format — it adds a third param state (bound / open-with-default / open-bare) and muddies the closed/open distinction instead of dissolving it — and (c) keeping the explicit closed/open file pair, which carries real research friction: sweeping a bound param requires hand-editing JSON.

Consequences (design intent):

  • run / mc semantics unchanged: a truly OPEN param (no value) still refuses ("run requires a closed blueprint"). "Open" now means must be bound; "bound" means default, overridable by a sweep axis.
  • --list-axes lists both classes: open knobs first, bound params marked with their default value.
  • The "fully bound; nothing to sweep" refusal retires: a closed blueprint with axes naming bound params sweeps; an axis naming neither an open nor a bound param gets a clear message pointing at --list-axes.
  • Reproducibility untouched: the blueprint file is never rewritten, its content id is stable, and every member manifest already records its per-cell bindings. reproduce re-derives families from those manifests and must honour overridden bound params.
  • Params still never change topology (contract C11 untouched); the design ledger's C12 gains the bound-as-default amendment. Verified: C12 documents the four orchestration axes but never states "axes bind only free knobs" — that restriction was an implementation consequence of bind() shrinking the param surface, not a recorded decision.

Expected mechanism (claim, to be verified in implementation): bind() stores the value as data (BoundParam already carries pos/name/kind/value) and merges it at build time instead of capturing it in a wrapped closure, making an unbind reversal possible; Composite gains a path-addressed re-open; the CLI derives the re-open set from the invocation's axes at the family boundary and threads it through the probe, the per-member reloads, and reproduce.

## Design record (in-context entry) This issue seeds the removal of the closed/open starter-blueprint pair by changing the sweep-axis semantics. The design was settled in an in-context discussion on 2026-07-12; this records it. **Context.** `aura new` ships two starter blueprints (#241): `signal.json` (closed — the `aura run` target) and `signal_open.json` (`fast.length` left unbound — the `aura sweep` target). The pair exists because an axis can today bind only an OPEN param: `PrimitiveBuilder::bind` (crates/aura-core/src/node.rs) removes the param from the blueprint's `param_space()`, so a closed blueprint is refused with "this blueprint is fully bound; nothing to sweep" (`blueprint_sweep_family`, crates/aura-cli/src/main.rs). The pair was flagged as inelegant when it shipped (see the reservation recorded on #241). **Fork: how a bound param relates to a sweep axis** → a bound value is the param's DEFAULT; a sweep axis that names a bound param re-opens it and binds it per cell. The second starter blueprint disappears (`signal_open.json` leaves the `aura new` scaffold). Basis: user decision, 2026-07-12 — the blueprint keeps one meaning (values are values, owned by the strategy author); what the experiment varies is the campaign's statement, which is where experiment intent lives (contract C25). Chosen over (b) a `default` field in the blueprint format — it adds a third param state (bound / open-with-default / open-bare) and muddies the closed/open distinction instead of dissolving it — and (c) keeping the explicit closed/open file pair, which carries real research friction: sweeping a bound param requires hand-editing JSON. **Consequences (design intent):** - `run` / `mc` semantics unchanged: a truly OPEN param (no value) still refuses ("run requires a closed blueprint"). "Open" now means *must be bound*; "bound" means *default, overridable by a sweep axis*. - `--list-axes` lists both classes: open knobs first, bound params marked with their default value. - The "fully bound; nothing to sweep" refusal retires: a closed blueprint with axes naming bound params sweeps; an axis naming neither an open nor a bound param gets a clear message pointing at `--list-axes`. - Reproducibility untouched: the blueprint file is never rewritten, its content id is stable, and every member manifest already records its per-cell bindings. `reproduce` re-derives families from those manifests and must honour overridden bound params. - Params still never change topology (contract C11 untouched); the design ledger's C12 gains the bound-as-default amendment. Verified: C12 documents the four orchestration axes but never states "axes bind only free knobs" — that restriction was an implementation consequence of `bind()` shrinking the param surface, not a recorded decision. **Expected mechanism (claim, to be verified in implementation):** `bind()` stores the value as data (`BoundParam` already carries pos/name/kind/value) and merges it at build time instead of capturing it in a wrapped closure, making an `unbind` reversal possible; `Composite` gains a path-addressed re-open; the CLI derives the re-open set from the invocation's axes at the family boundary and threads it through the probe, the per-member reloads, and `reproduce`.
Brummel added the feature label 2026-07-12 22:35:09 +02:00
Author
Owner

Derived decisions (implementation forks)

Three forks derived while planning; recorded for audit. All three are orchestrator decisions, not user decisions.

  1. Probe seamblueprint_axis_probe keeps its exact behaviour (mc/run closed-checks and the open --list-axes lines read it unchanged) and becomes a delegation to a new variant taking a re-open set; sweep / walk-forward / campaign / reproduce call the variant. Derived: one wrap recipe stays single-sourced, and the closed-blueprint requirements of run/mc are untouched by construction rather than by convention.

  2. Override surface = the strategy subtree only — the bound-param space consulted for overrides (and printed by --list-axes) is enumerated on the UN-wrapped strategy composite and prefixed with its name, never on the wrapped harness probe. Derived: the wrap scaffolding (broker/stop/executor nodes) is fully bound by design; exposing it would make harness internals sweepable and drown --list-axes in non-strategy noise, contradicting the risk-regime-as-structural-axis decision recorded on #216.

  3. Error routing — every axis rejection derivable at the family boundary is rejected there with one message (axis names neither an open nor a bound param → points at --list-axes, exit 2, the same class as the retired "fully bound; nothing to sweep" refusal). The compile-time param-namespace injectivity check stays as the safety net for engine-direct callers; via the boundary path a mid-family collision is unreachable (an axis matching an open path passes through without re-opening anything).

## Derived decisions (implementation forks) Three forks derived while planning; recorded for audit. All three are orchestrator decisions, not user decisions. 1. **Probe seam** — `blueprint_axis_probe` keeps its exact behaviour (mc/run closed-checks and the open `--list-axes` lines read it unchanged) and becomes a delegation to a new variant taking a re-open set; sweep / walk-forward / campaign / reproduce call the variant. Derived: one wrap recipe stays single-sourced, and the closed-blueprint requirements of `run`/`mc` are untouched by construction rather than by convention. 2. **Override surface = the strategy subtree only** — the bound-param space consulted for overrides (and printed by `--list-axes`) is enumerated on the UN-wrapped strategy composite and prefixed with its name, never on the wrapped harness probe. Derived: the wrap scaffolding (broker/stop/executor nodes) is fully bound by design; exposing it would make harness internals sweepable and drown `--list-axes` in non-strategy noise, contradicting the risk-regime-as-structural-axis decision recorded on #216. 3. **Error routing** — every axis rejection derivable at the family boundary is rejected there with one message (axis names neither an open nor a bound param → points at `--list-axes`, exit 2, the same class as the retired "fully bound; nothing to sweep" refusal). The compile-time param-namespace injectivity check stays as the safety net for engine-direct callers; via the boundary path a mid-family collision is unreachable (an axis matching an open path passes through without re-opening anything).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#246