Skip to content

Assembling a choreography ๐Ÿšง

Work in progress

This page documents Phase 1 (the contribution output contract), Phase 2a (the contribution spec and the choreography object), and Phase 2b (the contribution-output table format, prepare-run, the judge commons agent, and freeze-run) exactly as shipped in src/murmurent/core/ and src/murmurent/commands/. Compositional choreography remains under active development, and two steps in the workflow below stay manual by design rather than by omission: producing the contribution-output table (step 3) and invoking the judge (step 8) both happen outside any murmurent CLI, as described in those steps.

This page is a technical how-to and glossary for assembling a compositional choreography: a posed question that several contributors answer with independently authored contributions, joined and compared by a judge. For the conceptual overview, read Choreographies and Contributions first; this page assumes that context and goes one level deeper, into the exact artefacts, fields, and commands.

Glossary

Each term below is anchored to the concrete artefact or field that defines it, avoiding loose description. Where a term names a Python class or a CLI subcommand, that mapping is given explicitly.

output contract

Implemented by murmurent.core.contribution_contract.ContributionContract (src/murmurent/core/contribution_contract.py). The typed declaration of what a contribution produces, authored once per contribution, independent of how the contribution computes its answer. A contract is a schema-validated markdown entry (kind: contribution_contract in its frontmatter) with these required fields:

Field Meaning
contribution Name/slug of the contribution this contract belongs to.
author Offering member's handle, of the form @name.
question The posed question / choreography this contribution answers.
candidate_key The candidate-identity key (see below) this contribution's output is keyed on.
metric What the contribution reports, e.g. binding_affinity.
units Units of that metric, e.g. nM, kcal/mol, dimensionless.
direction higher_better or lower_better: whether a larger value of the metric is the better outcome.
uncertainty How uncertainty is expressed, e.g. stderr, ci95, or none.

Optional fields: tags (list) and a free-text notes body. A contract declares the shape and meaning of an output; the actual result table conforming to that shape is the contribution output, defined below.

ContributionContract.validate() checks the mechanical properties only (required fields present, author starts with @, direction is one of the two allowed values, candidate_key is in the controlled vocabulary or an other: escape). It does not, and cannot, judge whether the metric is scientifically meaningful.

candidate-identity key

The field candidate_key on both a ContributionContract and a Choreography. It names the identity space that lets two different contributions refer to the same candidate despite reporting different metrics. The controlled vocabulary (contribution_contract.CANDIDATE_KEY_VOCAB) is:

inchikey | smiles | gene_symbol | uniprot

with an escape hatch other:<free-text> for identity spaces outside that set (checked by candidate_key_ok()). A wet-lab binding assay, a docking simulation, and a generative-model score can all report on the same inchikey even though their metric and units differ entirely; that shared key is what makes their outputs combinable.

joinability

The property, checked mechanically by Choreography.validate() โ†’ _validate_joinability(), that every contribution attached to a choreography resolves to a spec whose output contract's candidate_key equals the choreography's own candidate_key. A contribution whose contract key differs is reported as a validation problem (... does not join the choreography's ... contributions are not combinable) and a contribution whose spec or contract cannot be resolved on disk is reported separately. Joinability is a necessary condition for contributions to be combinable; it is insufficient on its own, since the judge (below) still has to decide what to do when the joined outputs disagree.

contribution spec

Implemented by murmurent.core.contribution_spec.ContributionSpec (src/murmurent/core/contribution_spec.py). The authored contribution itself: a schema-validated markdown entry (kind: contribution_spec) with required fields contribution, author, question, and contract (a reference, given as a relative path or bare slug, to the contribution's output contract), plus an ordered steps list and an optional transitions list. ContributionSpec.validate() resolves the referenced contract and validates it too, so an authored contribution and the declared shape of what it produces cannot silently drift apart.

A spec also carries an optional output field: a path, relative to the spec file, to the produced contribution-output table (see below). ContributionSpec.validate() validates that table against the referenced contract only when output is set; a spec authored before the table exists is still a valid spec.

step

A contribution_spec.Step: one analysis in a contribution's graph, transforming an input into an output. Fields: name, kind (agent or script, per contribution_spec.STEP_KIND_VOCAB), run (the agent name or the command to execute), and an optional description. A contribution spec must declare at least one step (steps must be a non-empty, ordered list); each step must have a non-empty name and run, and a kind in the controlled vocabulary.

transition

A contribution_spec.Transition: a decision applied to a step's output. Fields: name, kind (rank, filter, or select, per contribution_spec.TRANSITION_KIND_VOCAB), and a free params dict (e.g. {"top": 100}). Transitions are optional at the spec level, but any transition present must have a valid name and kind.

contribution output

The contract-conforming result table a contribution actually produces once its steps and transitions run, validated by core.contribution_output.validate_output(contract, path). One row per candidate, with a column named after the contract's candidate_key and a column named after its metric; an uncertainty column (named uncertainty, or after the estimate kind, e.g. stderr) is required only when the contract's uncertainty is not none. CSV is the baseline format; .tsv/.tab (tab-delimited) and .parquet/.pq are recognised by extension. There is no CLI to produce this table: the contributor writes it with their own pipeline and points the contribution spec's output field at it (see contribution spec, above). Once written under Tier-3 append_only/<project>/... (see How Murmurent remembers), the table is itself append-only and immutable inputs stay untouched. The contribution output is what the judge reads and aligns across contributions; the contribution contract is the promise the output keeps.

choreography

Implemented by murmurent.core.choreography.Choreography (src/murmurent/core/choreography.py). The posed question itself: a schema-validated markdown entry (kind: choreography) with required fields:

Field Meaning
question Question slug/id.
poser Posing member's handle, @name.
title The human question, in prose.
candidate_key Identity space for the whole choreography (same vocabulary as a contract's candidate_key).
criteria The poser's judging/presentation criteria: free text, or loaded from a file.

Plus contributions, a list of references (path or slug) to attached contribution specs, populated by attach_contribution() / choreography offer. Choreography.validate() checks the required fields, that poser starts with @, that candidate_key is in vocabulary, and joinability across every attached contribution.

run package / prepare-run

murmurent choreography prepare-run <choreography-path> [--out <dir>] assembles a run package: a directory holding a run.yaml manifest (the choreography's question, title, poser, candidate_key, and criteria; a judge: {agent: judge, version_sha256: <sha256 of agents/judge.md>} block; and, per attached contribution, the contract fields plus a copied contract.md and a copied output.<ext>, each recorded with its own sha256), a choreography.md copy, and a contributions/<slug>/ copy per contribution. The command refuses to run if the choreography fails validate() (the joinability check) or if any attached contribution lacks a conforming output table. By default the package is written to Tier-3 append_only/ when the project's data tree exists, falling back to --out or a temporary default otherwise. The run package is what the judge agent reads; gathering these into one bundle up front means the judge's session starts from a fixed, inspectable set of inputs rather than re-resolving references live.

judge

A markdown-defined commons agent, shipped as agents/judge.md (alongside bookworm, blacksmith, adversary, etc.; see The reference agents), with a verdict vocabulary of Presented / Split / Insufficient, following the headline-first convention every reference agent uses. There is no CLI to run the judge: it is invoked in a Claude Code session against a prepared run package. Once invoked, it:

  • aligns every contribution's output on the choreography's candidate_key;
  • applies the poser's criteria to rank and present results;
  • surfaces disagreement explicitly rather than silently averaging it away, flagging a candidate that only one contribution favours rather than dropping it;
  • computes a single consensus figure only when the joined outputs share a metric; otherwise it reports the alternatives side by side with their provenance;
  • hands its output to the Artist for expression (a ranked table, a figure, an HTML report);
  • is itself reviewed by the Adversary, which checks the combination step for laundered or incommensurable evidence (e.g. quietly treating a docking score and a measured K_D as the same number).

Like other reference agents, the judge's ranking and presentation strategy is expected to be forked and adapted per lab as the group learns which presentations work (see Customizing an agent).

freeze-run / run record

murmurent choreography freeze-run <choreography-path> --result <path> [--run <run-package>] [--out <dir>] writes an append-only run record (record.yaml): a copy of the run package (frozen inputs, judge version, criteria) plus the judge's --result. Only new files are written, never overwrites. This is what makes a choreography's headline result reconstructible later, following the same discipline as Tier-3 immutable//append_only/ storage described in How Murmurent remembers, applied to a choreography run rather than a data file.

Step-by-step assembly workflow

The example below follows the sulfopin/Pin1-inhibitor case from Choreographies and Contributions: a lab poses the question of optimizing a Pin1 inhibitor, and three contributors, @member_a (wet-lab binding assay), @member_b (structure-based docking), and @member_c (generative model), each offer a contribution. All commands are shown as run from a shell with the murmurent CLI on PATH; every new subcommand writes to the personal vault's contributions//choreographies/ folder by default (or the lab-mgmt repo's choreographies/ for choreography new specifically), falls back to --out, and falls back to stdout if neither resolves.

1. Author each contribution's output contract

Each contributor authors a contract before writing any code, so the shape of what they will report is fixed and reviewable up front.

murmurent contribution contract new \
  --contribution pin1_binding_assay \
  --author @member_a \
  --question optimize_sulfopin_pin1 \
  --candidate-key inchikey \
  --metric binding_affinity \
  --units nM \
  --direction lower_better \
  --uncertainty stderr

murmurent contribution contract new \
  --contribution pin1_docking_filter \
  --author @member_b \
  --question optimize_sulfopin_pin1 \
  --candidate-key inchikey \
  --metric docking_score \
  --units kcal/mol \
  --direction lower_better \
  --uncertainty none

murmurent contribution contract new \
  --contribution pin1_generate_score \
  --author @member_c \
  --question optimize_sulfopin_pin1 \
  --candidate-key inchikey \
  --metric model_score \
  --units dimensionless \
  --direction higher_better \
  --uncertainty ci95

All three declare --candidate-key inchikey: that is the joinability requirement, decided before anyone writes a step. murmurent contribution contract new refuses to write an invalid contract; problems are printed and the command exits non-zero. Validate an existing contract file with:

murmurent contribution contract validate <path>

2. Author each contribution spec

Each contributor then authors the contribution itself: the steps and transitions that produce output conforming to the contract just written. --step is repeatable, one name:kind:run per step (kind is agent or script); --transition is repeatable, name:kind (kind is rank, filter, or select).

murmurent contribution spec new \
  --contribution pin1_binding_assay \
  --author @member_a \
  --question optimize_sulfopin_pin1 \
  --contract pin1_binding_assay \
  --step "assay:script:run_binding_assay.sh" \
  --transition "rank_by_affinity:rank"

murmurent contribution spec new \
  --contribution pin1_docking_filter \
  --author @member_b \
  --question optimize_sulfopin_pin1 \
  --contract pin1_docking_filter \
  --step "dock:script:run_docking.py --target pin1" \
  --transition "filter_by_energy:filter"

murmurent contribution spec new \
  --contribution pin1_generate_score \
  --author @member_c \
  --question optimize_sulfopin_pin1 \
  --contract pin1_generate_score \
  --step "generate:agent:blacksmith" \
  --step "score:script:score_candidates.py" \
  --transition "shortlist:select"

The --contract value is a bare slug here (pin1_binding_assay), which resolves to <slug>_contract.md in the same directory the spec is being written to; murmurent contribution spec new roots that resolution at the intended output directory before validating, so the stored reference still resolves once the file is written. murmurent contribution spec new validates both the spec's own fields (non-empty steps, valid kind values) and the referenced contract, refusing to write if either fails. Validate an existing spec (contract included) with:

murmurent contribution spec validate <path>

3. Produce each contribution output table

Manual step: no CLI wrapper. Each contributor runs their contribution's steps and transitions (however run invokes them: a script, an agent session) and writes the resulting table under append_only/<project>/<contribution>/, one row per candidate, with a column named after the choreography's candidate_key, a column named after the contract's metric, and an uncertainty column when the contract's uncertainty is not none. CSV is the baseline format; .tsv/.tab and .parquet/.pq are also recognised, by extension. Producing this table is the contributor's own pipeline; murmurent's part is only to validate it against the contract (contribution_output.validate_output) once written. The contributor then sets the contribution spec's output field to a path pointing at the table, relative to the spec file, so prepare-run (step 7) can find it.

4. Pose the choreography

If the choreography was started with murmurent choreography init (see Starting a choreography) and given a candidate key, its question is already posed and this step is done.

The poser states the question, the shared candidate-identity space, and the criteria the judge should apply. --criteria accepts either a literal string or @<file> to load criteria text from a file.

murmurent choreography new \
  --question optimize_sulfopin_pin1 \
  --poser @the_pi \
  --title "Optimize sulfopin (a Pin1 inhibitor) for improved binding affinity" \
  --candidate-key inchikey \
  --criteria @sulfopin_judging_criteria.md

A freshly posed choreography has no contributions attached, so validate() at this point only checks the poser-supplied fields; the joinability check is a no-op until contributions are offered.

5. Offer contributions

Each contributor attaches their contribution spec to the posed choreography. offer is idempotent: attaching the same reference twice is a no-op, not an error.

murmurent choreography offer optimize_sulfopin_pin1.md --contribution pin1_binding_assay
murmurent choreography offer optimize_sulfopin_pin1.md --contribution pin1_docking_filter
murmurent choreography offer optimize_sulfopin_pin1.md --contribution pin1_generate_score

6. Validate the choreography (the joinability check)

murmurent choreography validate optimize_sulfopin_pin1.md

This resolves every attached contribution's spec, resolves each spec's contract, and checks contract.candidate_key == choreography.candidate_key for all three. On success:

OK โ€” optimize_sulfopin_pin1.md is a valid choreography (3 contribution(s), all joinable on 'inchikey').

A contribution whose contract used, say, --candidate-key smiles instead of inchikey would fail here with a problem of the form:

contribution '...': contract candidate_key 'smiles' does not join the
choreography's 'inchikey' -- contributions are not combinable

before any judge ever runs. murmurent choreography show <path> is the read-only companion command: it prints the question, poser, candidate key, criteria, and each attached contribution's metric/units/direction with a joins/DIFFERS flag per contribution, useful for a quick human check before running validate.

7. Prepare the run

murmurent choreography prepare-run optimize_sulfopin_pin1.md

Assembles the run package: a run.yaml manifest (question, title, poser, candidate key, criteria, and the judge's agent name and agents/judge.md sha256), each attached contribution's contract and output table (from step 3, found via each spec's output field), and copies of the choreography and contribution specs. The command refuses to proceed if the choreography fails validate (the joinability check from step 6) or if any attached contribution lacks a conforming output table. By default the package is written to Tier-3 append_only/ when the project's data tree exists; pass --out <dir> to choose a destination explicitly.

8. Invoke the judge

Manual step: no CLI to run the judge. Open a Claude Code session and invoke the judge commons agent against the prepared run package. The judge aligns the three contribution outputs on inchikey, applies the criteria from sulfopin_judging_criteria.md, and produces a ranked result, flagging a candidate the docking contribution ranks highly but the binding assay does not cover rather than silently omitting it, and leads its reply with a Presented / Split / Insufficient verdict. The judge's combination step is expected to go to the Adversary for review before the result is treated as final.

9. Freeze the run

murmurent choreography freeze-run optimize_sulfopin_pin1.md --result <path-to-judge-output>

Writes the append-only run record (record.yaml): a copy of the run package (frozen inputs, judge-definition sha256, criteria) plus the judge's result, so the choreography's headline conclusion can be reconstructed later even as contributions are added or re-run. Pass --run <run-package> to freeze a specific previously-prepared package, or --out <dir> to choose the run record's destination.

See also

  • Choreographies: the conceptual model, coordination vs. compositional mode, the finalisation choreography.
  • Contributions: what a contribution is, method vs. service contributions, the sulfopin worked example this page follows.
  • How Murmurent remembers: the three-tier memory model; Tier-3 immutable//append_only/ storage referenced by contribution outputs and run records.
  • The reference agents: the Adversary and Artist roles the judge hands off to.