> ## Documentation Index
> Fetch the complete documentation index at: https://proto.evodesign.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Programs

> Chains multiple optimizers into a multi-stage pipeline for biological sequence design

# Programs

While individual [optimizers](/docs/language/concepts/optimizers) run a single search strategy, a Program chains multiple optimizers into a **multi-stage pipeline**: broad exploration followed by targeted refinement, cheap filters before expensive scoring, temperature annealing across stages.

A Program runs its optimizers sequentially, automatically handling the handoff of results between stages.

## Single vs Multi-Stage

<Tabs>
  <Tab title="Single Stage">
    For simple designs, wrap one optimizer in a Program:

    ```python python icon="python" theme={null}
    from proto_language.core import Segment, Construct, Constraint, Program
    from proto_language.optimizer import MCMCOptimizer, MCMCOptimizerConfig
    from proto_language.generator import (
        RandomNucleotideGenerator, RandomNucleotideGeneratorConfig
    )
    from proto_language.constraint import gc_content_constraint

    # Setup
    segment = Segment(length=100, sequence_type="dna")
    construct = Construct([segment])

    generator = RandomNucleotideGenerator(
        RandomNucleotideGeneratorConfig()
    )
    generator.assign(segment)

    constraint = Constraint(
        inputs=[segment],
        function=gc_content_constraint,
        function_config={"min_gc": 45, "max_gc": 55},
    )

    # Single optimizer
    optimizer = MCMCOptimizer(
        constructs=[construct],
        generators=[generator],
        constraints=[constraint],
        config=MCMCOptimizerConfig(num_steps=500, num_results=5, proposals_per_result=10),
    )

    program = Program(optimizers=[optimizer], num_results=5)
    program.run()
    ```

    <div className="block dark:hidden">
      <svg viewBox="0 0 620 210" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Single-stage program from input sequence to MCMC to results" style={{width:"100%",height:"auto",display:"block"}}><defs><pattern id="gridprograms1L" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="1.2" fill="#344649" fillOpacity="0.10" /></pattern><marker id="arrprograms1L" viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 L3,5 z" fill="#768b8e" /></marker></defs><rect x="12" y="12" width="596" height="186" rx="16" fill="#f9fcfc" stroke="#dee9e8" strokeWidth="1.2" /><rect x="12" y="12" width="596" height="186" rx="16" fill="url(#gridprograms1L)" /><path d="M190,110 L244,110" fill="none" stroke="#9eb4b2" strokeWidth="1.6" markerEnd="url(#arrprograms1L)" /><path d="M374,110 L428,110" fill="none" stroke="#9eb4b2" strokeWidth="1.6" markerEnd="url(#arrprograms1L)" /><rect x="62" y="68" width="128" height="84" rx="10" fill="#eef2f4" stroke="#dbe3e6" strokeWidth="1.2" /><rect x="112" y="80" width="28" height="3.5" rx="2" fill="#768b8e" /><text x="126" y="108" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#344649" textAnchor="middle">Input</text><text x="126" y="126" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#768b8e" textAnchor="middle">Sequence</text><rect x="246" y="68" width="128" height="84" rx="10" fill="#046e7a" /><rect x="296" y="80" width="28" height="3.5" rx="2" fill="#046e7a" /><text x="310" y="108" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">MCMC</text><text x="310" y="126" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#bfe2e7" textAnchor="middle">500 steps</text><rect x="430" y="68" width="128" height="84" rx="10" fill="#2f8f6b" /><rect x="480" y="80" width="28" height="3.5" rx="2" fill="#2f8f6b" /><text x="494" y="108" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">Results</text><text x="494" y="126" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#d4ebe0" textAnchor="middle">(5 best)</text><g fill="#ffffff" stroke="#9eb4b2" strokeWidth="1.4"><circle cx="190" cy="110" r="3.2" /><circle cx="246" cy="110" r="3.2" /><circle cx="374" cy="110" r="3.2" /><circle cx="430" cy="110" r="3.2" /></g></svg>
    </div>

    <div className="hidden dark:block">
      <svg viewBox="0 0 620 210" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Single-stage program from input sequence to MCMC to results" style={{width:"100%",height:"auto",display:"block"}}><defs><pattern id="gridprograms1D" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="1.2" fill="#9eb4b2" fillOpacity="0.10" /></pattern><marker id="arrprograms1D" viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 L3,5 z" fill="#7e9498" /></marker></defs><rect x="12" y="12" width="596" height="186" rx="16" fill="#0e1718" stroke="#2b3c3e" strokeWidth="1.2" /><rect x="12" y="12" width="596" height="186" rx="16" fill="url(#gridprograms1D)" /><path d="M190,110 L244,110" fill="none" stroke="#566b6e" strokeWidth="1.6" markerEnd="url(#arrprograms1D)" /><path d="M374,110 L428,110" fill="none" stroke="#566b6e" strokeWidth="1.6" markerEnd="url(#arrprograms1D)" /><rect x="62" y="68" width="128" height="84" rx="10" fill="#1b2829" stroke="#33474a" strokeWidth="1.2" /><rect x="112" y="80" width="28" height="3.5" rx="2" fill="#7e9498" /><text x="126" y="108" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#d6e1df" textAnchor="middle">Input</text><text x="126" y="126" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#9eb4b2" textAnchor="middle">Sequence</text><rect x="246" y="68" width="128" height="84" rx="10" fill="#0a7e8c" /><rect x="296" y="80" width="28" height="3.5" rx="2" fill="#0a7e8c" /><text x="310" y="108" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">MCMC</text><text x="310" y="126" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#c0e6ea" textAnchor="middle">500 steps</text><rect x="430" y="68" width="128" height="84" rx="10" fill="#2f8f6b" /><rect x="480" y="80" width="28" height="3.5" rx="2" fill="#37a07c" /><text x="494" y="108" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">Results</text><text x="494" y="126" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#cfeadd" textAnchor="middle">(5 best)</text><g fill="#13201f" stroke="#566b6e" strokeWidth="1.4"><circle cx="190" cy="110" r="3.2" /><circle cx="246" cy="110" r="3.2" /><circle cx="374" cy="110" r="3.2" /><circle cx="430" cy="110" r="3.2" /></g></svg>
    </div>
  </Tab>

  <Tab title="Multi-Stage">
    Chain optimizers for coarse-to-fine optimization:

    ```python python icon="python" theme={null}
    from proto_language.optimizer import (
        RejectionSamplingOptimizer, RejectionSamplingOptimizerConfig,
        MCMCOptimizer, MCMCOptimizerConfig,
    )

    # Stage 1: Broad exploration with cheap constraints
    gen1 = RandomNucleotideGenerator(
        RandomNucleotideGeneratorConfig(masking_strategy=MaskingStrategy(num_mutations=10))
    )
    gen1.assign(segment)

    opt1 = RejectionSamplingOptimizer(
        constructs=[construct],
        generators=[gen1],
        constraints=[gc_constraint],
        config=RejectionSamplingOptimizerConfig(num_samples=5000, num_results=20),
    )

    # Stage 2: Refinement with expensive structure prediction
    gen2 = ESM2Generator(ESM2GeneratorConfig(masking_strategy=MaskingStrategy(num_mutations=3)))
    gen2.assign(segment)

    opt2 = MCMCOptimizer(
        constructs=[construct],  # Same construct object!
        generators=[gen2],
        constraints=[gc_constraint_2, structure_constraint],
        config=MCMCOptimizerConfig(num_steps=200, num_results=5, proposals_per_result=5),
    )

    # num_results=5 sets the default for all optimizers.
    # opt1 overrides with config.num_results=20; opt2 uses the program default of 5.
    program = Program(optimizers=[opt1, opt2], num_results=5)
    program.run()
    ```

    <div className="block dark:hidden">
      <svg viewBox="0 0 956 230" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Two-stage program: rejection sampling hands top results to MCMC" style={{width:"100%",height:"auto",display:"block"}}><defs><pattern id="gridprograms2L" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="1.2" fill="#344649" fillOpacity="0.10" /></pattern><marker id="arrprograms2L" viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 L3,5 z" fill="#768b8e" /></marker></defs><rect x="12" y="12" width="932" height="206" rx="16" fill="#f9fcfc" stroke="#dee9e8" strokeWidth="1.2" /><rect x="12" y="12" width="932" height="206" rx="16" fill="url(#gridprograms2L)" /><path d="M200,115 L246,115" fill="none" stroke="#9eb4b2" strokeWidth="1.6" markerEnd="url(#arrprograms2L)" /><path d="M408,115 L546,115" fill="none" stroke="#9eb4b2" strokeWidth="1.6" markerEnd="url(#arrprograms2L)" /><path d="M708,115 L754,115" fill="none" stroke="#9eb4b2" strokeWidth="1.6" markerEnd="url(#arrprograms2L)" /><rect x="40" y="71" width="160" height="88" rx="10" fill="#eef2f4" stroke="#dbe3e6" strokeWidth="1.2" /><rect x="106" y="83" width="28" height="3.5" rx="2" fill="#768b8e" /><text x="120" y="121" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#344649" textAnchor="middle">Input</text><rect x="248" y="71" width="160" height="88" rx="10" fill="#046e7a" /><rect x="314" y="83" width="28" height="3.5" rx="2" fill="#046e7a" /><text x="328" y="105" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">Rejection Sampling</text><text x="328" y="123" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#bfe2e7" textAnchor="middle">5000 samples</text><text x="328" y="138" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#bfe2e7" textAnchor="middle">(cheap filters)</text><rect x="548" y="71" width="160" height="88" rx="10" fill="#2f8f6b" /><rect x="614" y="83" width="28" height="3.5" rx="2" fill="#2f8f6b" /><text x="628" y="105" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">MCMC</text><text x="628" y="123" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#d4ebe0" textAnchor="middle">200 steps</text><text x="628" y="138" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#d4ebe0" textAnchor="middle">(+ structure)</text><rect x="756" y="71" width="160" height="88" rx="10" fill="#2f8f6b" /><rect x="822" y="83" width="28" height="3.5" rx="2" fill="#2f8f6b" /><text x="836" y="113" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">Results</text><text x="836" y="131" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#d4ebe0" textAnchor="middle">(5 best)</text><g fill="#ffffff" stroke="#9eb4b2" strokeWidth="1.4"><circle cx="200" cy="115" r="3.2" /><circle cx="248" cy="115" r="3.2" /><circle cx="408" cy="115" r="3.2" /><circle cx="548" cy="115" r="3.2" /><circle cx="708" cy="115" r="3.2" /><circle cx="756" cy="115" r="3.2" /></g><rect x="420" y="97.5" width="116" height="35" rx="6" fill="#f9fcfc" stroke="#e2e9ea" strokeWidth="1" /><text x="478" y="112.25" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11" fontWeight="400" fill="#506467" textAnchor="middle">top 20</text><text x="478" y="125.75" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11" fontWeight="400" fill="#506467" textAnchor="middle">sorted by energy</text></svg>
    </div>

    <div className="hidden dark:block">
      <svg viewBox="0 0 956 230" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Two-stage program: rejection sampling hands top results to MCMC" style={{width:"100%",height:"auto",display:"block"}}><defs><pattern id="gridprograms2D" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="1.2" fill="#9eb4b2" fillOpacity="0.10" /></pattern><marker id="arrprograms2D" viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 L3,5 z" fill="#7e9498" /></marker></defs><rect x="12" y="12" width="932" height="206" rx="16" fill="#0e1718" stroke="#2b3c3e" strokeWidth="1.2" /><rect x="12" y="12" width="932" height="206" rx="16" fill="url(#gridprograms2D)" /><path d="M200,115 L246,115" fill="none" stroke="#566b6e" strokeWidth="1.6" markerEnd="url(#arrprograms2D)" /><path d="M408,115 L546,115" fill="none" stroke="#566b6e" strokeWidth="1.6" markerEnd="url(#arrprograms2D)" /><path d="M708,115 L754,115" fill="none" stroke="#566b6e" strokeWidth="1.6" markerEnd="url(#arrprograms2D)" /><rect x="40" y="71" width="160" height="88" rx="10" fill="#1b2829" stroke="#33474a" strokeWidth="1.2" /><rect x="106" y="83" width="28" height="3.5" rx="2" fill="#7e9498" /><text x="120" y="121" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#d6e1df" textAnchor="middle">Input</text><rect x="248" y="71" width="160" height="88" rx="10" fill="#0a7e8c" /><rect x="314" y="83" width="28" height="3.5" rx="2" fill="#0a7e8c" /><text x="328" y="105" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">Rejection Sampling</text><text x="328" y="123" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#c0e6ea" textAnchor="middle">5000 samples</text><text x="328" y="138" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#c0e6ea" textAnchor="middle">(cheap filters)</text><rect x="548" y="71" width="160" height="88" rx="10" fill="#2f8f6b" /><rect x="614" y="83" width="28" height="3.5" rx="2" fill="#37a07c" /><text x="628" y="105" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">MCMC</text><text x="628" y="123" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#cfeadd" textAnchor="middle">200 steps</text><text x="628" y="138" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#cfeadd" textAnchor="middle">(+ structure)</text><rect x="756" y="71" width="160" height="88" rx="10" fill="#2f8f6b" /><rect x="822" y="83" width="28" height="3.5" rx="2" fill="#37a07c" /><text x="836" y="113" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14" fontWeight="600" fill="#ffffff" textAnchor="middle">Results</text><text x="836" y="131" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11.5" fontWeight="400" fill="#cfeadd" textAnchor="middle">(5 best)</text><g fill="#13201f" stroke="#566b6e" strokeWidth="1.4"><circle cx="200" cy="115" r="3.2" /><circle cx="248" cy="115" r="3.2" /><circle cx="408" cy="115" r="3.2" /><circle cx="548" cy="115" r="3.2" /><circle cx="708" cy="115" r="3.2" /><circle cx="756" cy="115" r="3.2" /></g><rect x="420" y="97.5" width="116" height="35" rx="6" fill="#13201f" stroke="#33474a" strokeWidth="1" /><text x="478" y="112.25" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11" fontWeight="400" fill="#9eb4b2" textAnchor="middle">top 20</text><text x="478" y="125.75" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="11" fontWeight="400" fill="#9eb4b2" textAnchor="middle">sorted by energy</text></svg>
    </div>
  </Tab>
</Tabs>

## The Handoff

When one optimizer finishes and the next begins, the Program performs a carefully orchestrated handoff:

<div className="block dark:hidden">
  <svg viewBox="0 0 460 606" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Program handoff: results flow from stage one through the handoff to stage two" style={{width:"100%",height:"auto",display:"block"}}><defs><pattern id="gridprograms3L" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="1.2" fill="#344649" fillOpacity="0.10" /></pattern><marker id="arrprograms3L" viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 L3,5 z" fill="#768b8e" /></marker></defs><rect x="12" y="12" width="436" height="582" rx="16" fill="#f9fcfc" stroke="#dee9e8" strokeWidth="1.2" /><rect x="12" y="12" width="436" height="582" rx="16" fill="url(#gridprograms3L)" /><rect x="46" y="40" width="368" height="186" rx="14" fill="#eef5f4" stroke="#ced9d7" strokeWidth="1.3" fillOpacity="0.55" strokeDasharray="5 4" /><text x="64" y="61" fontFamily="'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace" fontSize="10.5" fontWeight="500" fill="#768b8e" textAnchor="start" letterSpacing="1.6">Stage 1: Rejection Sampling</text><rect x="46" y="252" width="368" height="186" rx="14" fill="#eef5f4" stroke="#ced9d7" strokeWidth="1.3" fillOpacity="0.55" strokeDasharray="5 4" /><text x="64" y="273" fontFamily="'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace" fontSize="10.5" fontWeight="500" fill="#768b8e" textAnchor="start" letterSpacing="1.6">Handoff</text><rect x="46" y="464" width="368" height="106" rx="14" fill="#eef5f4" stroke="#ced9d7" strokeWidth="1.3" fillOpacity="0.55" strokeDasharray="5 4" /><text x="64" y="485" fontFamily="'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace" fontSize="10.5" fontWeight="500" fill="#768b8e" textAnchor="start" letterSpacing="1.6">Stage 2: MCMC</text><path d="M230,131 L230,151" fill="none" stroke="#9eb4b2" strokeWidth="1.6" markerEnd="url(#arrprograms3L)" /><path d="M230,211 L230,283" fill="none" stroke="#9eb4b2" strokeWidth="1.6" markerEnd="url(#arrprograms3L)" /><path d="M230,343 L230,363" fill="none" stroke="#9eb4b2" strokeWidth="1.6" markerEnd="url(#arrprograms3L)" /><path d="M230,423 L230,495" fill="none" stroke="#9eb4b2" strokeWidth="1.6" markerEnd="url(#arrprograms3L)" /><rect x="70" y="73" width="320" height="58" rx="10" fill="#eef2f4" stroke="#dbe3e6" strokeWidth="1.2" /><rect x="80" y="85" width="4" height="34" rx="2" fill="#768b8e" /><text x="92" y="97" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#344649" textAnchor="start">Run optimizer</text><rect x="70" y="153" width="320" height="58" rx="10" fill="#eef2f4" stroke="#dbe3e6" strokeWidth="1.2" /><rect x="80" y="165" width="4" height="34" rx="2" fill="#768b8e" /><text x="92" y="174" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#344649" textAnchor="start">Sort result\_sequences</text><text x="92" y="193" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="12" fontWeight="400" fill="#768b8e" textAnchor="start">by energy (best first)</text><rect x="70" y="285" width="320" height="58" rx="10" fill="#046e7a" /><rect x="80" y="297" width="4" height="34" rx="2" fill="#bfe2e7" /><text x="92" y="306" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#ffffff" textAnchor="start">Initialize next optimizer's pools</text><text x="92" y="325" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="12" fontWeight="400" fill="#bfe2e7" textAnchor="start">by cycling through sorted results</text><rect x="70" y="365" width="320" height="58" rx="10" fill="#eef2f4" stroke="#dbe3e6" strokeWidth="1.2" /><rect x="80" y="377" width="4" height="34" rx="2" fill="#768b8e" /><text x="92" y="386" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#344649" textAnchor="start">Clear stale constraint</text><text x="92" y="405" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="12" fontWeight="400" fill="#768b8e" textAnchor="start">metadata</text><rect x="70" y="497" width="320" height="58" rx="10" fill="#eef2f4" stroke="#dbe3e6" strokeWidth="1.2" /><rect x="80" y="509" width="4" height="34" rx="2" fill="#768b8e" /><text x="92" y="521" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#344649" textAnchor="start">Run optimizer</text><g fill="#ffffff" stroke="#9eb4b2" strokeWidth="1.4"><circle cx="230" cy="131" r="3.2" /><circle cx="230" cy="153" r="3.2" /><circle cx="230" cy="211" r="3.2" /><circle cx="230" cy="285" r="3.2" /><circle cx="230" cy="343" r="3.2" /><circle cx="230" cy="365" r="3.2" /><circle cx="230" cy="423" r="3.2" /><circle cx="230" cy="497" r="3.2" /></g></svg>
</div>

<div className="hidden dark:block">
  <svg viewBox="0 0 460 606" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Program handoff: results flow from stage one through the handoff to stage two" style={{width:"100%",height:"auto",display:"block"}}><defs><pattern id="gridprograms3D" width="22" height="22" patternUnits="userSpaceOnUse"><circle cx="2" cy="2" r="1.2" fill="#9eb4b2" fillOpacity="0.10" /></pattern><marker id="arrprograms3D" viewBox="0 0 10 10" refX="8.5" refY="5" markerWidth="6.5" markerHeight="6.5" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 L3,5 z" fill="#7e9498" /></marker></defs><rect x="12" y="12" width="436" height="582" rx="16" fill="#0e1718" stroke="#2b3c3e" strokeWidth="1.2" /><rect x="12" y="12" width="436" height="582" rx="16" fill="url(#gridprograms3D)" /><rect x="46" y="40" width="368" height="186" rx="14" fill="#15211f" stroke="#33474a" strokeWidth="1.3" fillOpacity="0.5" strokeDasharray="5 4" /><text x="64" y="61" fontFamily="'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace" fontSize="10.5" fontWeight="500" fill="#7e9498" textAnchor="start" letterSpacing="1.6">Stage 1: Rejection Sampling</text><rect x="46" y="252" width="368" height="186" rx="14" fill="#15211f" stroke="#33474a" strokeWidth="1.3" fillOpacity="0.5" strokeDasharray="5 4" /><text x="64" y="273" fontFamily="'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace" fontSize="10.5" fontWeight="500" fill="#7e9498" textAnchor="start" letterSpacing="1.6">Handoff</text><rect x="46" y="464" width="368" height="106" rx="14" fill="#15211f" stroke="#33474a" strokeWidth="1.3" fillOpacity="0.5" strokeDasharray="5 4" /><text x="64" y="485" fontFamily="'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace" fontSize="10.5" fontWeight="500" fill="#7e9498" textAnchor="start" letterSpacing="1.6">Stage 2: MCMC</text><path d="M230,131 L230,151" fill="none" stroke="#566b6e" strokeWidth="1.6" markerEnd="url(#arrprograms3D)" /><path d="M230,211 L230,283" fill="none" stroke="#566b6e" strokeWidth="1.6" markerEnd="url(#arrprograms3D)" /><path d="M230,343 L230,363" fill="none" stroke="#566b6e" strokeWidth="1.6" markerEnd="url(#arrprograms3D)" /><path d="M230,423 L230,495" fill="none" stroke="#566b6e" strokeWidth="1.6" markerEnd="url(#arrprograms3D)" /><rect x="70" y="73" width="320" height="58" rx="10" fill="#1b2829" stroke="#33474a" strokeWidth="1.2" /><rect x="80" y="85" width="4" height="34" rx="2" fill="#7e9498" /><text x="92" y="97" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#d6e1df" textAnchor="start">Run optimizer</text><rect x="70" y="153" width="320" height="58" rx="10" fill="#1b2829" stroke="#33474a" strokeWidth="1.2" /><rect x="80" y="165" width="4" height="34" rx="2" fill="#7e9498" /><text x="92" y="174" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#d6e1df" textAnchor="start">Sort result\_sequences</text><text x="92" y="193" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="12" fontWeight="400" fill="#9eb4b2" textAnchor="start">by energy (best first)</text><rect x="70" y="285" width="320" height="58" rx="10" fill="#0a7e8c" /><rect x="80" y="297" width="4" height="34" rx="2" fill="#c0e6ea" /><text x="92" y="306" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#ffffff" textAnchor="start">Initialize next optimizer's pools</text><text x="92" y="325" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="12" fontWeight="400" fill="#c0e6ea" textAnchor="start">by cycling through sorted results</text><rect x="70" y="365" width="320" height="58" rx="10" fill="#1b2829" stroke="#33474a" strokeWidth="1.2" /><rect x="80" y="377" width="4" height="34" rx="2" fill="#7e9498" /><text x="92" y="386" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#d6e1df" textAnchor="start">Clear stale constraint</text><text x="92" y="405" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="12" fontWeight="400" fill="#9eb4b2" textAnchor="start">metadata</text><rect x="70" y="497" width="320" height="58" rx="10" fill="#1b2829" stroke="#33474a" strokeWidth="1.2" /><rect x="80" y="509" width="4" height="34" rx="2" fill="#7e9498" /><text x="92" y="521" fontFamily="Geist, ui-sans-serif, system-ui, -apple-system, sans-serif" fontSize="14.5" fontWeight="600" fill="#d6e1df" textAnchor="start">Run optimizer</text><g fill="#13201f" stroke="#566b6e" strokeWidth="1.4"><circle cx="230" cy="131" r="3.2" /><circle cx="230" cy="153" r="3.2" /><circle cx="230" cy="211" r="3.2" /><circle cx="230" cy="285" r="3.2" /><circle cx="230" cy="343" r="3.2" /><circle cx="230" cy="365" r="3.2" /><circle cx="230" cy="423" r="3.2" /><circle cx="230" cy="497" r="3.2" /></g></svg>
</div>

**After each optimizer completes:**

Optimizers are responsible for their own ordering. Rejection Sampling keeps `result_sequences` sorted by energy (best first) throughout its run. Other optimizers preserve their natural ordering.

**Before the next optimizer runs:**

1. `_initialize_sequence_pools()` reads from the previous optimizer's `result_sequences`
2. Both pools are filled by cycling through source (preserving diversity when sizes differ)
3. Stale constraint metadata is cleared so the new stage starts with a clean slate

### Optimizer-Specific Behavior

Not all optimizers use inherited state the same way:

| Optimizer              | How It Uses Previous Results                                                  |
| ---------------------- | ----------------------------------------------------------------------------- |
| **Rejection Sampling** | Uses as starting proposals, then generates more and keeps overall best        |
| **MCMC**               | Uses as parallel trajectories, generates proposals from each                  |
| **Cycling**            | Uses as working proposals for conditioning cycles                             |
| **BeamSearch**         | **Ignores previous results.** Always starts fresh from its `prompt` parameter |

<Note>
  BeamSearch ignores previous optimizer results by design. It always starts fresh from its configured `prompt` since it is built for autoregressive generation. Place it as the first stage in a pipeline, or use it standalone.
</Note>

## Pipeline Design Recipes

<Note>
  The snippets below are illustrative patterns. They assume the `segment`, `construct`, generators, and the named constraint objects (for example `gc_constraint`, `structure_constraint`, `expression_constraint`) have already been defined as shown in the earlier examples and the [Constraints](/docs/language/concepts/constraints) guide.
</Note>

<CardGroup cols={2}>
  <Card title="Exploration then Refinement" icon="search">
    **Rejection Sampling (broad) then MCMC (focused)**

    Use Rejection Sampling to quickly sample thousands of proposals with cheap constraints, then hand the best ones to MCMC for detailed optimization with expensive constraints.

    *Most common multi-stage pattern.*
  </Card>

  <Card title="Progressive Constraints" icon="layers">
    **MCMC (basic) then MCMC (+ structure) then MCMC (+ expression)**

    Start with cheap sequence-level constraints, then progressively add expensive constraints. Each stage builds on the previous one's results.

    *Avoids wasting GPU time scoring bad sequences.*
  </Card>

  <Card title="Temperature Annealing" icon="thermometer">
    **MCMC (hot) then MCMC (warm) then MCMC (cold)**

    Explicit temperature stages: high temperature for broad exploration, medium for narrowing, low for final polishing. More control than single-optimizer annealing.

    *Better for rugged energy landscapes.*
  </Card>

  <Card title="Generator Switching" icon="shuffle">
    **Rejection Sampling + RandomNucleotide then MCMC + ESM2**

    Start with fast random mutations for initial screening, then switch to language-model-guided mutations for biologically informed refinement.

    *Combines fast screening with language-model-guided refinement.*
  </Card>
</CardGroup>

### Exploration then Refinement

```python python icon="python" theme={null}
# Stage 1: Fast exploration with cheap constraints
gen1 = RandomNucleotideGenerator(
    RandomNucleotideGeneratorConfig(masking_strategy=MaskingStrategy(num_mutations=10))
)
gen1.assign(segment)

opt1 = RejectionSamplingOptimizer(
    constructs=[construct],
    generators=[gen1],
    constraints=[gc_filter, homopolymer_filter],
    config=RejectionSamplingOptimizerConfig(num_samples=5000, num_results=20),
)

# Stage 2: Structure-based refinement
gen2 = ESM2Generator(ESM2GeneratorConfig(masking_strategy=MaskingStrategy(num_mutations=3)))
gen2.assign(segment)

opt2 = MCMCOptimizer(
    constructs=[construct],
    generators=[gen2],
    constraints=[gc_constraint, plddt_constraint, rmsd_constraint],
    config=MCMCOptimizerConfig(
        num_steps=200,
        num_results=5,
        proposals_per_result=5,
        max_temperature=2.0,
    ),
)

Program(optimizers=[opt1, opt2], num_results=5).run()
```

### Progressive Constraints

```python python icon="python" theme={null}
# Stage 1: Sequence composition only
gen1 = RandomNucleotideGenerator(RandomNucleotideGeneratorConfig())
gen1.assign(segment)
opt1 = MCMCOptimizer(
    constructs=[construct],
    generators=[gen1],
    constraints=[gc_constraint_1],
    config=MCMCOptimizerConfig(num_steps=300, num_results=10, proposals_per_result=5),
)

# Stage 2: Add structure prediction
gen2 = RandomNucleotideGenerator(RandomNucleotideGeneratorConfig(masking_strategy=MaskingStrategy(num_mutations=9)))
gen2.assign(segment)
opt2 = MCMCOptimizer(
    constructs=[construct],
    generators=[gen2],
    constraints=[gc_constraint_2, structure_constraint],
    config=MCMCOptimizerConfig(num_steps=200, num_results=5, proposals_per_result=5),
)

# Stage 3: Add expression constraint
gen3 = RandomNucleotideGenerator(RandomNucleotideGeneratorConfig(masking_strategy=MaskingStrategy(num_mutations=6)))
gen3.assign(segment)
opt3 = MCMCOptimizer(
    constructs=[construct],
    generators=[gen3],
    constraints=[gc_constraint_3, structure_constraint_2, expression_constraint],
    config=MCMCOptimizerConfig(num_steps=100, num_results=3, proposals_per_result=5),
)

Program(optimizers=[opt1, opt2, opt3], num_results=10).run()
```

### Temperature Annealing

```python python icon="python" theme={null}
# High temperature: broad exploration
gen1 = RandomNucleotideGenerator(RandomNucleotideGeneratorConfig(masking_strategy=MaskingStrategy(num_mutations=8)))
gen1.assign(segment)
opt1 = MCMCOptimizer(
    constructs=[construct],
    generators=[gen1],
    constraints=constraints_1,
    config=MCMCOptimizerConfig(
        num_steps=500, num_results=10, proposals_per_result=10, max_temperature=5.0
    ),
)

# Medium temperature: narrowing
gen2 = RandomNucleotideGenerator(RandomNucleotideGeneratorConfig())
gen2.assign(segment)
opt2 = MCMCOptimizer(
    constructs=[construct],
    generators=[gen2],
    constraints=constraints_2,
    config=MCMCOptimizerConfig(
        num_steps=300, num_results=5, proposals_per_result=5, max_temperature=2.0
    ),
)

# Low temperature: polishing
gen3 = RandomNucleotideGenerator(RandomNucleotideGeneratorConfig(masking_strategy=MaskingStrategy(num_mutations=6)))
gen3.assign(segment)
opt3 = MCMCOptimizer(
    constructs=[construct],
    generators=[gen3],
    constraints=constraints_3,
    config=MCMCOptimizerConfig(
        num_steps=200, num_results=3, proposals_per_result=3, max_temperature=0.5
    ),
)

Program(optimizers=[opt1, opt2, opt3], num_results=10).run()
```

## Running Stages Individually

Use `run_stage()` for fine-grained control: inspect results between stages, conditionally skip stages, or re-run a stage with different parameters.

```python python icon="python" theme={null}
program = Program(optimizers=[opt1, opt2, opt3], num_results=5)

# Run first stage
program.run_stage(0)
results = program.get_stage_results(0)

# Inspect before continuing
best = results["results"][results["best_result_idx"]]
print(f"Stage 1 best energy: {best['energy_score']:.4f}")

# Conditionally run next stage
if best["energy_score"] < 0.5:
    program.run_stage(1)
else:
    print("Stage 1 didn't converge, skipping refinement")
```

A previous stage can also be **re-run**, which resets the pipeline to that point and invalidates subsequent stages:

```python python icon="python" theme={null}
# Re-run stage 0 (invalidates stages 1 and 2)
program.run_stage(0)
```

## Results and Export

### Accessing Results

```python python icon="python" theme={null}
program.run()

# Final energy scores (from last optimizer)
print(program.energy_scores)  # [0.05, 0.08, 0.12, ...]

# Final sequences (from shared constructs)
for construct in program.constructs:
    for sequence in construct.joined_sequences:
        print(sequence.sequence)

# Structured results
results = program.extract_results(program.energy_scores)
for result in results["results"]:
    print(f"Result {result['result_idx']}: energy={result['energy_score']:.4f}")
    for construct in result["constructs"]:
        for seg in construct["segments"]:
            print(f"  {seg['label']}: {seg['sequence'][:50]}...")
```

### Export Formats

<CodeGroup>
  ```python python icon="python" title="Bundle Export" theme={null}
  # Export all 4 tables at once (sequences, constraints, constructs, optimization)
  program.export(path="./results/", format="csv")
  # Creates: results/sequences.csv, results/constraints.csv,
  #          results/constructs.csv, results/optimization.csv
  ```

  ```python python icon="python" title="Single Table" theme={null}
  # A single table is accessed as a DataFrame (sequences, constraints,
  # constructs, or optimization), which can be written out directly
  df = program.to_dataframe(table="sequences")
  df.to_csv("sequences.csv", index=False)
  program.to_dataframe(table="constraints").to_csv("constraints.csv", index=False)
  ```

  ```python python icon="python" title="Excel" theme={null}
  # Multi-sheet Excel workbook with all 4 tables.
  # path is a folder; xlsx is written to <path>/results.xlsx
  program.export(path="./results/", format="xlsx")  # -> ./results/results.xlsx
  ```
</CodeGroup>

### Stage-Specific Results

Access results from any completed stage:

```python python icon="python" theme={null}
# Results from stage 0
stage_0_results = program.get_stage_results(0)

# Export a specific stage's results (writes the 4-table folder for that stage)
program.export(path="./stage0_results/", format="csv", stage=0)
```

### Optimizer-Level Export

Individual `Optimizer` instances also provide the same export methods (without the `stage` parameter):

```python python icon="python" theme={null}
optimizer.export(path="./results/", format="csv")
df = optimizer.to_dataframe(table="sequences")
fasta = optimizer.to_fasta()
```

### State Serialization

Save and restore program state for long-running optimization or checkpointing:

```python python icon="python" theme={null}
# Save state
state = program.serialize_state()
# Save to file, database, etc.
import json
with open("checkpoint.json", "w") as f:
    json.dump(state, f)

# Later: restore state and continue
with open("checkpoint.json") as f:
    state = json.load(f)
program.restore_state(state, stage_index=1)
program.run_stage(1)  # Resume from stage 1
```

## Important Rules

<Warning>
  **All optimizers in a Program must share the same Construct objects (by identity, not just value).** This is how state persists between stages. The construct is created once and the same object is passed to all optimizers.

  ```python python icon="python" theme={null}
  # Correct: same construct object
  construct = Construct([segment])
  opt1 = MCMCOptimizer(constructs=[construct], ...)
  opt2 = MCMCOptimizer(constructs=[construct], ...)  # Same object

  # Wrong: different construct objects (raises ValueError)
  opt1 = MCMCOptimizer(constructs=[Construct([segment])], ...)
  opt2 = MCMCOptimizer(constructs=[Construct([segment])], ...)  # Different object!
  ```
</Warning>

<Warning>
  **Each generator and constraint instance can only be used in one optimizer.** This prevents shared mutable state bugs. Create new instances for each stage.

  ```python python icon="python" theme={null}
  # Correct: separate generator instances per optimizer
  gen1 = RandomNucleotideGenerator(config)
  gen2 = RandomNucleotideGenerator(config)  # New instance, same config is fine
  gen1.assign(segment)
  gen2.assign(segment)

  # Wrong: reusing the same generator instance (raises ValueError)
  gen = RandomNucleotideGenerator(config)
  gen.assign(segment)
  opt1 = MCMCOptimizer(generators=[gen], ...)
  opt2 = MCMCOptimizer(generators=[gen], ...)  # Same instance -- error!
  ```
</Warning>

## Properties

| Property        | Description                                                                                                                                     |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `constructs`    | List of Construct objects being optimized (shared across all optimizers)                                                                        |
| `optimizers`    | List of Optimizer objects in sequence                                                                                                           |
| `num_results`   | Program-level default for the number of output sequences. Each optimizer resolves its result count as: config override > `num_results` > error. |
| `energy_scores` | Final energy scores from the last optimizer (after `run()`)                                                                                     |
| `current_stage` | Index of current/next stage to run                                                                                                              |
| `verbose`       | If True, forces verbose mode in all optimizers                                                                                                  |

## Next Steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="play" href="/docs/language/quickstart">
    A complete program, from scratch
  </Card>

  <Card title="Optimizers" icon="chart-line" href="/docs/language/concepts/optimizers">
    Deep dive into individual optimizer strategies
  </Card>

  <Card title="Constraints" icon="square-function" href="/docs/language/concepts/constraints">
    Scoring functions for design objectives
  </Card>

  <Card title="Tools" icon="wrench" href="/docs/language/concepts/tools">
    The bioinformatics tools that constraints and generators call
  </Card>
</CardGroup>
