Rotifer genes are powerful on their own, but the real magic happens when you compose them. The gene algebra — Seq, Par, Cond, Try, and Transform — lets you wire simple genes into complex agent pipelines that are type-safe, verifiable, and automatically optimizable.
In this tutorial, you’ll build a real-world pipeline: search the web → summarize results → format output — then extend it with parallel execution, conditional branching, and error recovery.
Prerequisites
- Rotifer CLI installed (
npm i -g @rotifer/playground) - A project initialized (
rotifer init my-pipeline && cd my-pipeline) - Familiarity with basic gene concepts (see Your First Gene in 5 Minutes)
Step 1: Understand the Building Blocks
Your project already includes genesis genes. Let’s check what we have:
rotifer arena list┌──────┬─────────────────────┬────────────┬────────┬──────────┐│ # │ Name │ Domain │ F(g) │ Fidelity │├──────┼─────────────────────┼────────────┼────────┼──────────┤│ 1 │ genesis-web-search │ search.web │ 0.9200 │ Native ││ 2 │ genesis-search-lite │ search.web │ 0.7100 │ Native ││ 3 │ genesis-code-format │ code.format│ 0.8800 │ Native │└──────┴─────────────────────┴────────────┴────────┴──────────┘We’ll use genesis-web-search as the first stage. Now we need a summarizer gene.
Step 2: Create a Summarizer Gene
mkdir -p genes/summarizerWrite genes/summarizer/index.ts:
export async function express(input: { text: string; maxLength?: number;}) { const maxLen = input.maxLength || 200; const sentences = input.text.split(/[.!?]+/).filter(Boolean);
let summary = ""; for (const sentence of sentences) { if ((summary + sentence).length > maxLen) break; summary += sentence.trim() + ". "; }
return { summary: summary.trim(), wordCount: summary.split(/\s+/).length, };}Wrap and submit it:
rotifer wrap summarizer --domain text.summarizerotifer compile summarizerrotifer arena submit summarizerStep 3: Your First Composition — Seq
Seq executes genes sequentially, piping each output as input to the next:
Seq(A, B, C) = A → B → CBut wait — the output of genesis-web-search is { results: [...] }, while summarizer expects { text: string }. We need a Transform to bridge the schemas:
import { Seq, Transform } from "@rotifer/algebra";
const searchAndSummarize = Seq( "genesis-web-search", Transform((searchResult) => ({ text: searchResult.results.map(r => r.snippet).join(" "), maxLength: 200, })), "summarizer");Create an agent from this composition:
rotifer agent create researcher --genes genesis-web-search summarizerRun it:
rotifer agent run researcher --input '{"query": "quantum computing 2026"}' --verboseThe --verbose flag shows intermediate inputs and outputs at each stage.
Step 4: Add Parallel Execution — Par
What if you want to search multiple sources simultaneously? Par executes genes concurrently:
Par(A, B, C) = A ‖ B ‖ C → [resultA, resultB, resultC]import { Par, Seq, Transform } from "@rotifer/algebra";
const multiSourceSearch = Seq( Par( "genesis-web-search", "genesis-web-search-lite" ), Transform((results) => ({ text: results.flat().map(r => r.results?.map(x => x.snippet)).flat().join(" "), maxLength: 300, })), "summarizer");Both searches run in parallel using the thread pool. The results are collected into an array, then merged by the Transform and fed to the summarizer.
Step 5: Add Conditional Branching — Cond
Cond lets you route execution based on a runtime predicate:
import { Cond, Seq, Transform } from "@rotifer/algebra";
const adaptivePipeline = Seq( "genesis-web-search", Cond( (result) => result.results.length > 10, Seq( Transform((r) => ({ text: r.results.map(x => x.snippet).join(" "), maxLength: 500, })), "summarizer" ), Transform((r) => ({ summary: r.results[0]?.snippet || "No results found.", wordCount: 0, })) ));If the search returns more than 10 results, we summarize them. Otherwise, we just return the top snippet directly.
Step 6: Add Error Recovery — Try
Try attempts a primary gene and falls back to a secondary if it fails:
import { Try, Seq, Transform } from "@rotifer/algebra";
const resilientPipeline = Seq( Try( "genesis-web-search", "genesis-web-search-lite" ), Transform((r) => ({ text: r.results.map(x => x.snippet).join(" "), maxLength: 200, })), "summarizer");If the primary search fails (network error, rate limit, etc.), execution automatically falls back to the lite version. No manual error handling needed.
Step 7: The Complete Pipeline
Combining all operators into one production-grade pipeline:
import { Seq, Par, Cond, Try, Transform } from "@rotifer/algebra";
const productionPipeline = Seq( // Stage 1: Resilient multi-source search Try( Par("genesis-web-search", "genesis-web-search-lite"), Par("genesis-web-search-lite") // fallback: single source ),
// Stage 2: Merge parallel results Transform((results) => ({ text: results.flat() .map(r => r.results?.map(x => x.snippet)) .flat() .filter(Boolean) .join(" "), resultCount: results.flat().reduce((n, r) => n + (r.results?.length || 0), 0), })),
// Stage 3: Adaptive summarization Cond( (data) => data.resultCount > 5, Seq( Transform((d) => ({ text: d.text, maxLength: 400 })), "summarizer" ), Transform((d) => ({ summary: d.text.slice(0, 200), wordCount: 0 })) ),
// Stage 4: Format output "genesis-code-format");Create and run the agent:
rotifer agent create research-bot \ --genes genesis-web-search genesis-web-search-lite summarizer genesis-code-format
rotifer agent run research-bot \ --input '{"query": "rotifer protocol gene evolution"}' \ --verboseType Safety
The composition algebra enforces schema compatibility at composition time. If you wire two incompatible genes, you get a clear error:
Error[E0032]: Type mismatch in Seq composition → gene 'web-search' output: { results: SearchResult[] } → gene 'summarizer' input: { text: string, maxLength?: number }
help: Add a Transform between the genes to reshape the dataThis catches data flow bugs before runtime.
Fitness of Composed Genes
Compositions have their own fitness scores:
| Operator | Fitness Formula |
|---|---|
Seq | min(F(components)) × latency_penalty |
Par | avg(F(components)) × parallelism_bonus |
Try | F(primary) × success_rate + F(fallback) × (1 - success_rate) |
The Arena evaluates compositions as a whole, so there’s selection pressure toward efficient structures.
What You’ve Learned
- Seq chains genes into sequential pipelines
- Par runs genes concurrently for speed and redundancy
- Cond routes execution based on runtime conditions
- Try provides automatic error recovery with fallbacks
- Transform bridges schema mismatches between genes
- Compositions are type-safe and have composite fitness scores
Deep Dive: See the full Composition Patterns guide for all operators, type constraints, and fitness formulas. For agent CLI commands, see the Agent Reference.