Drivers

Mentat uses five runtime drivers in a zero trust model. Each workload runs in the isolation level that fits its risk profile. No single driver fits all workloads — the platform picks the right one.

When To Use Each Driver

FirecrackerDedicated kernel per service (KVM). For Raft clusters, Rust unikernels, and high-security workloads. ~8ms boot, ~256MB
SandboxLinux namespace isolation (PID + mount + UTS + IPC) with cgroups v2. For Next.js, Node.js, web apps. No Docker daemon. ~2MB overhead
DockerOCI containers for existing services. Mars Mode auto-adopts running containers on restart
ExecBare binaries for trusted internal agents. No isolation — host-level access
HullIn-house daemonless container runtime. ~3 MB Zig binary, 7-layer isolation (ns + cgroups + seccomp + landlock + pivot_root), optional veth bridge. Runs BEAM / .NET / Node workloads

FirecrackerDriver

Use Firecracker when isolation matters most. Each workload gets its own kernel boundary and dedicated runtime environment, which is a better fit for sensitive services, internal platforms, and private serverless-style workloads.

Typical use: StreamForge and OxideStore.

What the Agent Does

  1. Prepares a root filesystem with the service binary
  2. Configures tap networking and VM resources
  3. Starts Firecracker and attaches volumes
  4. Boots the workload and waits for health

Example: StreamForge (CLI args)

StreamForge receives runtime arguments directly through the service definition.

streamforge service.yaml
name: streamforge
driver: firecracker
count: 3
config:
  memory_mb: 256
  vcpus: 2
  args:
    - "--wal"
    - "--data-dir"
    - "/data/streamforge"
    - "--raft-port"
    - "8081"
    - "--http-port"
    - "8080"
  volumes:
    - source: /mnt/unikernel-volumes/streamforge
      target: /data

Example: OxideStore (env vars)

OxideStore reads configuration from environment variables and persistent volumes.

oxidestore service.yaml
name: oxidestore
driver: firecracker
count: 1
config:
  memory_mb: 256
  vcpus: 2
  env:
    OXIDESTORE_DATA_DIR: "/data/oxidestore"
    OXIDESTORE_BIND: "0.0.0.0:9000"
    OXIDESTORE_REGION: "us-east-1"
  volumes:
    - source: /mnt/unikernel-volumes/oxidestore
      target: /data

Boot Flow

1. mt run service.yaml
2. Server schedules the allocation
3. Agent prepares runtime assets
4. Firecracker receives machine config, boot source, and volumes
5. Workload starts and reports healthy

DockerDriver

Use Docker when the service already exists as a container and the main goal is operational consistency, not runtime conversion. This is the migration path for existing .NET, Node.js, and third-party services.

Typical use: API Jarvis and other existing containerized services.

docker config
config:
  driver: docker
  image: "registry.example/api-service:latest"
  env:
    - - ASPNETCORE_ENVIRONMENT
      - Production
  ports:
    - "8080:80"
  volumes:
    - source: /data/api-service
      target: /app/data
      read_only: true

SandboxDriver

Use Sandbox for web applications, APIs, and services that need isolation without Docker. Linux namespace isolation (PID + mount + UTS + IPC) with cgroups v2 resource limits. A 15KB C binary acts as PID 1 inside the sandbox, with pivot_root and all capabilities dropped.

Typical use: Next.js SSR, React apps, Python FastAPI, Java Spring Boot, static sites.

Pre-built Bases (overlayfs)

No need to build images or copy runtime dependencies. Mentat provides pre-built base rootfs that are merged with your application code via overlayfs. The base is created once and reused across all deploys.

node22Node.js 22 + npm + SSL certsNext.js SSR, Express, Fastify
python312Python 3.12 + stdlib + pipFastAPI, Django, Flask
java21OpenJDK 21 JRE + SSLSpring Boot, Quarkus, Micronaut
staticBusyBox httpd onlyReact, Vue, Angular static builds
Next.js SSR in sandbox
name: my-nextjs-app
driver: sandbox
base: node22              # pre-built, reused via overlayfs
rootfs: ./.next/standalone # just your build output
command: /bin/sh
args: ["/app/start.sh"]
env:
  PORT: "3000"
  NODE_ENV: production
resources:
  memory: 256m
  cpu: 50%
scale:
  min: 1
  max: 5
  metric: cpu
  scale_up_at: 70
  scale_down_at: 20
Python FastAPI in sandbox
name: my-api
driver: sandbox
base: python312
rootfs: ./app
command: /usr/bin/python3
args: ["-m", "uvicorn", "main:app", "--host", "0.0.0.0"]
env:
  PORT: "8000"
resources:
  memory: 128m

Security Model

  1. PID namespace — process cannot see or signal host processes
  2. Mount namespace — pivot_root (not chroot) into isolated rootfs
  3. ALL capabilities dropped — no privilege escalation possible
  4. NO_NEW_PRIVS — set via prctl, inherited by all children
  5. cgroups v2 — memory, CPU, and PID limits enforced by kernel
  6. Minimal /dev — only null, zero, random, urandom

ExecDriver

Use exec for host-level processes that should start fast and run without container or VM overhead. This is useful for platform-side agents, collectors, and operational helpers.

Typical use: Node Agent

exec config
config:
  driver: exec
  command: /opt/bin/node-agent
  args:
    - "--port"
    - "9090"
    - "--workers"
    - "4"
  env:
    - - RUST_LOG
      - info

HullDriver

Use Hull when you want Docker-style container semantics without a daemon, a container registry, or an OCI layer tree. Hull is a single ~3 MB static-musl Zig binary that starts the workload inside a fresh bundle of Linux namespaces, applies cgroups v2 limits, installs a seccomp-bpf syscall allowlist per workload profile, locks down the filesystem via Landlock, and pivot_roots into the container’s rootfs — all in one call.

Typical use: Titan ESB (Elixir/Phoenix/BEAM), .NET AOT services, Node.js services that don’t fit the sandbox base model.

Isolation layers (outer → inner)

  1. cgroups v2 — CPU / memory / pids hard limits
  2. PID + NET + MNT + UTS + IPC namespaces (+ optional USER in --rootless mode)
  3. mount --make-rprivate + fresh /proc inside the mount namespace
  4. seccomp-bpf allowlist (KILL_PROCESS on violation, curated per workload profile)
  5. Landlock LSM filesystem allowlist (graceful fallback on kernels < 5.13)
  6. pivot_root into a dedicated rootfs (extracted and cached under /var/lib/hull/rootfs/<name>)

Bridge networking

Set network: bridge in the manifest and Hull creates a veth pair, attaches one end to the hull0 bridge (10.88.0.0/24 by default), leases the next free IP via atomic O_EXCL lock files, and wires the container side from the host via nsenter. nftables masquerade andiptables -I FORWARD 1 -i hull0 -j ACCEPTare installed idempotently so containers can reach the internet through the host’s default route.

Rootless mode

Pass --rootless and Hull runs the NEWUSER dance even when invoked as root: the parent forks a userns_setup child, writes/proc/<pid>/{setgroups,gid_map,uid_map}, and the workload sees itself as uid 0 inside a brand new user namespace. Defense in depth — there’s no root credential for the workload to escape to.

Titan ESB in hull
name: titan
replicas: 1
config:
  driver: hull
  manifest: /etc/hull/titan.json
  binary: /usr/local/bin/hull-sudo
  port: 4500
resources:
  cpu_mhz: 2000
  memory_mb: 1024
  disk_mb: 256
port: 4500
ingress:
  host: www.titan-bus.com
  aliases:
    - titan.getmentat.run
  path: /
  tls: true
security:
  profile: hull
hull manifest (/etc/hull/titan.json)
{
  "name": "titan",
  "rootfs": "/var/lib/hull/rootfs/titan",
  "argv": ["/opt/titan/bin/titan", "start"],
  "env": ["PHX_SERVER=true", "PORT=4500", "PHX_HOST=www.titan-bus.com"],
  "profile": "beam",
  "network": "bridge",
  "bridge": { "subnet": "10.88.0.0/24" },
  "hostname": "titan",
  "cwd": "/opt/titan",
  "limits": { "memory_mb": 1024, "cpu": 2.0, "pids": 4096 }
}

Inspecting a running container

hull inspect <name> reads~/.hull/state/<name>.json, then prints the live cgroup numbers, all 7 namespace inums (viareadlink /proc/<pid>/ns/*), and the first 12 mount points from mountinfo. No daemon to query —ps, stop, kill, logs, and inspect all go through the same state directory.

Persistent Volumes

All drivers support persistent storage. Firecracker uses attached block-backed volumes. Docker maps host paths as bind mounts. The same platform model can therefore support data-heavy services and lightweight stateless workloads together.

volumes:
  - source: /mnt/unikernel-volumes/vector-store
    target: /data
    read_only: false