Recipe A · 02 · ~20 min · advanced

Compose a custom Pass.

A pass is the smallest unit of work the engine knows how to schedule. It reads the latest IR revision, decides what to change, and commits a new revision. If you want to inject a biological effect, a corruption mode, or an instrumentation hook that GenAIRR doesn't ship out of the box, you write a pass. This recipe walks through the three pieces every pass needs and shows how to slot it into a pipeline.

01 Implement name · effects · apply()
02 Emit events trace addresses · choice records
03 Plug in add to the PassPlan
PART 01

Anatomy of a pass.

Every pass exposes three things the runtime needs to call it: a stable name (used by the trace and error messages), a set of effects it declares (so the scheduler knows what invariants it can preserve), and an apply function that reads the latest simulation revision and returns a new one.

Three required pieces
  • name()stable identifier used in trace addresses and strict-mode errors
  • effects()declares what kind of edit the pass performs (mutation, structural indel, region-only, …)
  • apply(sim, rng, contracts)reads the latest IR, returns a new IR (or `None` for a no-op)
What you get for free
  • Deterministic RNGthe runtime threads a seeded RNG into every pass — same seed, same result
  • Trace integrationdraws you record under your pass-name end up in outcome.trace()
  • Contract bridgeif you sample from a distribution, the runtime can prune it against active contracts
PART 02

A minimal pass — flip every Nth base.

This pass is a toy: at simulation time it walks the assembled sequence and transversions every Nth base. Useless for biology, perfect for getting the wiring right.

Pass skeleton
from GenAIRR._engine import Pass, Simulation, Rng

class EveryNthPass:
    def __init__(self, stride: int):
        self.stride = stride

    def name(self) -> str:
        return "custom.every_nth"

    def effects(self):
        return frozenset({"point_mutation"})

    def apply(self, sim: Simulation, rng: Rng, contracts) -> Simulation | None:
        new_sim = sim.to_builder()
        for i in range(0, len(sim), self.stride):
            old = sim.base_at(i)
            new_sim.set_base(i, "T" if old != "T" else "A")
            new_sim.record_choice(
                address=f"custom.every_nth.flip[{i}]",
                value={"pos": i, "from": old, "to": "T"},
            )
        return new_sim.build()
PART 03

Plug it into a pipeline.

The Experiment DSL exposes the canonical phases. To insert a custom pass alongside them, drop one level lower and assemble the PassPlan by hand. The runtime accepts any list of passes; the DSL is just a curated default.

Building a plan around your pass
from GenAIRR._engine import PassPlan, CompiledSimulator
import GenAIRR as ga

refdata = ga.dataconfig_to_refdata(ga.HUMAN_IGH_OGRDB)
plan = PassPlan.recombine(refdata)

# slot the custom pass after recombination
plan.push_python_pass(EveryNthPass(stride=25))

sim = CompiledSimulator(plan)
out = sim.run(n=10, seed=42)

# the choices end up in the trace under your address
out[0].trace().prefix_query("custom.every_nth")
PART 04

Three worked examples to copy from.

The engine source ships canonical passes that match the patterns you'll need. Read them for shape; copy the structure for your own.

Point mutations

See engine_rs/src/passes/mutate/s5f.rs for the SHM pattern: walk the V region, draw a position from a per-base distribution, draw a substitute, set the base, record a trace entry.

Structural indels

See engine_rs/src/passes/corrupt/indel.rs for insertions/deletions: more bookkeeping because regions and provenance both shift, but the same name/effects/apply skeleton.

Region-only edits

See engine_rs/src/passes/corrupt/rev_comp.rs for a pass that touches only metadata (flips orientation, no base edits). Useful template for adding annotation without changing the pool.

Related recipes

Where to next.

A · 04 · Wire a custom SHM model →

A more focused variant: replace the SHM model without touching the rest of the pipeline.

Concept · The Simulation Pipeline →

Read how the built-in passes compose if you want to slot yours alongside them.