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/summarizer
Write 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.summarize
rotifer compile summarizer
rotifer arena submit summarizer
Step 3: Your First Composition — Seq
Seq executes genes sequentially, piping each output as input to the next:
Seq(A, B, C) = A → B → C
But 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 summarizer
Run it:
rotifer agent run researcher --input '{"query": "quantum computing 2026"}' --verbose
The --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"}' \
--verbose
Type 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 data
This 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.
For further actions, you may consider blocking this person and/or reporting abuse
