Skip to content

Service Architecture

Capsem uses a service-oriented architecture with multiple cooperating binaries. Every VM operation flows through a single path: client -> service -> per-VM process -> guest.

At the top level, Capsem is a small process tree. The background service owns lifecycle. It starts desktop companion processes, and it spawns one capsem-process per running VM. Each VM process owns the hypervisor instance, guest vsock bridges, and a separate MCP aggregator subprocess for external MCP server connections.

flowchart TD
    subgraph Clients["Client processes"]
        CLI["capsem<br/>CLI"]
        APP["capsem-app<br/>desktop UI"]
        HOSTMCP["capsem-mcp<br/>host MCP server"]
    end

    SVC["capsem-service<br/>daemon process"]
    GW["capsem-gateway<br/>HTTP + WebSocket process"]
    TRAY["capsem-tray<br/>menu bar process"]

    subgraph VMHost["Per running VM"]
        PROC["capsem-process<br/>VM supervisor process"]
        AGG["capsem-mcp-aggregator<br/>isolated subprocess"]
        VM["Linux VM<br/>hardware-isolated guest"]

        subgraph Guest["Guest processes"]
            PTY["capsem-pty-agent"]
            NET["capsem-net-proxy"]
            DNS["capsem-dns-proxy"]
            GMCP["capsem-mcp-server"]
            SYS["capsem-sysutil"]
        end
    end

    EXT["External MCP servers<br/>HTTP/SSE"]

    SVC -->|spawns companion| GW
    SVC -->|spawns companion| TRAY
    SVC ==>|spawns one per VM| PROC

    APP -->|HTTP| GW
    TRAY -->|HTTP| GW
    GW -->|HTTP/UDS| SVC
    CLI -->|HTTP/UDS| SVC
    HOSTMCP -->|HTTP/UDS| SVC

    PROC -->|spawns| AGG
    AGG -->|MCP over HTTP/SSE| EXT
    PROC ==>|boots + owns| VM
    PROC -->|vsock bridges| PTY
    PROC -->|vsock:5002| NET
    PROC -->|vsock:5007| DNS
    PROC -->|framed MCP over vsock:5002| GMCP
    PROC -->|vsock:5004| SYS

Seven binaries run on the host machine. They are installed to ~/.capsem/bin/ by capsem setup.

BinaryRoleCommunication
capsemCLI clientHTTP over UDS to service
capsem-serviceBackground daemonAxum HTTP over UDS (~/.capsem/run/service.sock)
capsem-processPer-VM processSpawned by service, MessagePack over UDS
capsem-mcpMCP server for AI agentsstdio (rmcp), HTTP over UDS to service
capsem-mcp-aggregatorExternal MCP server connectionsNDJSON over stdin/stdout, spawned by capsem-process
capsem-gatewayHTTP/WebSocket gatewayTCP port 19222, proxies to service UDS
capsem-traySystem trayPolls gateway for VM status

Additionally, capsem-app is a thin Tauri webview shell (desktop GUI). It connects to the gateway at http://127.0.0.1:19222 and has no direct VM logic — all operations route through the gateway to the service.

Five binaries run inside each Linux VM, cross-compiled for aarch64-unknown-linux-musl and x86_64-unknown-linux-musl. All are deployed chmod 555 (read-only).

BinaryRoleVsock port
capsem-pty-agentPTY bridge, control channel, exec, file I/O, kernel audit stream5000 (control), 5001 (terminal), 5005 (exec), 5006 (audit)
capsem-net-proxyRedirects HTTPS to host MITM proxy5002
capsem-dns-proxyRedirects DNS queries to the host DNS policy/resolver path5007
capsem-mcp-serverGuest MCP stdio-to-framed-vsock relay5002
capsem-sysutilLifecycle multi-call (shutdown/halt/poweroff/reboot/suspend)5004

All clients route through capsem-service. There is no direct VM boot from any other binary.

graph TD
    subgraph Clients
        CLI["capsem (CLI)"]
        MCP["capsem-mcp (MCP)"]
        GW["capsem-gateway (TCP:19222)"]
    end

    subgraph "UI Layer"
        APP["capsem-app (Tauri)"]
        TRAY["capsem-tray"]
    end

    APP -->|HTTP| GW
    TRAY -->|HTTP| GW

    CLI -->|HTTP/UDS| SVC
    MCP -->|HTTP/UDS| SVC
    GW -->|HTTP/UDS| SVC

    SVC["capsem-service (daemon)"]

    SVC -->|"MessagePack/UDS"| PROC["capsem-process (per-VM)"]

    PROC -->|"NDJSON/stdio"| AGG["capsem-mcp-aggregator"]
    AGG -->|"HTTP/SSE"| EXT["External MCP servers"]

    subgraph "Linux VM (guest)"
        AGENT["capsem-pty-agent"]
        NETPROXY["capsem-net-proxy"]
        DNSPROXY["capsem-dns-proxy"]
        MCPGW["capsem-mcp-server"]
        SYSUTIL["capsem-sysutil"]
    end

    PROC -->|"vsock:5000,5001,5005,5006"| AGENT
    PROC -->|"vsock:5002"| NETPROXY
    PROC -->|"vsock:5007"| DNSPROXY
    PROC -->|"vsock:5002"| MCPGW
    PROC -->|"vsock:5004"| SYSUTIL

Each layer uses a different protocol optimized for its role:

LayerProtocolSocket
Frontend/Tray -> gatewayHTTP/1.1 over TCP127.0.0.1:19222 (Bearer token auth)
Gateway -> serviceHTTP/1.1 over UDS~/.capsem/run/service.sock
CLI/MCP -> serviceHTTP/1.1 over UDS~/.capsem/run/service.sock
Service -> processMessagePack over UDS~/.capsem/run/instances/{id}.sock
Process -> guestBinary frames over vsockPorts 5000, 5001, 5002, 5004, 5005, 5006, 5007
PortPurposeBinary
5000Control messages (resize, heartbeat, exec, file I/O)capsem-pty-agent
5001Terminal data (PTY I/O)capsem-pty-agent
5002MITM proxy and framed guest MCP endpointcapsem-net-proxy, capsem-mcp-server
5004Lifecycle commands (shutdown/suspend)capsem-sysutil
5005Exec output (direct child stdout)capsem-pty-agent
5006Kernel audit streamcapsem-pty-agent
5007DNS proxy queriescapsem-dns-proxy

When the service starts, it spawns two companion processes:

  1. capsem-gateway — TCP gateway on port 19222
  2. capsem-tray — system tray menu bar icon

All three are separate OS processes. If the service crashes, the LaunchAgent/systemd restarts it automatically.

PlatformMechanismUnit
macOSLaunchAgent~/Library/LaunchAgents/com.capsem.service.plist
Linuxsystemd user unit~/.config/systemd/user/capsem.service

Both are configured for auto-restart (KeepAlive/Restart=always) and run-at-login.

The CLI (capsem) auto-launches the service if it’s not running. On every service-dependent command:

  1. Check socket connectivity
  2. Try service manager (LaunchAgent/systemd)
  3. Fall back to direct spawn
  4. Poll socket for up to 5 seconds

Each running VM gets its own capsem-process child. This provides security isolation:

  • Minimal environment: service uses env_clear() before spawn — API keys and tokens from the user’s shell never reach the process
  • Socket permissions 0600: only the owning user can connect to per-VM sockets
  • Session directory 0700: contains workspace, system, serial.log, session.db
  • No guest-triggered exit: control channel errors cause loop exit, not process::exit()
  • VirtioFS boundary: only session_dir/guest/ is shared — host-only files (session.db, serial.log, snapshots, checkpoints) are outside the share
  • MCP aggregator isolation: external MCP server connections run in a separate subprocess (capsem-mcp-aggregator) with only network access — no VM, database, or filesystem access. See MCP Aggregator for details.

The service exposes a REST API over UDS. The gateway proxies this transparently.

MethodPathPurpose
POST/provisionCreate a new VM (persistent: true for named VMs)
GET/listList all VMs (running + stopped persistent)
GET/info/{id}VM details (config, status, persistent)
POST/exec/{id}Execute command, return stdout/stderr/exit_code
POST/runOne-shot: provision + exec + destroy
POST/stop/{id}Stop VM (persistent: preserve; ephemeral: destroy)
POST/resume/{name}Resume a stopped persistent VM
POST/persist/{id}Convert ephemeral to persistent
POST/purgeKill all temp VMs (all: true includes persistent)
POST/files/{id}/content?path=<relpath>Write workspace file
GET/files/{id}/content?path=<relpath>Read workspace file
GET/logs/{id}Security, process, and serial logs
POST/inspect/{id}SQL query against session.db
DELETE/delete/{id}Destroy VM and wipe state
POST/suspend/{id}Suspend VM to disk (persistent only)
POST/fork/{id}Fork VM into reusable image
GET/statsFull telemetry dump (all sessions)
POST/reload-configHot-reload settings from disk

capsem setup is the primary install path — an interactive wizard that runs on first use.

  1. Corp config — optional enterprise config from URL or file
  2. Asset download — background download of VM assets (kernel, rootfs, initrd)
  3. Security preset — medium or high (corp can lock this)
  4. AI providers — auto-detect Anthropic, Google, OpenAI, GitHub credentials
  5. Repository access — detect Git, SSH, GitHub token
  6. Service install — register LaunchAgent/systemd + PATH check

Auto-runs non-interactively on first CLI use if ~/.capsem/setup-state.json is missing. Re-run with capsem setup --force.

~/.capsem/
bin/ capsem, capsem-service, capsem-process, capsem-mcp, capsem-gateway, capsem-tray
assets/ manifest.json, manifest.json.minisig, {arch}/{vmlinuz-<hash16>, initrd-<hash16>.img, rootfs-<hash16>.squashfs}
run/ service.sock, service.pid, gateway.token, gateway.port, instances/
setup-state.json Wizard progress (resumable)
user.toml User settings
corp.toml Enterprise config (optional)

capsem update-assets checks GitHub for new VM asset versions, downloads hash-named per-arch assets, verifies the signed manifest and BLAKE3 hashes, and cleans up stale asset aliases. Binary updates are handled by the platform package manager (.pkg/.deb).

CrateTypeWhat
capsem-corelibAll shared business logic (VM, network, policy, telemetry, config)
capsem-servicebinDaemon. Axum HTTP over UDS, spawns/manages capsem-process children
capsem-processbinPer-VM. Boots VM via capsem-core, bridges vsock, job store
capsembinCLI. HTTP over UDS to service, direct UDS to process for shell
capsem-mcpbinMCP server (stdio). rmcp crate, bridges tool calls to service
capsem-mcp-aggregatorbinIsolated subprocess. Manages external MCP server connections via NDJSON
capsem-gatewaybinHTTP gateway. Axum on TCP:19222, Bearer auth, WebSocket terminal relay
capsem-appbinThin Tauri webview. Points at gateway, bundles frontend/dist as fallback
capsem-traybinSystem tray. Polls gateway, shows VM status
capsem-agentbin(5)Guest binaries (pty-agent, net-proxy, dns-proxy, mcp-server, sysutil)
capsem-loggerlibSession DB schema, queries, async writer
capsem-protolibShared protocol types (host-guest, service-process IPC)