Hypotheses, designed experiments, evidence and conclusions as durable state — built for researchers, applications and AI agents across any empirical domain.
Two projects, side by side. Each folder is self-contained and usable on its own.
ml-scientist/ the five ml-* MCP servers (agora, arete, zetesis,
episteme, anamnesis) — run them anywhere, no VM needed
ml-labloop/ the KVM isolation harness — builds a per-user VM where
those services run in a trusted zone, sealed off from
a hostile experiment zone
No VM required. From ml-scientist/:
uv sync --frozen
./labloop start all # or ./run-ml-episteme.sh for a single server
./labloop statusPorts are defined in ports.env (38050/38060/38070/38080/38090).
Requires KVM/libvirt on a Linux host. From ml-labloop/:
./00-build-lab-template.sh # Mint ISO -> unattended build
./01-prepare-template-for-cloning.sh # seal
./02-create-vm-from-template.sh <user> # clone -> lab-vm-<user>A locally built VM boots with login lab / password lab — change it
after first login.
create-lab-template finds the sibling ../ml-scientist checkout
automatically (override with ML_SCIENTIST=). See ml-labloop/README.md
for the full workflow including publishing VM images.
Released VM images live in the public Hugging Face bucket: https://huggingface.co/buckets/cloudcell/LabLoop
Download an image plus its checksum, verify it, then import it with
the script in ml-labloop/release-package/:
hf buckets cp hf://buckets/cloudcell/LabLoop/<image>.qcow2 .
hf buckets cp hf://buckets/cloudcell/LabLoop/<image>.qcow2.sha256 .
sha256sum -c <image>.qcow2.sha256
./import-lab-vm.sh <image>.qcow2 <vm-name>Published images are sealed — unlike a local build, the lab/exp
passwords are locked and the import generates a one-time password for
first login. See ml-labloop/release-package/import-lab-vm.sh for
details.
Beyond the build/clone pipeline (00/01/02), the numbered
scripts cover the day-to-day operations on a running lab VM. All
data movement is host-initiated over the qemu guest agent — no
SSH, no virtiofs, no guest→host sockets.
Seals a template for publication (locks lab/exp passwords,
sysprep, snapshot), flattens + compresses the qcow2, checksums it,
and uploads the pair to the private trials bucket
(hf://buckets/LabLoopCommunity/lab-trials). Asks before publishing.
Promotes a qcow2 + .sha256 from the trials bucket to the public
release bucket (hf://buckets/cloudcell/LabLoop). Shows a numbered
menu when no name is given; verifies both objects landed before
removing them from the source (--keep copies instead of moving).
Idempotent in-place upgrade for VMs built before a tooling change.
Pushes the current deploy/ payload into the guest — wrappers
(labloop-exec, labloop-export, labloop-build,
labloop-update-opencode), sudoers, quadlets, opencode config,
readiness check, agent guides — and restarts quadlets only when
their definition changed. New templates bake all of this in; this
script is the upgrade path for clones that already exist.
The sanctioned push channel. Lands a file or directory on the
guest at /srv/lab/incoming/<batch>-<UTC-ts>/, mounted read-only
into the hostile zone at /incoming. Batches are immutable —
re-ingesting creates a new timestamped batch, never overwrites.
Convention: stage inputs under ./incoming/ on the host first.
The sanctioned pull channel. Reads the manifest + tarball produced
in-guest by labloop-export (run sudo labloop-export --all, or a
path-scoped export, inside the VM first), verifies the tarball's
sha256 against the manifest, and lands
<vm>-extraction-<UTC-ts>.tar.gz (mode 0600) plus its checksum in
./extracted/. The tarball is never opened on the host — it is
untrusted, sensitive content; extract it deliberately yourself.
Templates and tooling were developed and tested on:
virsh -c qemu:///system start lab-vm-<name> # or: virt-manager GUIThe desktop autologin opens with a password prompt (local clones:
lab/lab → forced change at first login; published images: the OTP
the importer printed). MCP services and containers start themselves
via quadlets — nothing to launch by hand. The desktop carries
launchers for the dashboard (:38051), VSCodium and opencode, plus
check-lab-ready.sh for a full readiness + security pass.
Every LLM driver has its own peculiarities — how it sequences tool calls, whether it reaches for a shell before an MCP tool, which assumptions it makes about paths and zones. The warmup exists to surface those quirks on a small, safe problem before they can contaminate real work — and to produce a reusable note about them.
GENESIS-RESEARCH-PROMPT.md is that warmup — it ships in
~/workspace/, so the driver agent should discover and follow it on
its own: just prompt "do a warmup run". (Only if it doesn't pick
it up, paste the file contents directly — the file lives at
~/GENESIS-RESEARCH-PROMPT.md.) It runs one complete scientific
cycle end-to-end — a smoke test of the entire apparatus, not just
connectivity — on an A/B question that finishes in minutes:
programme → falsifiable hypothesis → ≥2 recorded trials →
observations → belief update → tournament → verdict → claim →
staged artifacts.
Two outputs matter beyond the verdict:
- It proves every stage works — execution, recording, memory, tournament — before you trust the lab with a real programme.
- It ends by having the agent write <name>-ONBOARDING.mdinto~/workspace/— agent-authored notes on how to drive this system: the call order that mattered, what bit it, the gotchas. Later sessions (of the same model or another) read it first, so each model's quirks get learned once, not re-discovered per run.
If the model changes, run the warmup again — a new driver means new peculiarities and a new note.
The common failure mode: the agent falls back to writing files and
running python directly — iterating in a shell instead of
recording through the tools. Runs done that way are telemetry, not
science: nothing is registered, nothing is reproducible, nothing
moves a belief. Course corrections that work:
- Start every session with state rather than intent. Ask it to call
lab://status(or the server'sstatus_reportprompt) first. An agent that has read the live state knows the tools exist; one that hasn't improvises.
- Name the two planes explicitly. design_experiment→capture_bundle→run_trialis recorded evidence;labloop-execis for iteration and debugging only. If it wants a quick syntax check,labloop-exec— if it wants a result that counts, the episteme executor.
- Point at the workspace AGENTS.md. It links toAGENT-LAB-GUIDE.md— the zone map and the sanctioned lanes — plus the agent's own*-ONBOARDING.mdif a previous run left one.
- Cite the hard rules. "Never run experiment code as lab" and "code_refmust be staged under/exchangebeforecapture_bundle" resolve most drift — the agent usually wanders because a path or permission surprised it, not because it prefers the shell.
- Re-anchor on the record. If it produces results outside the tools, ask: "which trial id is that under?" — the absence of an id is the cue that it left the loop.