Firecracker Setup (Tier 3)¶
Tier 3 runs oneshot commands, custom output parsers, MCP stdio servers, PTY sessions
and managed CLI workers in a
fresh Firecracker microVM. The selected ToolClad boundary and public FirecrackerRunner use the
same guest protocol and independent supervisor. A VM boot or VMM exit cannot
stand in for the requested command's result.
Tier 3 is part of the open-source runtime. It needs no license key and no Enterprise
build: the symbi-sandbox-guest and symbi-sandbox-supervisor crates ship in this
repository, so you can build, audit and reproduce the guest image yourself.
Fixed read_file, list_files and grep_files operations use explicit read-only
source_roots through the runtime's bounded file broker. These roots never become
guest mounts or automatic imports. See source queries for
configuration and limits. Declared command/MCP/PTY files use bounded protocol-5
byte transfers and a separate output_roots ceiling for new host outputs. See
file grants. Git uses a separate bounded snapshot stream
with a sealed guest filesystem.
Isolated browser execution remains unavailable. Selected unsupported paths fail explicitly. Native HTTP remains a host broker operation, with custom parsers sent to the selected VM. See command isolation and branch coverage.
Provision the artifacts¶
The host requires Linux with accessible /dev/kvm, a compatible
Firecracker executable,
a kernel with virtio block/vsock support, a read-only ext4 root image and a matching
supervisor helper. Follow the
versioned Firecracker setup instructions
for kernel and VMM compatibility. Verify artifact provenance and keep artifacts
outside workload-writable storage. The profile binds paths and configuration;
it does not pin operator artifact contents against later replacement.
Install symbi-sandbox-guest at /sbin/symbi-sandbox-guest inside the image. It
must run as guest PID 1 without other services. Build it and the host runtime from
the same source revision: the handshake checks protocol version and a fingerprint
of the guest crate sources and build script. Stale images are refused before a
command is sent. The kernel and image remain trusted artifacts.
For Linux x86-64 with a static GNU toolchain:
cargo build --locked -p symbi-sandbox-supervisor
cargo rustc --locked -p symbi-sandbox-guest --bin symbi-sandbox-guest \
--target x86_64-unknown-linux-gnu -- -C target-feature=+crt-static
A suitable musl target is another option. Include all loaders, libraries and tools needed by an operator-built image. The following rootless test-image builder requires a static guest executable and static BusyBox:
python3 scripts/build_firecracker_rootfs.py \
--guest-binary target/x86_64-unknown-linux-gnu/debug/symbi-sandbox-guest \
--busybox /absolute/path/to/static-busybox \
--output /absolute/path/to/test-rootfs.ext4
The builder uses mkfs.ext4 -d, creates a 64 MiB image, records binary/image
SHA-256 hashes beside it and refuses overwrite. It installs selected BusyBox
commands and a synthetic root-only canary. It downloads nothing and mounts no
host filesystem. Additional local static test programs can be installed with
--program NAME=/absolute/path/to/binary; their hashes are included in the record.
This is a local test fixture, not a maintained production image.
Configure a project¶
The existing scaffold accepts kernel and rootfs paths:
symbi init --profile assistant --sandbox tier3 \
--firecracker-kernel /var/lib/symbi/vmlinux \
--firecracker-rootfs /var/lib/symbi/rootfs.ext4
Review the generated configuration. Use a private supervisor state directory with a short absolute path, because Unix socket names have a kernel length limit:
[sandbox]
tier = "firecracker" # tier3 is also accepted
[sandbox.firecracker]
kernel_image_path = "/var/lib/symbi/vmlinux"
rootfs_path = "/var/lib/symbi/rootfs.ext4"
firecracker_binary = "/usr/local/bin/firecracker"
rootfs_read_only = true
vcpus = 1
mem_mib = 512
working_dir = "/tmp"
max_execution_time = { secs = 30, nanos = 0 }
startup_timeout = { secs = 5, nanos = 0 }
max_output_bytes = 1048576
[sandbox.firecracker.supervisor]
binary = "/usr/local/bin/symbi-sandbox-supervisor"
state_dir = "/var/lib/symbi/vm-leases"
Shipping CLI executables can serve the embedded supervisor protocol. SDKs need the matching standalone helper or an explicitly configured helper path.
Default boot arguments select init=/sbin/symbi-sandbox-guest and a read-only root.
Writable rootfs and the old work_dir option are refused. Use the supervisor state
directory for host lifecycle data and /tmp for guest scratch. Limits permit
1–32 vCPUs, 64–16,384 MiB memory and at most 24 hours per invocation. Registered
agent CPU, memory and time budgets only tighten the profile. CPU budgets below
one whole vCPU and memory below 64 MiB are rejected.
init and doctor check prerequisites. An actual command trial is needed to
establish that the kernel, image, helper and host can execute together.
Guest execution and results¶
Guest PID 1 mounts /proc, /sys, device nodes and a 128 MiB /tmp tmpfs, and
enables guest loopback for local adapters. Before
invocation it drops supplementary groups, GID and UID to 65534, sets
no_new_privs, disables core dumps and caps processes and open descriptors at
256. No external network interface, host source mount, ambient provider credential,
approval socket or audit directory is supplied by the runtime to the VM. Tools must exist in the image; scratch
storage disappears with each command.
The supervisor durably registers a lease, starts the VMM and records its PID,
start time and boot identity before reporting readiness. The runtime connects
to the actual vsock device and requests guest port 4050. Each VM accepts one
versioned request with an ID, exact argv, explicit environment, working directory
and limits. Defaults are PATH=/usr/bin:/bin, HOME=/tmp and LANG=C.UTF-8.
Host ambient variables are not inherited; values are never converted through a
JSON-to-shell environment parser.
A four-byte big-endian length precedes each JSON header; raw stream bytes follow
separately. Custom parsers receive /tmp/symbi-parser-input, created by PID 1
from supplied input inside the guest. No host temporary file is mounted. Headers
are limited to 1 MiB, input and each output stream to 10 MiB, with a configurable
smaller output limit. Runtime string results replace invalid UTF-8 bytes.
The response binds the same ID to exit code, byte lengths, timeout/truncation flags and service error. Malformed, stale, mismatched, oversized, incomplete or extra data fails the invocation. Success requires a valid correlated response, exit zero, no error/timeout/truncation and confirmed VM cleanup. Kernel panic, missing init, VMM exit and failed cleanup cannot fabricate success. Guest console and VMM logging are discarded; command output uses only the bounded protocol.
MCP and live byte streams¶
The current protocol is version 5, with explicit oneshot, stdio and PTY modes. The guest acknowledges the exact request ID after spawning the command. A frame contains a one-byte type, a four-byte big-endian length and at most 64 KiB of raw data. Stdin, stdin EOF, stdout, stderr, startup, file finalization and terminal outcome have distinct types. Total stdin is limited to 10 MiB; each output stream uses the configured output limit. Partial frames and slow readers cannot allocate unbounded queues or bypass the lifetime. Raw SDK streams preserve binary bytes, including invalid UTF-8.
FirecrackerRunner::spawn_stdio returns stdin, stdout, stderr and a lifetime
guard. Keep the guard through the complete interaction and await finish() to
confirm cleanup. Dropping it cancels the retained VM owner. An unexpected exit,
invalid frame, excess output or lifetime expiry remains an error when finishing.
wait_for_exit() passively awaits the verified exit code and removal; a nonzero
code is returned to CLI callers, while finish() still reports it as failure.
finish_cleanup() separately confirms removal after a failed operation.
Cancellation or a missing acknowledgement cannot invent an exit status.
Closing stdin does not discard pending output. Blocked readers cannot prevent
VM removal at the deadline.
The selected MCP adapter uses these streams for initialization, discovery,
SchemaPin verification and invocation in one guest process. Configure the server
executable and its dependencies inside the image; mcp-config.toml keeps the same
command/args/env and public-key fields. The runtime does not pass its provider credentials, signing keys or host project
files to the VM automatically; only declared file snapshots and explicitly
configured server environment values enter. A valid MCP response and confirmed
worker removal are required; the server can remain alive until the adapter closes
the session. Unsigned or changed schemas and pinned-key substitutions are refused.
The static mcp_fixture example is only for deterministic local tests. Build it
for the same static target and install it with
--program mcp_fixture=/absolute/path/to/mcp_fixture to run the signed MCP case in
firecracker_sandbox.rs. Rebuild the guest image after upgrading from protocol
versions 1, 2, 3 or 4; old guest fingerprints are deliberately refused.
Interactive PTY sessions¶
Session tools now use a real controlling PTY
inside the selected VM. The guest kernel must support Unix PTYs and devpts. The
runtime retains the session across authorized calls, and terminal stderr merges
into stdout. Startup, interaction, idle, total lifetime and output limits remain
enforced; a matched prompt reports prompt_observed with no invented exit code.
The guest starts at 80×24 with echo and canonical input disabled. Ordinary stdio
keeps its separate binary streams. There is no host terminal or mount fallback.
For local tests, build the static pty_fixture example and install it with
--program pty_fixture=/absolute/path/to/pty_fixture. This complements the
mcp_fixture example; neither is a production tool server. The same bounded
protocol and independent VMM ownership carry all three execution modes.
Managed CLI workers¶
Managed CLI execution uses the same selected VM profile and supervised streams. Provision Python 3, the native CLI and its libraries in the read-only rootfs; the static BusyBox fixture alone cannot run this route. Rebuild the guest service from this branch even if an older image already uses an earlier implementation of protocol 5: the implementation fingerprint includes loopback setup.
The runtime issues only two guest-to-host capabilities: CID 2, port 4051 reaches this run's governed tool broker, and port 4052 reaches its protected inference broker. A tools-only SDK broker receives only 4051. Other ports have no endpoint. Project configuration cannot deserialize or select these socket capabilities. Private lease-directory links are removed by the supervisor with the VM.
The generated Python bridges are part of the exact admission argv. The inference adapter listens on guest loopback, while provider credentials remain in the host broker. The MCP bridge sends a private close notification after complete stdin frames, allowing the broker to finish pending responses before closing its connection. Closing this connection grants no authority and closes no other connection or run.
--target selects an absolute guest backend path; omission uses the configured
working directory. The CLI starts in private /tmp. No host repository is
transferred, and scratch files do not persist across backend calls. Rootfs files
readable by the workload are available to both CLI and backend VMs, so the base
image must not contain private control material.
Lifetime and deployment limits¶
PID 1 kills remaining workload processes, including descendants that created new sessions, before returning output. The supervisor kills and waits for the entire VMM on release, runtime disconnect or lifetime expiry. A dropped execution future signals its retained owner; governed runs retain cleanup acknowledgement. Linux parent-death signaling kills the directly launched VMM if the per-user supervisor dies. Recovery verifies boot/start identity and uses pidfds, so a stale numeric PID alone cannot authorize cleanup.
The kernel, VMM, image, service account and host storage remain trusted. The ordinary per-user supervisor does not provision a jailer or host cgroups. Its worker admission shares configured guest CPU and memory reservations with Docker/gVisor workers using the same state directory, without charging VMM overhead. See shared budgets.
The opt-in managed host service provisions approved artifacts, the jailer, distinct VMM identities, host cgroups and guest-plus-overhead admission. It requires a separately managed root service and uses systemd watchdog and recovery commands for cleanup across supervisor failure; UID changes mean the direct-launch parent-death contract alone is insufficient. Its endpoint accepts only approved VM launches, so any Docker/gVisor allocation must be partitioned separately. Privileged host and outage E2E is still pending; the opt-in profile is not yet deployment-validated. No network destination broker is provided.
Provision the host for the deployment and review Firecracker's production host guidance. The test image and regression cases do not establish complete containment.
Verification¶
crates/runtime/tests/firecracker_sandbox.rs exercises real KVM through the public
runner, selected command boundary and ToolClad. Set SYMBI_FIRECRACKER_BINARY,
SYMBI_FIRECRACKER_KERNEL, SYMBI_FIRECRACKER_ROOTFS and
SYMBIONT_SANDBOX_SUPERVISOR to local artifacts. The stale-image test also requires
SYMBI_FIRECRACKER_STALE_ROOTFS, a bootable earlier guest implementation.
cargo test -p symbi-runtime --features cli-executor,mcp-client,toolclad-session,cedar \
--test firecracker_sandbox cli:: -- --ignored --test-threads=1
This command selects the managed CLI cases and requires Python in the rootfs. Select the relevant other tests with their required fixture programs when changing command, MCP or PTY behavior. Run ignored tests explicitly on a provisioned host. Ordinary ignored-test counts are not VM evidence. Retain source/artifact identities, positive outputs, expected failures and cleanup observations from actual trials.