Hey! It's OS-level, no WASM. Three tiers depending on what's available on the host:
The full-isolation path is Firecracker. Each tool execution spins a microVM (1 vCPU, 512MB), and the runner communicates with a statically-linked guest agent over AF_VSOCK — the host process never shells out directly. Inside the VM there's a second allowlist before spawn() is called, so even if you somehow escaped the first filter you'd still hit a second one. In prod the pod itself runs under gVisor (runsc), so the microVM guest kernel is one layer inside gVisor's user-space kernel. That's three kernel-boundary crossings before you touch the real host.
When Firecracker isn't available (dev laptops, restricted CI), it falls back to unshare-based namespace isolation. If that's also absent, it's just process-level controls — allowlist, CWD scoping, cgroup v2 limits (512MB memory, 50% CPU, 256 pids), and a prompt-injection scan that looks for bidi overrides and things like reverse.*ssh patterns.
The hard part honestly wasn't the VM layer, it was the cgroup v2 writes being gracefully no-op'd when not root so the same binary works in dev and prod without ifdef soup. The AF_VSOCK path was also fiddly — you need a raw fd via libc::socket(AF_VSOCK, SOCK_STREAM, 0) and then wrap it into tokio's async I/O, which isn't exactly well-documented (got to fix that).
Kata Containers RuntimeClass is also defined if you want hardware-VM pods instead of gVisor, but it defaults to gVisor since it doesn't require nested virt support on the nodes.
The sandboxed execution part sounds really interesting.
How are you handling isolation in practice — is it more like WASM-based sandboxing or OS-level isolation (containers, seccomp, etc.)?
Feels like this is one of the hardest parts to get right for agent-based systems.
Hey! It's OS-level, no WASM. Three tiers depending on what's available on the host:
The full-isolation path is Firecracker. Each tool execution spins a microVM (1 vCPU, 512MB), and the runner communicates with a statically-linked guest agent over AF_VSOCK — the host process never shells out directly. Inside the VM there's a second allowlist before spawn() is called, so even if you somehow escaped the first filter you'd still hit a second one. In prod the pod itself runs under gVisor (runsc), so the microVM guest kernel is one layer inside gVisor's user-space kernel. That's three kernel-boundary crossings before you touch the real host.
When Firecracker isn't available (dev laptops, restricted CI), it falls back to unshare-based namespace isolation. If that's also absent, it's just process-level controls — allowlist, CWD scoping, cgroup v2 limits (512MB memory, 50% CPU, 256 pids), and a prompt-injection scan that looks for bidi overrides and things like reverse.*ssh patterns.
The hard part honestly wasn't the VM layer, it was the cgroup v2 writes being gracefully no-op'd when not root so the same binary works in dev and prod without ifdef soup. The AF_VSOCK path was also fiddly — you need a raw fd via libc::socket(AF_VSOCK, SOCK_STREAM, 0) and then wrap it into tokio's async I/O, which isn't exactly well-documented (got to fix that).
Kata Containers RuntimeClass is also defined if you want hardware-VM pods instead of gVisor, but it defaults to gVisor since it doesn't require nested virt support on the nodes.
Feel free to ask if you have anymore questions!
[dead]