A sandbox for the code your agents run.

grix is a small Linux-compatible kernel, written in Rust. Every run boots a fresh microVM that runs exactly one unmodified Linux program. The program sees only the files you hand it, and can do only what you grant it. Anything else is denied, and grix tells your agent exactly what was missing.

New Read Greg's announcement post
Terminal
# Only /bin/net exists, and nothing is granted.
$ grix run -i ./net:/bin/net -- /bin/net
socket: Operation not permitted
grix: denied socket: needs capability `net`

# Grant it, and the program gets its socket.
$ grix run -a net -i ./net:/bin/net -- /bin/net
socket: opened fd 3

And this is that program, in grix, running in your browser. Run it, then tick net and run it again.

What is it

One program, one VM, nothing it wasn't given

grix (greg + unix) is an agentbox: a unikernel built to run Linux programs on behalf of AI agents, with least privilege by default.

A fresh microVM per run

Each run boots grix's kernel in its own virtual machine, on Hypervisor.framework or KVM. Booting, running a small program and exiting takes about a tenth of a second.

Only what you import

The filesystem is the files you list, and the environment is the variables you set. The VM has no network card and no disk. The program can't reach anything you didn't give it.

Denied, and reported

Writing files, threads, processes and the network are capabilities you grant. Anything else fails the way it would on Linux, and grix reports it: denied socket: needs capability `net`.

Why should I care

Agents run code. Something has to say no.

An agent that writes or downloads a program has to run it somewhere. On your machine, it can do anything you can. A container shares your kernel, and inside it everything is allowed. A full VM is a whole operating system to boot and maintain. grix starts from nothing allowed, starts in a tenth of a second, and answers in a form an agent can act on.

Try it

Run it here

These demos run the real grix kernel in your browser, on QEMU compiled to WebAssembly. Edit a command, grant a capability, and run it again.

A shell pipeline, without and then with process

bash, in a terminal

Try ls -l / | head, sleep 100 then Ctrl-C, or echo hi > /tmp/x (that needs fs_write).

Python's own REPL

Try import os; os.listdir('/'), or open('/tmp/x', 'w') without fs_write.

All the demos: Go, Node, stdin, missing files and more.

Install

Get grix

Every commit to main is a release: grix, its VMM, its kernels and the Python package, in one archive for each platform. Nothing else to install.

  1. Download and check it

    Pick your platform: macos-arm64 (Apple silicon, macOS 15+), linux-arm64 or linux-x86_64.

    os=macos-arm64
    tag=$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/gmmeyer/grix/releases/latest | sed 's|.*/tag/||')
    v=${tag#v}
    curl -fsSLO "https://github.com/gmmeyer/grix/releases/download/$tag/grix-$v-$os.tar.gz"
    curl -fsSLO "https://github.com/gmmeyer/grix/releases/download/$tag/SHA256SUMS"
    grep " grix-$v-$os.tar.gz" SHA256SUMS | shasum -a 256 -c   # or: sha256sum -c
    tar xzf "grix-$v-$os.tar.gz"
  2. Put it on your PATH

    grix finds its kernel and VMM beside itself, so keep the directory together.

    mkdir -p ~/.local && mv "grix-$v-$os" ~/.local/grix
    export PATH="$HOME/.local/grix:$PATH"   # and in your shell's profile
    grix --version
  3. Run something

    Turn Docker Hub's Alpine into a grix image, then run its shell. It can't start cat until you grant process, and it can't reach the network until you grant net for one host.

    grix build --from alpine:3.21 -o alpine.grix
    
    grix run --image alpine.grix -- /bin/sh -c 'cat /etc/alpine-release'
    /bin/sh: cat: Operation not permitted
    grix: denied execve: needs capability `process`
    
    grix run --image alpine.grix -a process -- /bin/sh -c 'cat /etc/alpine-release'
    3.21.9
    
    grix run --image alpine.grix -a process -a net --allow-host example.com:80 -- \
        /bin/sh -c 'wget -qO- http://example.com | grep -o "<title>.*</title>"'
    <title>Example Domain</title>

Platform notes

  • macOS: Apple silicon and macOS 15 or later. If you downloaded the archive in a browser instead of with curl, clear the quarantine first: xattr -d com.apple.quarantine ~/.local/grix/grix ~/.local/grix/grix-vmm.
  • Linux: grix's own VMM needs /dev/kvm. Add yourself to the kvm group (sudo usermod -aG kvm $USER, then log in again), or pass --device /dev/kvm to a container. Without KVM, grix falls back to QEMU (qemu-system-aarch64 or qemu-system-x86_64), which emulates and is much slower.
  • x86_64 Linux: images built from Docker/OCI images are arm64 for now. Set GRIX_ARCH=aarch64 for grix build and grix run, and they run emulated in qemu-system-aarch64. x86_64 programs you import yourself run natively on KVM.
  • From source: git clone https://github.com/gmmeyer/grix && cd grix && make needs Rust, and zig and Go for the test programs. See the README.
Use it

From the command line, an agent, or your code

The same sandbox from the CLI, from an agent over MCP, from Python (3.9+, with PYTHONPATH=~/.local/grix/python), or from Rust.

CLI
# Only /bin/net exists, and nothing is granted.
$ grix run -i ./net:/bin/net -- /bin/net
socket: Operation not permitted
grix: denied socket: needs capability `net`

# Python, with threads, and the network only to example.com.
$ grix run -a threads -a net --allow-host example.com:443 \
    -i ./python-rootfs:/ -- /usr/bin/python3 script.py
MCP
# Claude Code: a `run` tool that runs programs in
# fresh sandboxes, with host files only under --root.
claude mcp add grix -- grix mcp --root "$PWD"

# Any other MCP client's config:
{"mcpServers": {"grix": {
  "command": "grix",
  "args": ["mcp", "--root", "/path/to/project"]
}}}
Python
import grix

sb = grix.Sandbox(imports={"./busybox": "/bin/busybox"},
                  files={"/data/in.txt": "b\na\n"},
                  capabilities=["process"], timeout=30)
r = sb.run(["/bin/busybox", "sort", "/data/in.txt"])
print(r.exit_code, r.stdout)
print(r.events, r.missing_capabilities)
Rust
let sandbox = Sandbox::builder()
    .import("./tools/busybox.static", "/bin/busybox")?
    .file("/data/input.csv", csv_bytes, 0o644)?
    .allow(Capability::FsWrite)
    .export("/out")?
    .build()?;
let out = sandbox.run(&[
    "/bin/busybox", "sort",
    "-o", "/out/sorted.csv", "/data/input.csv",
])?;
let needed = out.missing_capabilities();
How it works

A kernel for one program

Your process (the host)

  • collects the imports, argv, env and policy into a boot image
  • starts the VMM: grix-vmm, or QEMU
  • reads output, events and the exit status
  • makes network connections the policy allows
→boot image←framed console

microVM: the grix kernel

  • no_std Rust, booted fresh for every run
  • builds a filesystem from the imports, in memory
  • implements Linux's system calls itself, checking each against the policy
  • runs your program, unmodified

The hypervisor is the wall. A program that took over the grix kernel would still be in a VM with no network card, no disk, and only its imports in memory.

A small, confined VMM. grix-vmm, on Hypervisor.framework or KVM, emulates only the few devices grix needs. Once the VM is up it confines itself: no files, no network, no new processes.

No network card. Sockets become messages on the console. The host checks each one against your allow-list, makes the connection, and answers DNS.

Host directories without a disk. Live mounts are served by the host over the console, file by file, as the program asks.

Capabilities

What a program gets

CapabilityWithout itWith it
(always)Memory, time, randomness, stdout/stderr, pipes, epoll, local sockets, and read access to the imported files
fs_writeThe filesystem is read-onlyThe whole tree is writable in guest memory, copy-on-write over the imports. The host's files never change.
threadsNo new threadsThreads, with futexes and preemption
processNo fork, exec or waitProcesses, shell pipelines, subprocess, os/exec
netNo sockets, and the VM has no network deviceTCP and DNS through the host, only where the allow-list says, and forwarded ports for servers
(never)Privileged calls: mount, ptrace, bpf, module loading, Linux capabilities

Status: early prototype. grix runs a lot of real software, but it isn't hardened against a determined attacker yet. See the security model and what's compatible.

Blog

The Future of Software Engineering

Unikernels lost to containers because they asked too much of developers. What happens when friction goes to zero? Why grix exists, with the demos running in the post.