Thanks for your interest in contributing to coop. This document covers how to build the project, run its tests, and submit changes.
coop is a Rust CLI that orchestrates disposable VMs for running agent CLIs — Firecracker microVMs on Linux, Lima on macOS. Because it drives real virtualization backends, some tests only run on a host with the matching backend. The sections below note where that applies.
-
Rust via rustup. The toolchain version is pinned in
rust-toolchain.tomland installed automatically when you build. -
Dev tools at pinned versions — prek (git hooks), taplo (TOML formatting), and cargo-deny (supply-chain checks). Install them with:
./scripts/install-dev-tools.sh # baseline ./scripts/install-dev-tools.sh --all # also cargo-mutants, cargo-fuzz, kani
The script pins the local dev-tool versions; bump a version there rather than installing a floating
latest. CI pins its own copies of taplo and cargo-deny in.github/workflows/ci.yml, so keep those in sync when bumping. -
To run the full integration suite you need a working backend:
- macOS: Apple Silicon with Lima
(
limactlon your PATH). - Linux: x86_64 or arm64 with KVM access (
/dev/kvm),sudo, andcurl,tar,e2fsprogs.
See the README for the full backend requirements.
- macOS: Apple Silicon with Lima
(
Clone the repository and build:
git clone https://github.com/trailofbits/coop
cd coop
cargo build --releaseThe binary lands at target/release/coop.
Install the hooks once, then let them run on every commit:
prek installThe hooks run cargo fmt -- --check, cargo clippy --all-targets --all-features -- -D warnings, cargo test, taplo format --check (TOML formatting, also
enforced by CI), and a set of file checks (trailing whitespace, end-of-file,
YAML, large files, merge conflicts). Run them by hand at any time with:
prek run --all-filesFix every warning before committing. coop has a zero-warnings policy — clippy
runs with -D warnings, so a warning fails the build.
cargo testUnit tests live in the library crate and cover the pure logic: config parsing and validation, workspace sync argument construction, env merging, secret routing, and the helpers the command handlers are built from. Test behavior, not implementation — a test that breaks under a refactor but not a behavior change is testing the wrong thing.
The integration suite exercises the full VM lifecycle (setup → start → status → shell → guest environment → Docker → stop → destroy). It is too slow for the pre-commit hooks, so run it before submitting a change.
Run it on both platforms — macOS/Lima and Linux/Firecracker — because the two backends share an abstraction but exercise different code paths:
# Local (whichever backend this host provides) — builds and runs
./tests/run-integration.sh
# Remote host running the other backend — cross-compiles, copies, and runs
./tests/run-integration.sh --remote user@remote-hostWhen you add a command or a guest-visible change, consider whether it needs a new integration test phase.
coop also carries mutation tests (cargo-mutants), fuzz targets
(cargo-fuzz), and formal proofs (kani). These are manual quality checks,
not required for every change. If you touch a logic-dense module — config
parsing, the JSONC reader, the arithmetic kernels — see
docs/testing.md for when and how to run them.
- Format with
cargo fmt; lint withcargo clippy --all-targets --all-features -- -D warnings. Both are enforced in CI. - Lean on the type system to make illegal states unrepresentable rather than validating at runtime: parse untrusted input into strong types at the boundary, use newtypes over bare primitives that carry an invariant, and use enums for state rather than boolean flags.
- Use
thiserrorfor library error types andanyhowfor application-level errors. Attach context at boundaries, not at every?. - Log with
tracing(error!/warn!/info!/debug!), notprintln!. Tracing output goes to stderr.
docs/code-style.md documents the project's conventions in detail, including the Rust patterns reviewers look for.
- Write commit subjects in the imperative mood, no more than 72 characters ("Add license field to Cargo.toml", not "Added..." or "Adds...").
- Keep each commit to one logical change.
- Work on a feature branch and open a pull request. Never push directly to
main.
- Branch from the latest
main. - Make your change, keeping commits focused.
- Run the pre-commit hooks and the integration suite on both platforms.
- Open a pull request describing what the change does. Describe the code as it stands — not discarded approaches or prior iterations — and use plain, factual language.
- A maintainer will review. Address feedback with follow-up commits.
CI must pass before a pull request can merge. The CI workflow runs:
cargo fmt -- --check— formatting.cargo clippy --all-targets --all-features -- -D warnings— lints.cargo test— unit tests../tests/integration-update.shand./tests/integration-uninstall.sh— the update and uninstall flows.cargo deny check— advisories, licenses, bans, and sources.- zizmor — GitHub Actions security audit.
The full two-platform lifecycle suite is not run in CI — run it locally, as described above.
Open an issue on the issue tracker.
For bug reports, include the platform and backend, the command you ran, and the
output (coop's tracing output goes to stderr — RUST_LOG=debug adds detail).
If you believe you have found a security vulnerability, do not open a public issue. Follow the private reporting process in SECURITY.md.
coop is licensed under the Apache License 2.0. By contributing, you agree that your contributions will be licensed under the same terms.