Skip to content

:>> ABIO → ABIO Architecture → ABIO-Experiment

ABIO-Experiment

The hub of the instrument, in six modules since T058: the spec, the drafter and agent registries, the guards, the record store and the report each have their own, and experiment.py keeps run_experiment + aggregate and re-exports the rest.

ABIO-Experiment.svg

A YAML file is one !experiment call whose task:, brief: and episode: are quoted calls; load_spec marks files under catalog/ trusted and hands off to expr_experiment.load_experiment, whose _split_call sorts each keyword into a swept axis (a free name) or a fixed dial, so a dial's slot decides whether it is world-side or brief-side. spec_from_dict validates the result and expands idle_baseline into an agent axis. run_experiment clears the guards in a fixed order, builds a drafter closure that merges fixed_dials under the per-trial dials and dispatches through DRAFTERS, and hands the grid to MassTrialRunner; on_trial prices real usage, stamps agent/model/memory/temperature/registration and appends one canonical-JSON line to records.jsonl. manifest.json is written before the grid and rewritten at the end. aggregate rebuilds the ReliabilityMap from those two files alone, and render_report emits its sections in one fixed order that plots.PLOTTERS mirrors.

The pre-flight, in order

# Guard Refuses
1 registration_admission a registration: claim whose entry in catalog/registrations.yaml does not name exactly these dials on exactly this drafter
2 no_peeking_violation a live model on a guarded drafter, or on any world with a guarded dial in play, without a licence
3 unknown_dials_violation a dial no drafter, brief, episode or factory setting reads
4 declared_surface_violation levers missing on a guarded drafter, or a lever equal to the readout molecule
5 sampling_violation a live arm without a stated temperature regime
6 out_dir resolution an existing records.jsonl unless resume=True
7 retry pass (resume only) re-runs error lines, preserving the originals in records.retried.jsonl

bio suite run --dry runs the same preflight() the run does and prints every verdict (registration, no-peeking, dials, surface, sampling, price, resume drift, out_dir), exiting 1 when the run would refuse; the estimate is advisory metadata on the manifest, and the only spend gate at run time is cost_ceiling_usd, consulted after every landed unit.

Modules

MODULE LOC Role
spec.py 577 ExperimentSpec (30 fields), spec_from_dict + validators, load_spec, agent_kinds_in_play, estimate_cost (prices every model-axis level).
drafters.py 1180 The drafter heads and DRAFTERS, WORLD_INVARIANT_DIALS, runtime_dials, guarded_drafters()/guarded_dials() (read live by the guards), CONFLICT_FREE_DRAFTERS.
agents.py 388 The scripted agent factories and AGENTS.
store.py 386 record_to_json/record_from_json, the brief codec, _build_manifest.
guards.py 307 The five guards, unknown_dials_violation, _resume_spec_drift, Preflight/preflight — every refusal before spend, in the run's order; --dry prints the same list.
report_text.py 389 render_report, idle_baseline_comparison, primary_contrast_result.
experiment.py 500 run_experiment, aggregate, and the facade that re-exports every name above (48 test files and AUP's launchers import from here).
expr_experiment.py 832 The Expr front end: !experiment, the brief/episode/power/llm/task/pattern/suite heads, load_experiment, spec_to_text (the declared inverse).
registration.py 153 Registration, load_registry, resolve_registration;
the registry test pins the filed AUP entries.
conditions.py 217 The older orthogonal dial sampler (DialAxis, ConditionSpec, sample); not on the run path.
models_snapshot.json — Pinned models.list snapshot, refreshed by bio suite models.

The expr_experiment ↔ experiment import cycle is gone (T058): expr_experiment.py imports spec/drafters/agents directly; _Drafters.__missing__ stays to admit a drafter a catalog file's _includes_ registers after import. The M1 bio/experiment.py that also exported a run_experiment was deleted in T056.

Where a config error could reach spend

Hypotheses for the latent-bug pass (T051 box 4).

HYPOTHESIS Why it would be quiet Where
Resume after an edited axis existing_by_key keys on each line's own (label, index); nothing compares the on-disk spec_sha256 to the new spec, so colliding labels are reused as the new cell and the rest re-charged experiment.py ~2661; mass_trial.py ~555
Resume overwrites the manifest hash the manifest is rebuilt from the current spec, keeping only started_at; aggregate then re-keys everything through it experiment.py ~2679–2687, ~2804
Pricing falls back to PINNED_MODEL cost_model = cond.get("model") or spec.model or PINNED_MODEL; a model axis is estimated at the default price, and an unpriced model raises mid-grid after the money is spent experiment.py ~629, ~2712; llm_agent.py ~172
expected_turns misses a non-dial budget derived from fixed_dials and axes only; a budget arriving another way falls through to the literal 8 experiment.py ~312–329
The record stamps the claimed licence string payload["registration"] = spec.registration, not the resolved entry; lines written before the claim carry no stamp experiment.py ~2731
Two condition-key normalizations the grid resets each key to the swept axes; a direct runner.run record carries every dial; scorers sort inconsistently mass_trial.py ~517; trial.py ~47
_canonical_json hashes exotic values by repr default=repr on the spec hash experiment.py ~2507