# crucible

> Sandbox runtime for AI coding agents. Firecracker microVMs, a single Go binary, snapshot/fork as first-class primitives.

![Status: v0.3.4](https://img.shields.io/badge/status-v0.3.4-orange)
![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue)
![Core: Go](https://img.shields.io/badge/core-Go-00ADD8)

![crucible: run nginx in a microVM, push content in, snapshot the running server, fork it onto its own port, and watch the two VMs serve different answers](/demo/landing.gif)

<p align="center"><em>Boot nginx in a microVM with <code>docker run</code> ergonomics, <code>cp</code> content in, then snapshot the <strong>running</strong> server and fork it onto its own port: two VMs, two answers, zero interference (<a href="docs/cli.md">CLI</a> · <a href="docs/architecture.md">how fork works</a>).</em></p>

AI coding agents write code and want to run it: check it compiles, run the tests they just wrote, try three approaches in parallel. Today's options are all wrong in different ways: raw Docker (shared kernel, weak isolation, no fork), hosted sandbox services (lock-in, usage-priced), or rolling your own Firecracker stack (months of operational work).

**crucible is the fourth option:** a single self-hosted Go binary on top of Firecracker, with snapshot/fork as first-class primitives and observability baked in, tuned for running AI-generated code. **Think of it as a safe `docker run` for code you don't trust:** a real guest kernel (a container escape is a VM escape), **default-deny egress**, and one-command **fork** to explore approaches in parallel.

```bash
crucible run nginx:alpine -p 8080:80     # boot an unmodified image, publish a port
crucible cp ./script.py <id>:/work        # drop local code in, no image build, no Dockerfile
crucible shell <id>                        # a real /bin/sh inside it (cd/env persist)
```

Full motivation and design: [docs/VISION.md](/vision).

> **Ephemeral by design.** A daemon restart does **not** resurrect running sandboxes (their registry records and durable logs persist; the live VMs do not). That is exactly the right contract for "run a sketchy repo, test it, tear it down." Durable, self-healing long-lived workloads are v0.4.

## Highlights

* **Real isolation, not containers.** Every sandbox is a Firecracker microVM under [jailer](https://github.com/firecracker-microvm/firecracker/blob/main/docs/jailer.md): its own chroot, mount/PID namespaces, a dropped uid, and cgroup v2 quotas. Not a shared kernel.
* **Snapshot & fork as primitives.** Run setup once, snapshot the warm state, fork *N* parallel children from it. Forks restore with **lazy `userfaultfd` memory**: guest RAM is served from the snapshot on demand, never byte-copied per fork (the AWS Lambda SnapStart technique).
* **Clone-safety.** Each fork wakes with a fresh kernel RNG seed, a rotated `machine-id`, and its own hostname, ordered *before* the fork is reachable, so no two forks silently share UUIDs, secrets, or entropy.
* **Default-deny networking.** No egress unless you allowlist hostnames, and only those; resolved IPs are range-filtered so a guest can't SSRF cloud metadata or private ranges. Enforced in host nftables + a DNS proxy the guest is forced through.
* **Three ways to drive it.** A [CLI](/cli), a live [TUI dashboard](/tui), and an [MCP server](/mcp): all thin clients over one REST API, so they can't drift.
* **Scoped tokens.** Bind an API key to a policy the daemon enforces (allowed operations, egress ceiling, profile allowlist, resource caps, expiry). See [docs/policy.md](/policy).
* **Observability.** Per-exec structured results (exit code, wall-clock, and CPU/memory/I/O usage), durable per-sandbox logs, and a Prometheus `/metrics` endpoint.
* **Self-hosted, single binary.** Daemon and CLI are one Go binary. No cloud, no account, no telemetry.

> **Maturity.** crucible is pre-1.0 and **not yet hardened for production or untrusted multi-tenant use.** The daemon binds loopback by default, with optional bearer-key auth (required, plus TLS, to bind a non-loopback address). See [SECURITY.md](https://github.com/gnana997/crucible/blob/main/SECURITY.md) for the exact isolation model and its limits.

## Quick start

crucible is a **Linux daemon** (it needs KVM + Firecracker) plus a **cross-platform client**: the CLI, TUI, and `crucible mcp serve` are thin HTTP clients, so they run on macOS and Windows too, driving a remote Linux daemon.

**Linux (a host where `ls /dev/kvm` works)**: one command fetches and checksum-verifies firecracker + jailer, a guest kernel, and a rootfs, then starts the service:

```bash
curl -fsSL https://raw.githubusercontent.com/gnana997/crucible/main/install.sh | sudo bash -s -- --with-deps --enable
```

Then run your first sandbox:

```bash
crucible run nginx:alpine -p 8080:80 && curl localhost:8080   # boots + serves
crucible tui                                                  # live dashboard
```

**macOS / Windows**: install just the client (no root, no VM) and point it at a Linux daemon:

```bash
curl -fsSL https://raw.githubusercontent.com/gnana997/crucible/main/install.sh | sh -s -- --client --addr https://YOUR-LINUX-HOST:7878 --token <key>
```

The daemon installer's `--connect-token` mints a scoped key and prints that exact client line plus an MCP snippet to paste into Claude Code / Cursor. Manual setup and the platform matrix are in [`install.sh --help`](https://github.com/gnana997/crucible/blob/main/install.sh) and [docs/cli.md](/cli); building from source is in [CONTRIBUTING.md](https://github.com/gnana997/crucible/blob/main/CONTRIBUTING.md).

## Usage

Daemon-authoritative: the CLI, TUI, and MCP server are thin clients over one REST API; point them with `--addr` (or `CRUCIBLE_ADDR`; default `127.0.0.1:7878`) and `--token`.

```bash
crucible run nginx:alpine -p 8080:80              # boot an OCI image, publish a port (long-lived)
crucible build -t myapp . && crucible run myapp   # a repo's Dockerfile → running, in two lines
crucible cp ./app.py <id>:/work                   # drop local code in, no image build
crucible shell <id>                               # interactive shell inside it (no PTY)
SBX=$(crucible run --profile python-3.12)
crucible snapshot create $SBX | xargs crucible fork --count 5   # explore 5 approaches in parallel
```

* **CLI**: the full reference (all commands, flags, exit codes) is in [docs/cli.md](/cli). Add `-o json` to any command for machine-readable output.
* **TUI**: `crucible tui` opens a live dashboard: running sandboxes, the fork tree, streaming `exec`, and a k9s-style logs view, all gated on the token's scope. [docs/tui.md](/tui).
* **MCP**: `crucible mcp serve` exposes crucible to any [MCP](https://modelcontextprotocol.io) agent (Claude Code, Cursor, …) as native tools (create, run, exec, snapshot, fork, `write_files`, `read_file`, and more) with operator guardrails. [docs/mcp.md](/mcp).
* **REST**: everything above is the daemon's HTTP API: endpoints, the exec frame protocol, and error codes are in [docs/api.md](/api).

## How it works

A single Go binary is both the daemon and the CLI; each guest runs a small vsock agent. The daemon boots Firecracker microVMs under jailer, runs commands over vsock with streamed output, captures snapshots, and forks end-to-end, each fork restored with lazy `userfaultfd` memory, its own netns + DHCP-assigned IP behind a default-deny allowlist, and a per-fork identity refresh. A durable registry is journaled and reconciled on restart (orphaned VMs / netns / nft rules are reaped). Full walkthrough: [docs/architecture.md](/architecture); networking has its own [design doc](/network).

## Performance

Measured on one 24-core box, 512 MiB sandboxes. The `--work-base` filesystem is the biggest lever: reflink (btrfs/XFS) makes fork's rootfs clone O(1), while **ext4 has no reflink, so it byte-copies**, so here's both. Full methodology in [docs/benchmarks.md](/benchmarks):

|                          | ext4 (common default) | btrfs / XFS (reflink)            |
| ------------------------ | --------------------- | -------------------------------- |
| Fork (warm → child)      | \~690 ms              | **\~207 ms**                     |
| Fork throughput (64-way) | 3.7/s                 | **45/s**                         |
| 128 forks, host RAM      | 4.9 GB                | **1.2 GB** (vs 64 GB naïve copy) |
| Exec roundtrip           | \~3 ms                | \~3 ms                           |

Fork is **\~9× faster than a cold boot** either way, and we ran **512 concurrent microVMs** on the laptop (reflink, RAM-bound).

> **By the numbers:** one static binary · no guest RAM copied per fork · 3 interfaces (CLI · TUI · MCP) · 15 MCP tools · 8 prebuilt profiles · 512 MB / 1 vCPU / 60 s safe defaults

## Roadmap

* **v0.3.2** (current): **the safe `docker run` for untrusted/AI code**: OCI image boot + `crucible build`, **`crucible cp`** + MCP `write_files`/`read_file` (drop code in and run it, no Dockerfile), an interactive `crucible shell`, a **TUI logs view**, `--disk` sizing, top-level `stop`/`rm`, and durable logs. Sandboxes are ephemeral (durability is v0.4).
* **v0.4** (planned): durable, self-healing long-lived workloads that survive a daemon restart, plus a PTY for full-terminal interactive sessions.

Full shipped-vs-planned capability matrix: [docs/ROADMAP.md](/roadmap).

## Security

crucible runs untrusted code, so isolation is a core property, but it is **pre-1.0 and not yet hardened for production or untrusted multi-tenant use.** The daemon binds loopback by default and ships with optional bearer-key auth (required, with TLS, to bind a non-loopback address). See [SECURITY.md](https://github.com/gnana997/crucible/blob/main/SECURITY.md) for the isolation model, current caveats, and how to report a vulnerability.

## Contributing

Early days: the API is stabilizing. If you're building a coding agent and want crucible to fit your workflow, open an issue describing it; concrete use cases shape priorities more than wishlists. Build/test setup, style, and PR guidelines are in [CONTRIBUTING.md](https://github.com/gnana997/crucible/blob/main/CONTRIBUTING.md); the codebase walk-through is in [docs/architecture.md](/architecture). By participating you agree to the [Code of Conduct](https://github.com/gnana997/crucible/blob/main/CODE_OF_CONDUCT.md).

## License

Apache License 2.0. See [LICENSE](https://github.com/gnana997/crucible/blob/main/LICENSE).
