Studies¶
The one sentence
A Study wraps one question, one emit-contract, and one pass/fail bar around a Composite.
A composite is reusable across many questions. A study attaches one of those questions to it: which composite to run, what to measure, and what "passing" means. An Investigation then lets a dozen such questions accumulate into a defensible claim.
Separating them means you can re-run the science without rewriting the argument — and audit the argument without re-reading the code.
A study is never compiled into a composite of its own. It stays metadata that points at a composite by a dotted id and reads back what came out. That is the whole design: the composite runs, the study records.
On this page
Assumes Workspaces & the Workbench, Composites & wiring. · You'll learn the study/composite split, the three on-disk layers, and the five-phase authoring lifecycle.
Three on-disk layers¶
| Layer | File | What it is |
|---|---|---|
| Investigation | investigations/<slug>/investigation.yaml |
a named collection of studies = a git branch = a worktree |
| Study | studies/<slug>/study.yaml |
one question + its narrative spine (nests under its investigation, or flat for legacy) |
| Composite | viva_<pkg>/composites/<id>.composite.yaml |
the runnable process-bigraph document |
Read top-down it is an argument; read bottom-up it is a simulation.
The five-phase lifecycle¶
Authoring a study runs through five phases. They are coarsely sequential and iterative in practice — Evaluate routinely sends you back to Build. Each phase writes a different part of the study spine:
| Phase | Produces | Writes to |
|---|---|---|
| Design | the spec: question, gate, run set, tests | purpose, pipeline_gate, simulation_set, behavior_tests, readouts |
| Build | executable code: Process classes + composites | model_change, implementation_requirements, the composite files |
| Simulate | the runs: trajectories | runs.db |
| Evaluate | the verdict: test results + figures | outcomes, visualizations, findings |
| Decide | conclusion + follow-ups | conclusion_verdicts, discovery implications |
flowchart LR
D["Design"] --> B["Build"] --> S["Simulate"] --> E["Evaluate"] --> C["Decide"]
E -.->|"fails / needs work"| B
C -.->|"seeds a child study"| D
The phase: field on a study is a capitalized enum — Design | Build | Simulate |
Evaluate | Decide. An investigation card surfaces its slowest-phase member, so one
study stuck in Design holds the whole investigation in Design.
Phases vs the study-detail 'acts'
On the study-detail page the same work is grouped into five reading acts — Study (overview) · Design (what you author) · Evidence (what came back) · Assurance (what grades it) · Decision. The load-bearing boundary there: a report card grades (→ Assurance › Tests); an analysis derives with no verdict (→ Evidence › Analyses). See Analyses, visualizations & report cards.
How a study uses its composite — the five join fields¶
A study does not rebuild its composite. It specializes one through exactly five fields:
1 · Baselines¶
baseline[] (or v4 conditions.baseline) — the runnable composite(s). A non-empty
list so a study can compare several composites side by side: {name, composite, params}.
2 · Variants¶
variants[] — parameter overrides only: {name, base_composite, parameter_overrides}.
No structural or initial-state edits; base_composite must name an existing baseline entry.
3 · Simulation set¶
simulation_set[] — the run recipe (base model, perturbation, condition, seeds, duration).
In v4 it replaces the separate v3 variants: + interventions: lists.
4 · Readouts — the emit contract¶
readouts[].store_path — ties a named observable to the exact emission path in the
composite's output. This is the contract that says what the run must emit to be gradable.
5 · Behavior tests — the verdict¶
behavior_tests[] — a machine-checkable spec: a measure (how to extract a number from
run history) plus a pass_if (op + value/range). This is what turns a trajectory into a
pass or fail. A test may carry a variant: sub-field to scope it to one named variant.
Running a baseline (POST /api/study-run-baseline) resolves the baseline id, builds
that composite in-process, merges the params, runs it, and records the trajectory in
runs.db. The study itself is never compiled.
Derive-on-read: verdicts are computed, never asserted¶
This is the single most important rule of the study spine, and it is worth internalizing:
A study's conclusion is computed from its evidence, not asserted. You never write
status: pass by hand. A per-test pass/fail pill is derived from the latest run's
outcomes[test].result in runs.db. This is what stops a study from claiming a
result it never produced.
The spine keeps authored intent and computed evidence in parallel fields, and a
diverges_from_authored flag is the dashboard headline when they disagree:
| Authored (a human writes) | Computed (code fills, on read) |
|---|---|
runs[].outcomes |
runs[].computed_outcomes |
gate_status |
pipeline_gate.gate_evaluator |
executive.verdict |
.computed_acceptance |
finding.statement / .summary |
finding.evidence / .expected / .provenance |
So a test authored status: passed but backed by no run outcome renders as a pending
pill (pending), not a pass. A study renders
Ran · Tests N✓ · Passed only when runs[].outcomes actually back the results.
The rolled-up verdict (study_verdict.roll_up_verdict()) aggregates outcomes and
prerequisites into a code-computed gate state — passed iff fail == 0 and pass > 0:
passed failed needs_calibration blocked not_started
It writes a parallel coded slot and never overwrites the authored gate.
Because the study is typed data, hardening it — filling gaps, citing bands, tightening a claim — is a transformation an agent can apply and a human can verify, not a vibe.
"Five phases" vs "six status axes"¶
Two similar-looking lists travel together in the docs. Keep them distinct:
Five phases — a sequence¶
Design → Build → Simulate → Evaluate → Decide. Where you are in authoring the study.
One value at a time; the phase: field.
Six status axes — independent dimensions¶
design · implementation · simulation · evaluation · gate · expert_review. Each is a
separate, nullable axis, so "we wrote it" is never confused with "it ran" or "it holds up."
The six axes replace the single coarse status: field for new specs, each with its own
enum (e.g. simulation_status: not_run · running · ran · failed;
gate_status: blocked · needs_calibration · passed · failed). The headline pill on
the study page prefers gate_status when set. They are additive — a legacy study with only
status: renders exactly as before.
The study.yaml narrative spine (v4)¶
A v3 study.yaml is organized into eight canonical sections plus two cross-cutting
fields. The v4 schema adds a second layer of optional narrative-spine fields — the
shape the DnaA-replication investigation evolved through use, now promoted to
canonical-optional. A v3 spec validates unchanged against v4; new scaffolds land the v4
fields commented in as TODO placeholders.
The eight canonical sections:
| # | Section | YAML field(s) |
|---|---|---|
| 1 | Purpose | purpose: (question / mechanism / expected_outcome) |
| 2 | Pipeline Gate | pipeline_gate: (prerequisites / enables / proceed_condition) |
| 3 | Simulations | simulation_set: (replaces v3 variants: + interventions:) |
| 4 | Build | model_change: + implementation_requirements: |
| 5 | Readouts | readouts: (replaces v3 observables:; each carries status) |
| 6 | Tests | behavior_tests: (replaces v3 expected_behavior:) |
| 7 | Limitations | limitations: |
| 8 | References | bibliography: |
Cross-cutting: key_assumptions: (pairs with Build) and conclusion_logic:
(if_primary_tests_pass: / if_primary_tests_fail:, pairs with Tests).
The v4 narrative spine, four layers (all optional; the six ★ fields are authored first and render at the top of the report):
- Executive —
title,claim,confidence(Accepted | Investigating | Planned | Refuted),runtime, ★report:(verdict / confidence / evidence_quality / key_metrics), ★study_card:(one-paragraph card). - Framing — ★
question:+assumptions:, ★conditions:(the grouped v4 alternative to top-levelbaseline:+variants:, whosemodel_settings[]is the source of truth for tunable parameters — current value + default + literature range + cite),enforced_params:(values each run is required to apply). - Validation — ★
behavior_tests:, ★readouts:(store_path= the emit contract),biological_summary:(textbook prose),literature_anchors:. - Implementation + decisions —
model_change,implementation_requirements,design_pivot_required:(open decision points), ★conclusion_verdicts:— a three-track block{regression_compatibility, biological_validation, explanatory_gain}, each{result, basis}, so a study can be "PASS on regression but MIXED on biology" instead of one forced boolean.
Accuracy note — the schema version is in flux
Study specs exist as v2 → v3 → v4, migrated on load. This chapter presents v4;
the schema_version: field selects the shape (and, at v4, the presence of a top-level
conditions: block disambiguates two v4 variants). Set schema_version: 4 to opt into
the v4 reserved field names. A handful of names are reserved at v4 and were renamed
from v3 to avoid collisions — references: → bibliography:, tests: is reserved (so
the field is behavior_tests:). When in doubt, read a live study.yaml in a real
workspace rather than inventing a field.
A realistic study.yaml skeleton¶
Faithful to the shipped shape; values truncated to placeholders.
schema_version: 4
name: dnaa-01-expression-dynamics # the slug is the technical id
title: DnaA expression dynamics # human display name (v4 spine)
created: '2026-08-14'
phase: Evaluate # Design | Build | Simulate | Evaluate | Decide
# --- six independent status axes (replace the coarse `status:`) ---
design_status: approved
implementation_status: complete
simulation_status: ran
evaluation_status: evaluated
gate_status: needs_calibration
expert_review_status: requested
# 1 · PURPOSE
purpose:
question: |
Does DnaA-ATP fraction cycle within its literature band across generations?
mechanism: |
Titration of DnaA by datA and the chromosomal DnaA boxes.
expected_outcome: |
DnaA-ATP fraction stays within 0.2-0.5, oscillating once per division.
# 2 · PIPELINE GATE
pipeline_gate:
prerequisites: [] # parent study slugs (bare slug or {study, condition})
enables: [dnaa-02-initiation]
proceed_condition: tests-passed
# 3 · SIMULATIONS (v4 grouped form)
conditions:
baseline:
composite: v2ecoli.composites.baseline.baseline
params: {}
variants:
- name: high-datA
base_composite: baseline
parameter_overrides: { datA_copies: 2 }
model_settings:
- name: dnaa_synthesis_rate
current: 0.85
default: 1.0
range: [0.5, 1.5]
units: 1/s
cites: [Hansen2018]
# 4 · BUILD
model_change:
- kind: change_config
path: processes.dnaa
implementation_requirements: []
# 5 · READOUTS (the emit contract)
readouts:
- name: dnaa_atp_fraction
store_path: listeners.dnaa.atp_fraction
status: available
units: dimensionless
# 6 · TESTS (measure + pass_if -> the verdict)
behavior_tests:
- name: atp-fraction-in-band
classification: primary
measure:
kind: generation_average # the discriminating sub-key is `kind`
path: listeners.dnaa.atp_fraction
pass_if: { op: in_range_every_generation, low: 0.2, high: 0.5 }
cites: [Boesen2024]
conclusion_logic:
if_primary_tests_pass: |
DnaA titration reproduces the observed cycling band.
if_primary_tests_fail: |
Revisit the datA titration parameters before advancing.
# --- v4 executive spine (authored last, renders first) ---
report:
verdict: passing-with-caveats # free-form; computed acceptance sits alongside
confidence: medium
evidence_quality: literature-matched
key_metrics: [dnaa_atp_fraction]
conclusion_verdicts:
regression_compatibility: { result: PASS, basis: "matches v1 baseline" }
biological_validation: { result: MIXED, basis: "band met in 6/7 generations" }
explanatory_gain: { result: POSITIVE, basis: "resolves initiation timing" }
# 7 · LIMITATIONS
limitations: |
Single-seed; ensemble replication pending.
# 8 · REFERENCES
bibliography:
- Boesen2024
- Hansen2018
# provenance — outcomes are written by the run, never by hand
runs: [] # {run_id, outcomes{...}} — filled from runs.db
findings: []
You author fields; you do not author verdicts
The runs[].outcomes, computed_outcomes, the rolled-up gate verdict, and the
per-finding evidence/provenance slots are all derive-on-read: code fills them
from the latest run in runs.db. Your job is the authored side — the question, the
tests, the bands, the prose. See Rigor & evidence for how the
computed side is graded.
Where studies fit¶
- Build the composite a study runs → Composites & wiring.
- Grade the runs, render figures and report cards → Analyses, visualizations & report cards.
- Group studies into a gated argument → Investigations.