Write your first reproduction

// GUIDE · WRITE A RECIPE

Author one Layer 1 recipe that runs in a browser tab.

Copy the nearest existing recipe, swap the slug and the reproduction script, and you have a Vivarium-compatible reproduction page running locally. From there, the choice is upstream PR or self-publish on a fork.

// 0 · WHAT THIS GUIDE COVERS

A minimal Layer 1 (in-browser WASM) recipe.

Layer 1 pulls the upstream code into the browser via Pyodide, Ruby.wasm, php-wasm, or the Rust wasm32-wasip1 target, runs the reproduction script the moment the page loads, and returns a verdict. No install, no server — so a Layer 1 recipe is the lowest-cost first contribution to Vivarium.

Layer 2 (Docker) and Layer 3 (record-replay) have their own niches in architecture, but if you are writing your first recipe, start at Layer 1.

This guide ends at "I have a working recipe locally." Where to publish it is a separate two-way fork: Run on your own fork or a PR to the upstream repo.

// 1 · PICK A BUG

Slug shape: <project>-<issue>.

If the upstream bug has an issue tracker entry, the slug is <project>-<issue> (e.g. pandas-56679). Otherwise, a descriptive kebab-case string (e.g. bash-local-shadows-exit) works. The slug is the directory name and also the Manifest v1 slug field.

A Layer 1-friendly bug usually has these properties:

  • No external I/O (no network, no filesystem, no subprocess).
  • The verdict collapses to one boolean (e.g. "are these two values equal?").
  • The required libraries ship inside the WASM build of the runtime (Pyodide / Ruby.wasm / php-wasm bundled package list).

// 2 · CLONE AND COPY A TEMPLATE

Copying the closest existing recipe is the shortest path.

01
Pick a clone target.

For an upstream PR, clone aletheia-works/vivarium directly. To self-publish, follow the fork guide and clone your fork.

02
Set up the toolchain via mise.

mise install at the repo root pulls bun and the rest. For Layer 1 specifically, also run bun install from src/layer1_wasm/.

03
Copy the closest existing recipe.

Pick one in the same language: Python → src/layer1_wasm/pandas-56679/, Ruby → ruby-21709/, PHP → php-12167/, Rust → regex-779/. Copy the whole directory: cp -r src/layer1_wasm/pandas-56679 src/layer1_wasm/<your-slug>.

// 3 · EDIT INDEX.HTML

Three swaps: title, heading, lede.

Keep the structure of the copied index.html; replace these three to match the bug:

  • <title> — tab title (e.g. Vivarium · Reproducing <project>#<issue>).
  • <h1> — heading. Link to the upstream issue.
  • <p class="lede"> — one-to-two-sentence summary.

Do not touch: <meta name="vivarium-contract" content="v1">, the #verdict element, or the link to ../_shared/style.css. Those carry Contract v1, and removing any of them breaks machine-readable verdict capture.

The authoritative spec lives at Contract v1. The allowed values for data-verdict and the schema for the VIVARIUM_RESULT envelope come from there.

// 4 · EDIT REPRO.TS

Swap the reproduction logic; keep the helpers.

The copied repro.ts imports from ../_shared/loader.js and ../_shared/verdict.js. Leave that frame alone and swap only the reproduction body. For a Python recipe:

const REPRO_CODE = `
import pandas as pd
# ↑ minimum code that exercises the upstream bug
result = {
"left": ...,   # value 1
"right": ...,  # value 2
"mismatch": <left> != <right>,
}
result
`.trim();

The helpers handle the verdict wiring: if the comparison is true (bug reproduced), call setVerdict("reproduced", "..."); otherwise setVerdict("unreproduced", "..."). setResult({ contract: "v1", bug, runtime, result, timing }) writes the VIVARIUM_RESULT envelope.

// 5 · RUN IT LOCALLY

bun run build → static server → open in browser.

# from src/layer1_wasm/
bun install
bun run build         # repro.ts → repro.js

# serve the recipe directory directly
python -m http.server -d src/layer1_wasm/<your-slug> 8765
# open http://localhost:8765/ in a browser

The page loads, the upstream library streams in from the CDN, and the verdict badge moves from pending to either reproduced or unreproduced. In DevTools, check that VIVARIUM_VERDICT and VIVARIUM_RESULT agree with the badge — that's the Contract v1 surface.

Pyodide / Ruby.wasm / php-wasm are all configured to avoid SharedArrayBuffer, so COOP/COEP headers are not required. A plain static server (python -m http.server, bunx serve) is enough.

// 6 · NATIVE RE-VERIFICATION (OPTIONAL)

Show the same bug reproduces outside the browser.

A reproduction observed via Pyodide / Ruby.wasm is more convincing when it also reproduces on a native CPython / MRI Ruby. For Python, uv + PEP 723 inline metadata gives you a single-file companion:

# /// script
# requires-python = ">=3.13"
# dependencies = ["pandas==2.3.3"]
# ///
import pandas as pd
# … same reproduction logic …

mise exec uv -- uv run repro.py spins up an ephemeral venv and runs it. Ruby, PHP, and Rust use the same pattern — existing recipes' repro.rb / repro.php / Cargo.toml are good starting points.

// 7 · REGENERATE recipes.json

recipes.json, which the gallery (/repro/) and the MCP server consume, is generated from src/layer*/ at build time. To regenerate locally:

mise run recipes:index
# or
cd docs && bun run generate-index

Check that docs/public/api/recipes.json contains your slug. With the docs site running locally, the new recipe shows up as a card on the gallery:

cd docs && bun run dev
# http://localhost:3000/vivarium/en/repro/

// 8 · WHERE TO PUBLISH

Upstream PR, fork, or consumer integration.

01
Contribute to the upstream catalogue.

Open a PR on aletheia-works/vivarium with a Conventional Commit such as feat(layer1): add <slug> reproduction. The CI gate is essentially Contract v1 conformance — anything that turns green is in.

02
Self-publish on your fork.

Push the recipe to your own vivarium fork's main, enable deploy via the fork guide, and the recipe appears at <you>.github.io/vivarium/. Sharing is just sharing the URL.

03
Track an existing recipe from your project repo.

If you only want to watch a recipe (no new authoring), you don't need this guide — see Integrate with your own repo, which uses Manifest v1 plus the reusable consumer-workflow.

If a step in this guide fails, that's a bug in this guide — file an issue with the step number. Where readers stall sets the priority for the next docs revision.

// NEXT

Read the glossary

The terms used here — slug, verdict, contract, layer — are pinned to fixed meanings in the glossary.

VIVARIUM IS PART OF ALETHEIA-WORKS · SEE SOURCE ON GITHUB →