← Back to Blog

Build a Production Hybrid Gene

Advanced tutorial: create a gene that calls external APIs through the Network Gateway with domain whitelisting, rate limiting, and graceful error handling.

Most genes are pure functions — they take input, compute, and return output. But real-world agents need to talk to external services: LLM providers, weather APIs, databases, search engines. Rotifer handles this through Hybrid fidelity, which gives a gene controlled network access via a Network Gateway — a sandboxed fetch proxy with domain whitelisting, rate limiting, timeout enforcement, and response size caps.

This tutorial walks through building a production-quality Hybrid gene from scratch.

When to Use Hybrid

FidelityNetwork AccessUse Case
WrappedNonePure logic — text transforms, math, formatting
HybridGateway-controlledExternal API calls — LLMs, weather, search, databases
NativeNoneCPU-bound computation — compiled WASM, crypto, parsing

Choose Hybrid when your gene needs to reach the outside world. The Network Gateway ensures it can only reach domains you explicitly allow, at rates you define.

Step 1: Initialize the Gene

Terminal window
rotifer init weather-gene --fidelity Hybrid

This scaffolds a gene directory with a phenotype.json pre-configured for Hybrid fidelity:

{
"name": "weather-gene",
"domain": "utility",
"description": "",
"inputSchema": {
"type": "object",
"properties": {
"prompt": { "type": "string" }
},
"required": []
},
"outputSchema": {
"type": "object",
"properties": {
"result": { "type": "string" }
}
},
"dependencies": [],
"version": "0.1.0",
"fidelity": "Hybrid",
"transparency": "Open",
"network": {
"allowedDomains": [],
"maxTimeoutMs": 30000,
"maxResponseBytes": 1048576,
"maxRequestsPerMin": 10
}
}

The network block is what distinguishes Hybrid from other fidelity levels. Every field matters — the runtime enforces all of them.

Step 2: Configure Network Access

Edit phenotype.json to declare the domains your gene needs:

{
"network": {
"allowedDomains": ["wttr.in"],
"maxTimeoutMs": 10000,
"maxResponseBytes": 524288,
"maxRequestsPerMin": 5
}
}

Domain rules:

Keep the domain list minimal. Every domain you add is an attack surface your gene exposes. If your gene calls one API, list one domain.

Tuning the limits:

FieldDefaultGuidance
maxTimeoutMs30000Set to 2–3× your expected API latency. 10s for fast APIs, 30s for LLMs.
maxResponseBytes1048576 (1 MiB)Enough for most JSON APIs. Increase for large payloads, decrease for simple endpoints.
maxRequestsPerMin10Match your upstream rate limit. Don’t set higher than the provider allows.

Step 3: Write the Express Function

Create express.ts in the gene directory. Hybrid genes receive a ctx object with a gatewayFetch function — this is your only network interface. Global fetch is not available inside the gene sandbox.

import type { GatewayFetchOptions, GatewayResponse } from "@rotifer/core";
interface WeatherInput {
city: string;
format?: "brief" | "detailed";
}
interface WeatherOutput {
result: string;
city: string;
source: string;
}
export default async function express(
input: WeatherInput,
ctx: { gatewayFetch: (url: string, options?: GatewayFetchOptions) => Promise<GatewayResponse> }
): Promise<WeatherOutput> {
const city = encodeURIComponent(input.city);
const formatParam = input.format === "detailed" ? "" : "?format=3";
const url = `https://wttr.in/${city}${formatParam}`;
const response = await ctx.gatewayFetch(url, {
method: "GET",
headers: { "Accept": "text/plain" },
});
if (response.status !== 200) {
throw new Error(`Weather API returned ${response.status}: ${response.body}`);
}
return {
result: response.body.trim(),
city: input.city,
source: "wttr.in",
};
}

Key points:

Step 4: Handle Gateway Errors

The Network Gateway throws NetworkGatewayError with specific codes. Production genes must handle each one gracefully:

import type { GatewayFetchOptions, GatewayResponse } from "@rotifer/core";
interface WeatherInput {
city: string;
}
interface WeatherOutput {
result: string;
city: string;
source: string;
degraded: boolean;
}
export default async function express(
input: WeatherInput,
ctx: { gatewayFetch: (url: string, options?: GatewayFetchOptions) => Promise<GatewayResponse> }
): Promise<WeatherOutput> {
const city = encodeURIComponent(input.city);
try {
const response = await ctx.gatewayFetch(`https://wttr.in/${city}?format=3`, {
method: "GET",
headers: { "Accept": "text/plain" },
});
return {
result: response.body.trim(),
city: input.city,
source: "wttr.in",
degraded: response.truncated,
};
} catch (err: any) {
switch (err.code) {
case "DOMAIN_BLOCKED":
throw new Error(`Configuration error: wttr.in not in allowedDomains`);
case "RATE_LIMITED":
return {
result: "Weather service temporarily unavailable (rate limited). Try again shortly.",
city: input.city,
source: "fallback",
degraded: true,
};
case "TIMEOUT":
return {
result: "Weather service did not respond in time. Try again later.",
city: input.city,
source: "fallback",
degraded: true,
};
case "RESPONSE_TOO_LARGE":
return {
result: "Weather response exceeded size limit. Try brief format.",
city: input.city,
source: "fallback",
degraded: true,
};
case "NETWORK_ERROR":
return {
result: `Network error: ${err.message}`,
city: input.city,
source: "fallback",
degraded: true,
};
default:
throw err;
}
}
}

The pattern: throw on configuration errors (DOMAIN_BLOCKED means the phenotype is misconfigured), degrade gracefully on transient errors (rate limits, timeouts, network failures). The degraded flag lets downstream consumers know the result may be incomplete.

Step 5: Test and Compile

Run the gene’s test suite:

Terminal window
rotifer test weather-gene

Expected output:

✓ weather-gene/express — city:"London" → result contains "London"
✓ weather-gene/express — city:"東京" → result contains "東京"
✓ weather-gene/error — invalid city → graceful fallback
3 passed | 0 failed
Next: rotifer compile weather-gene

Then compile to IR:

Terminal window
rotifer compile weather-gene
Compiling weather-gene v0.1.0
Fidelity: Hybrid
Network: wttr.in (1 domain)
Timeout: 10000ms | Max body: 512 KiB | Rate: 5/min
Output: genes/weather-gene/weather-gene.wasm (42 KiB)
✓ Compiled successfully

The compiler embeds the network configuration into the WASM custom sections. The runtime reads this at load time — there’s no way for the gene to bypass its declared network policy.

Step 6: Publish and Run in an Agent

Publish to the Rotifer Cloud registry:

Terminal window
rotifer publish weather-gene

Now use it in an agent:

Terminal window
rotifer agent create my-weather-agent --genes weather-gene
rotifer agent run my-weather-agent --input '{"city": "Berlin"}'
Agent: my-weather-agent
Gene: weather-gene (Hybrid, gateway: wttr.in)
Result: Berlin: ⛅ +12°C

The runtime auto-detects Hybrid fidelity, reads the network config from the compiled IR, constructs a NetworkGateway instance with the declared limits, and injects gatewayFetch into the gene’s execution context. The gene never touches raw fetch.

Best Practices

Minimal domain surface. List only the domains your gene actually calls. One gene, one API, one domain is the ideal. If you need multiple APIs, consider splitting into separate genes that compose in an agent.

Environment variables for API keys. Never hardcode credentials in gene source. Use ctx.env for API keys that the agent operator provides at deploy time. The gene declares what it needs; the operator supplies values.

Reasonable limits. Set maxTimeoutMs to 2–3× your expected latency, not the maximum 30s. Set maxResponseBytes to the actual payload size you expect, not 1 MiB by default. Tight limits catch regressions early.

LLM provider agnosticism. If your gene wraps an LLM, accept the endpoint URL and model name as input parameters. Don’t hardcode api.openai.com — let the operator choose their provider. Declare "allowedDomains": ["*.openai.com", "*.anthropic.com", "*.mistral.ai"] or accept the domain as a runtime parameter.

Test both happy and error paths. Every gatewayFetch call can fail five ways. Your test suite should cover at minimum: successful response, rate-limited response, and timeout. Use the NetworkGateway class directly in tests to simulate each scenario.

Deep Dive: See the full Hybrid Gene Development Guide for Network Gateway reference and RAG pipeline examples.