A multi-agent system that takes a published biology paper and turns it into something you can actually run, either physically (a validated Opentrons script simulated in a cloud sandbox) or computationally (find the repo, spin up its exact environment, try to reproduce the results, return a Reproducibility Score). Built on GPT-5.4 mini, Tavily, Daytona, FastAPI, and a vanilla HTML/JS frontend.
Biology has a reproducibility crisis. A 2016 Nature survey found over 70% of researchers couldn't reproduce others' experiments and over half couldn't reproduce their own, and the US spends roughly $28 billion a year on preclinical research that can't be replicated. LLM agents built to help often make this worse by fabricating real-looking citations and passing blobs of paper text around that lose signal at every hop. Here's how we tried to fix that from the architecture up.
Every value the system produces carries its full origin as a typed FieldLineage chain, so an incubation temperature means exactly where it came from in the paper, what filled it in if the paper didn't state it, what an instrument measured during execution, and what any later revision changed it to and why. The chain renders as a tree.
The LLM can't fabricate citations. Every citation has to resolve to a registered entry whose quoted text is verified against the live source page at startup, and if the LLM keeps trying to cite a fake source the rationale text gets thrown out and replaced with a deterministic fallback rather than preserved with a hallucinated "per Schrader 2012" buried in it.
The system is allowed to refuse to act. When a measurement is ambiguous in a way automation can't responsibly resolve, it exits cleanly and tells the user the next move is manual diagnosis, not another automated guess.
No agent passes raw text to another agent. Every handoff is a typed Pydantic object or a filename pointing into the shared workspace, so context flows as paths and models, never as blobs.
You can optionally let the system iterate: after the script is generated and simulated, a (currently mocked) instrument reads the result and the system decides whether the protocol is good, needs an adjustment, or should be handed back because the issue isn't something automation should fix. Three cycles max. Every revision is preserved in the lineage chain so the final value is traceable to the instrument reading that motivated each step.
Eight agents coordinate through a shared file-based workspace. No raw text passes between agents, only structured JSON and filenames.
| Agent | Role | Tools |
|---|---|---|
| Supervisor (PI) | Orchestrates pipeline, owns state.json | Python only, no LLM |
| Researcher | Web search and scraping | Tavily |
| Methodology | Extracts structured protocols from raw research | GPT-5.4 mini, Pydantic |
| Enricher (PIE) | Fills null critical fields, wet lab only | Tavily, GPT-5.4 mini |
| Coder | Generates and validates executable code | Daytona sandboxes |
| Results Reader | Interprets instrument output into typed lineage records | deterministic Python |
| Replanner | Picks revision action by rule, narrates rationale | GPT-5.4 mini, citation registry |
| Synthesizer | Writes the final markdown report | GPT-5.4 mini |
Wet lab pipeline:
Research Tavily search and scrape -> workspace/raw_research/
v
Methodology LLM extracts protocol JSON -> writes FieldLineage(paper_span)
Pydantic validates per typed field
v
Enricher Notes mining + targeted Tavily -> writes FieldLineage(enricher_fill)
on open-access domains only per filled gap
v
Coder opentrons_simulate in Daytona -> .py script + simulator output
retries on parse/import errors rejects silent no-ops
v
Iteration opt-in, max 3 cycles:
simulate_qpcr_well -> raw CSV + QPCRReading
Results Reader -> FieldLineage(oracle_reading)
Replanner (rule + LLM rationale) -> FieldLineage(replanner_revision)
exits on converged | diagnose_required
v
Synthesizer Markdown report -> includes Field Lineage Summary
when iterations ran
Dry lab pipeline: Research -> Methodology -> Coder -> Synthesizer (no Enricher, no Iteration).
bio-swarm/
├── server.py FastAPI entry, serves web/ and streams agent events over SSE
├── run_cli.py Headless CLI, supports --demo-paper and --enable-iteration
├── main.py Legacy Streamlit UI, kept as fallback
├── web/ Vanilla HTML/CSS/JS frontend
├── agents/ 8 agent modules
├── schemas/
│ ├── opentrons_schema.py
│ ├── dry_lab_schema.py
│ ├── state_schema.py
│ └── lineage_schema.py FieldLineage and four detail models, with validators
├── tools/
│ ├── tavily_tool.py
│ ├── daytona_tool.py
│ ├── file_tool.py
│ ├── token_tracker.py
│ ├── citation_registry.py Registry-keyed citations, three-state startup verification
│ ├── mock_qpcr.py Continuous Cq(template) model with inhibition regime
│ └── lineage_renderer.py Terminal renderer for FieldLineage chains
├── scripts/ One-off migrations
├── tests/ pytest suite (~90 tests; network tests gated by -m network)
└── workspace/ Shared agent memory, auto-created
├── demo_cache/ Committed pre-extracted demo protocols
├── raw_research/
├── extracted_protocols/
├── generated_code/
├── iterations/ Per-iteration raw instrument records
├── lineage/ Field lineage snapshots
└── final_reports/
Python 3.11 or newer.
pip install -r requirements.txt
cp .env.example .envFill the keys in .env: OPENAI_API_KEY, TAVILY_API_KEY, DAYTONA_API_KEY, DAYTONA_API_URL, DAYTONA_TARGET.
Web UI:
python server.py
# open http://127.0.0.1:8000CLI on a real paper:
python run_cli.py --mode wet_lab --input "<paper title or DOI>"Closed-loop demo from the committed cache (skips Research and Methodology):
python run_cli.py --mode wet_lab --input demo --demo-paper rt-qpcr \
--enable-iteration --trace-field "step_1.template_amount_ng"The demo prints the lineage tree for the demo field after the pipeline finishes.
- Research. Tavily searches and scrapes the paper. Raw results go to workspace/raw_research/.
- Extraction. The Methodology agent parses raw text into a Pydantic-validated protocol. Every typed field gets a FieldLineage(paper_span) record citing the source.
- Enrichment, wet lab only. The Enricher mines protocol notes for unstated-but-known values, then runs targeted Tavily searches pinned to open-access domains (PMC, biorxiv, protocols.io). Each fill writes a FieldLineage(enricher_fill) record.
- Coding. The Coder generates an Opentrons script, simulates it in a Daytona sandbox, and retries on parse or import errors. A clean simulation with zero liquid-handling calls is treated as failure (fabricated success).
- Iteration, wet lab, opt-in. A mocked qPCR instrument reads the protocol's template_amount_ng field. The Results Reader interprets the reading into a FieldLineage(oracle_reading) record. The Replanner picks one of converged, reduce_template, increase_template, or diagnose_required by deterministic rule, and the LLM narrates the rationale constrained to registry-keyed citations. Loop exits on converged or diagnose_required, max 3 iterations.
- Synthesis. The Synthesizer reads every workspace artifact and produces the final markdown report, including a Field Lineage Summary section when iterations ran.
Sandbox cleanup. All Daytona sandbox usage is wrapped in try/finally. No leaked billable sandboxes.
Single state owner. Only the Supervisor writes to state.json. Worker agents return structured contract dicts and the Supervisor updates state.
Token tracking. Every LLM call is logged through tools/token_tracker.py. Per-agent prompt and completion totals print at the end of each run.
Workspace is replayable. After a run completes, every intermediate artifact stays on disk (raw research bundles, extracted protocol JSON, enrichment audit log, generated script, simulator stdout, per-iteration raw CSVs, lineage snapshots, final report). You can rerun the Synthesizer alone or inspect any phase by hand.
See LICENSE.