Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ coverage/
.idea/
agent/.zig-cache/
agent/zig-out/
vmm/.zig-cache/
vmm/zig-out/
36 changes: 21 additions & 15 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## System Overview

Hearth provides local Firecracker microVM sandboxes for AI agent development. The architecture has three main layers:
Hearth provides local microVM sandboxes for AI agent development, powered by Flint (a custom Zig VMM). The architecture has three main layers:

```
┌─────────────────────────────────────────────┐
Expand All @@ -13,8 +13,8 @@ Hearth provides local Firecracker microVM sandboxes for AI agent development. Th
│ Direct (in-process) │ Daemon (UDS / WebSocket)
├──────────┬──────────┬───────────────────────┤
│ VM Layer │ Network │ Storage │
firecracker│ TAP/NAT │ rootfs + overlays │
jailer │ port fwd │ snapshots (CoW) │
flint │ TAP/NAT │ rootfs + overlays
(Zig VMM)│ port fwd │ snapshots (CoW) │
└──────────┴──────────┴───────────────────────┘
Linux host (KVM + user namespaces)
```
Expand All @@ -24,11 +24,17 @@ Hearth provides local Firecracker microVM sandboxes for AI agent development. Th
### `src/sandbox/`
The user-facing API. A `Sandbox` represents a running microVM with methods for exec, filesystem access, port forwarding, and lifecycle management. This is the only module that external consumers import.

### `vmm/` (Zig — Flint VMM)
Custom KVM-based microVMM written in Zig. Built from source during `hearth setup`. Handles:
- KVM VM lifecycle (boot, pause, resume, snapshot/restore)
- VirtIO device emulation (block, net, vsock)
- Built-in jail (mount namespace, pivot_root, cgroups, seccomp)
- Pre-boot REST API for configuration, post-boot API for control

### `src/vm/`
Manages Firecracker processes. Handles:
- Spawning `firecracker` with the correct config
- Jailer setup for unprivileged isolation
- Machine configuration (vCPUs, memory, drives)
TypeScript layer for VMM interaction. Handles:
- Spawning `flint` with the correct config
- Machine configuration (memory, drives)
- Graceful shutdown and kill

### `src/snapshot/`
Expand All @@ -47,9 +53,9 @@ Networking stack:
- Optional network isolation (no outbound)

### `agent/` (Zig — separate from TypeScript SDK)
Guest agent binary that runs inside the VM. Written in Zig, zero-allocation, ported from flint's agent. Three vsock listeners:
Guest agent binary that runs inside the VM. Written in Zig, zero-allocation. Four vsock listeners:
- **Port 1024** (control): exec, writeFile, readFile, ping, interactive spawn. Length-prefixed JSON protocol. Single-threaded, reconnects on snapshot restore.
- **Port 1025** (forward): TCP port forwarding. Host initiates via Firecracker CONNECT protocol. Agent dials guest localhost, relays bidirectionally via poll(). Fork-per-connection.
- **Port 1025** (forward): TCP port forwarding. Host initiates via vsock CONNECT protocol. Agent dials guest localhost, relays bidirectionally via poll(). Fork-per-connection.
- **Port 1026** (transfer): Tar streaming upload/download. Host initiates via CONNECT. Agent fork+exec's busybox tar with vsock fd redirected to stdin/stdout.
- **Port 1027** (proxy): HTTP CONNECT proxy bridge. Guest TCP listener at 127.0.0.1:3128 relays to host-side proxy over vsock for internet access.

Expand All @@ -60,12 +66,12 @@ The control channel (port 1024) supports an interactive shell mode used by `hear
- **PTY → host**: agent reads PTY master output, sends `{"type":"stdout","data":"<base64>"}` messages
- **host → PTY**: agent reads `{"type":"stdin","data":"<base64>"}` and `{"type":"resize","cols":N,"rows":N}` messages, writes decoded data to PTY master

**vsock POLLIN workaround**: Firecracker's virtio-vsock doesn't reliably trigger `POLLIN` in the guest kernel. The agent sets the vsock socket to `O_NONBLOCK` and tries a non-blocking read every 50ms poll iteration instead of relying on `poll()` to detect incoming host data.
**vsock POLLIN workaround**: The virtio-vsock device doesn't reliably trigger `POLLIN` in the guest kernel. The agent sets the vsock socket to `O_NONBLOCK` and tries a non-blocking read every 50ms poll iteration instead of relying on `poll()` to detect incoming host data.

### `src/agent/` (TypeScript — host-side client)
Host-side client that talks to the guest agent:
- Control channel: length-prefixed JSON requests over vsock UDS (guest-initiated connection)
- Port forwarding + transfers: Firecracker CONNECT protocol (host-initiated via `vsockConnect` helper)
- Port forwarding + transfers: vsock CONNECT protocol (host-initiated via `vsockConnect` helper)

### `src/daemon/`
Daemon server and client for multi-process and remote access:
Expand Down Expand Up @@ -96,10 +102,10 @@ SDK client (TypeScript)
Backend (in-process, or daemon via UDS / WebSocket)
│ 1. Clone rootfs overlay (cp --reflink=auto)
│ 2. Spawn firecracker + configure via REST API
│ 2. Spawn flint VMM (CLI restore or API config)
│ 3. Configure networking (TAP, NAT)
Firecracker microVM
Flint microVM
│ Boots in <150ms
│ Guest agent on vsock
Expand All @@ -113,9 +119,9 @@ Guest Linux (minimal rootfs)
See `docs/design-docs/core-beliefs.md` for principles. Notable decisions:

1. **Local-only**: No cloud dependency. Everything runs on the developer's machine.
2. **Stock Firecracker**: Upstream binary, auto-downloaded. Not containers, not a custom VMM.
2. **Custom VMM (Flint)**: Zig-based KVM VMM built from source. Not containers, not a third-party binary.
3. **Snapshot-first**: Fast clone from snapshots is the primary creation path.
4. **vsock for guest communication**: No network dependency for control plane.
5. **In-process or daemon**: `Sandbox` manages VMs in-process. `DaemonClient` connects via UDS (local) or WebSocket (remote) for multi-process and macOS access. `hearth shell` auto-starts the daemon if needed.
6. **Zig guest agent**: Zero-allocation, <1MB binary, ported from flint. Internal component — users never touch it.
6. **Zig guest agent**: Zero-allocation, <1MB binary. Internal component — users never touch it.
7. **Observability-first**: Every sandbox gets logs + metrics via Vector (guest) → Victoria (host). Agents query via SDK, not manual tooling. This is the key differentiator vs E2B.
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@

## What is Hearth?

Local-first Firecracker microVM sandboxes for AI agent development. Think E2B, but runs entirely on your machine. Agents get isolated Linux VMs they can boot, snapshot, exec into, and tear down in milliseconds.
Local-first microVM sandboxes for AI agent development. Think E2B, but runs entirely on your machine. Agents get isolated Linux VMs they can boot, snapshot, exec into, and tear down in milliseconds.

## Tech Stack

- **Language**: TypeScript (strict mode, ESM)
- **Runtime**: Node.js 20+
- **Build**: tsc
- **Test**: vitest
- **Underlying VM**: Firecracker microVMs via `/dev/kvm`
- **Underlying VM**: Flint (custom Zig VMM) via `/dev/kvm`

## Architecture

See `ARCHITECTURE.md` for the full system map. Key layers:

- `src/vm/` — Firecracker process lifecycle, VM configuration, jailer
- `src/vm/` — VMM interaction, API client, snapshot management
- `vmm/` — Flint VMM source (Zig), built during setup
- `src/snapshot/` — Copy-on-write snapshots, restore
- `src/network/` — TAP device management, port forwarding
- `src/agent/` — Guest agent protocol (vsock-based)
Expand Down
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# hearth

Local-first Firecracker microVM sandboxes for AI agent development. Think [E2B](https://e2b.dev), but runs entirely on your machine.
Local-first microVM sandboxes for AI agent development. Think [E2B](https://e2b.dev), but runs entirely on your machine.

```typescript
import { Sandbox } from "hearth";
Expand All @@ -11,15 +11,15 @@ console.log(result.stdout); // "hello\n"
await sandbox.destroy(); // cleanup: kill VM, delete overlay
```

Each sandbox is a real Firecracker microVM with its own Linux kernel. Not a container, not a namespace hack. An agent that `rm -rf /` inside a sandbox destroys nothing on your host.
Each sandbox is a real KVM microVM (powered by Flint, a custom Zig VMM) with its own Linux kernel. Not a container, not a namespace hack. An agent that `rm -rf /` inside a sandbox destroys nothing on your host.

## Why

Cloud sandboxes (E2B, Daytona, Modal) add latency, cost, and a dependency on someone else's uptime. AI agents spin up hundreds of sandboxes in a session. That feedback loop should be sub-second and free.

| | E2B | Daytona | hearth |
|---|---|---|---|
| Isolation | Firecracker | Docker | Firecracker |
| Isolation | Firecracker | Docker | KVM (Flint) |
| Create | ~150ms | ~90ms | ~135ms |
| Exec latency | Network RTT | Network RTT | ~2ms |
| Cost | $0.05/vCPU-hr | $0.067/hr | Free |
Expand All @@ -28,10 +28,10 @@ Cloud sandboxes (E2B, Daytona, Modal) add latency, cost, and a dependency on som

## How it works

1. **First `Sandbox.create()`**: Boots a fresh Firecracker VM, waits for the guest agent, pauses, captures a snapshot (vmstate + memory + rootfs). Takes ~1s one time.
2. **Every subsequent create**: Copies the snapshot files (reflink on btrfs/XFS = instant), restores Firecracker from snapshot, agent reconnects over vsock. ~135ms.
1. **First `Sandbox.create()`**: Boots a fresh VM via the Flint VMM, waits for the guest agent, pauses, captures a snapshot (vmstate + memory + rootfs). Takes ~1s one time.
2. **Every subsequent create**: Copies the snapshot files (reflink on btrfs/XFS = instant), restores from snapshot via Flint CLI, agent reconnects over vsock. ~135ms.
3. **`exec()`**: Sends a command to the Zig guest agent over virtio-vsock. Agent forks `/bin/sh -c <cmd>`, captures stdout/stderr, returns base64-encoded result. ~2ms round-trip.
4. **`destroy()`**: Kills the Firecracker process, deletes the run directory. Process exit handler catches orphans.
4. **`destroy()`**: Kills the Flint process, deletes the run directory. Process exit handler catches orphans.

## Requirements

Expand All @@ -53,7 +53,7 @@ wsl --install && wsl # one-time
npx hearth setup
```

**macOS** — use a remote Linux host. Firecracker requires KVM which is not available on macOS. Connect to a Linux server running `hearth daemon` over WebSocket:
**macOS** — use a remote Linux host. Flint requires KVM which is not available on macOS. Connect to a Linux server running `hearth daemon` over WebSocket:

```bash
# On your Linux server
Expand Down Expand Up @@ -82,7 +82,7 @@ Works over any network where the Mac can reach the server (ZeroTier, Tailscale,
npx hearth setup
```

Downloads Firecracker v1.15.0, guest kernel, prebuilt agent binary, builds an Ubuntu rootfs via Docker, and captures a base snapshot. Takes ~1-2 minutes on first run, idempotent after that.
Builds the Flint VMM from source (requires Zig 0.16+), downloads a guest kernel, builds the agent binary, creates an Ubuntu rootfs via Docker, and captures a base snapshot. Takes ~1-2 minutes on first run, idempotent after that.

## Environments

Expand Down Expand Up @@ -397,7 +397,7 @@ Environment.remove("my-api");
## Architecture

```
Host Guest (Firecracker microVM)
Host Guest (Flint microVM)
┌──────────────────────┐ ┌──────────────────────┐
│ TypeScript SDK │ │ hearth-agent (Zig) │
│ Sandbox.create() │ control (1024) │ - exec via fork/sh │
Expand All @@ -407,7 +407,7 @@ Host Guest (Firecracker microVM)
│ sandbox.forwardPort()│◄──── vsock ─────│ - reconnect on │
│ sandbox.destroy() │ proxy (1027) │ snapshot restore │
│ │ │ - HTTP proxy bridge │
Firecracker API │ │ │
Flint VMM API │ │ │
│ Snapshot manager │ │ Linux kernel 6.1 │
│ Process lifecycle │ │ Ubuntu 24.04 rootfs │
│ (or Daemon client) │ │ Node.js 22 │
Expand All @@ -431,14 +431,20 @@ src/ TypeScript SDK
cli/build.ts `hearth build` — build environment from Hearthfile
cli/envs.ts `hearth envs` — list, inspect, remove environments
network/proxy.ts HTTP CONNECT proxy for internet access over vsock
vm/api.ts Firecracker REST API client
vm/api.ts Flint VMM REST API client
vm/snapshot.ts Base snapshot creation and management
vm/binary.ts Binary/image path resolution
cli/setup.ts `npx hearth setup` — downloads and configures everything
cli/download.ts HTTP download with progress and redirect handling
errors.ts Typed error hierarchy
util.ts Shared utilities (encodeMessage, parseFrames, etc.)

vmm/ Flint VMM (custom Zig microVMM)
src/main.zig KVM VM lifecycle, CLI arg parsing, run loop
src/api.zig REST API server (pre-boot config, post-boot control)
src/snapshot.zig VM snapshot save/restore
build.zig Build configuration

agent/ Zig guest agent (runs inside VM)
src/main.zig vsock control server, exec, file I/O, port forward relay
build.zig Cross-compile for x86_64-linux and aarch64-linux
Expand Down Expand Up @@ -537,7 +543,7 @@ See [examples/claude-in-sandbox.ts](examples/claude-in-sandbox.ts) for a complet

Each sandbox is configured with 2 GB of guest memory by default — enough to run `pnpm install`, compile TypeScript, and handle typical AI agent workloads without OOM kills.

Hearth uses **KSM (Kernel Same-page Merging)** to keep actual host memory usage low. Sandboxes restored from the same snapshot share nearly identical memory pages. KSM runs in the kernel and transparently deduplicates these pages across all Firecracker processes. In practice, the unique memory per sandbox is typically 200–500 MB, so running 10 sandboxes costs ~4 GB of host RAM instead of 20 GB.
Hearth uses **KSM (Kernel Same-page Merging)** to keep actual host memory usage low. Sandboxes restored from the same snapshot share nearly identical memory pages. KSM runs in the kernel and transparently deduplicates these pages across all Flint processes. In practice, the unique memory per sandbox is typically 200–500 MB, so running 10 sandboxes costs ~4 GB of host RAM instead of 20 GB.

KSM is enabled automatically during `hearth setup` (requires root). No configuration needed. You can check current savings with:

Expand Down
Loading
Loading