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.

Seven binaries run on the host machine. They are installed to ~/.capsem/bin/ by the platform package or source install flow.

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 exposes the same contract through an explicit allowlist. Unknown paths return 404 at the gateway and are not forwarded to the service.

status means hot runtime counters suitable for polling. info means configuration and identity. Profile-owned behavior lives under /profiles/{profile_id}/...; only service-wide runtime aggregation lives at the root.

MethodPathPurpose
POST/vms/createCreate a VM from a profile, optionally with a name and resource overrides
GET/vms/listList VMs and their profile/status metadata
GET/vms/{id}/infoVM identity, profile, config, plugin descriptors, and non-hot metadata
GET/vms/{id}/statusRuntime state for one VM
POST/vms/{id}/execExecute command, return stdout/stderr/exit_code
POST/runOne-shot: provision + exec + destroy
POST/vms/{id}/stopStop a VM
POST/vms/{id}/pauseSuspend a VM to disk when supported
POST/vms/{id}/startStart a stopped VM
POST/vms/{id}/resumeResume a stopped or paused VM
POST/vms/{id}/saveSave current VM state
GET/vms/{id}/save/statusSave operation status
POST/vms/{id}/forkFork VM into a reusable image/VM state
GET/vms/{id}/fork/statusFork operation status
DELETE/vms/{id}/deleteDestroy VM and wipe state
POST/purgeStop/delete matching VMs according to the request
POST/vms/{id}/files/writeWrite file to guest
POST/vms/{id}/files/readRead file from guest
GET/POST/vms/{id}/files/contentDownload or upload file content
GET/vms/{id}/files/listList guest files through the file API
GET/vms/{id}/logsSerial/boot logs
GET/vms/{id}/timelineVM event timeline
GET/vms/{id}/historySession history summary
GET/vms/{id}/history/processesProcess history
GET/vms/{id}/history/countsHistory counters
GET/vms/{id}/history/transcriptTerminal transcript history
MethodPathPurpose
GET/vms/{id}/security/latestLatest security_rule_events rows for one VM
GET/vms/{id}/security/statusVM-scoped security ledger counters
GET/vms/{id}/detection/latestLatest detection-bearing security rows for one VM
GET/vms/{id}/detection/statusVM-scoped detection counters
GET/vms/{id}/enforcement/latestLatest enforcement-bearing security rows for one VM
GET/vms/{id}/enforcement/statusVM-scoped enforcement counters
GET/security/latestService-wide latest security rows
GET/security/statusService-wide security counters
GET/detection/latestService-wide latest detection rows
GET/detection/statusService-wide detection counters
GET/enforcement/latestService-wide latest enforcement rows
GET/enforcement/statusService-wide enforcement counters
MethodPathPurpose
GET/profiles/listList configured profiles
GET/profiles/statusProfile readiness, asset status, and validation state
POST/profiles/reloadReload the profile catalog
GET/profiles/{profile_id}/infoProfile identity/config truth
POST/profiles/{profile_id}/validateValidate a profile
POST/profiles/{profile_id}/reloadReload one profile
GET/profiles/{profile_id}/obomBase-image CycloneDX OBOM metadata and local document when installed
POST/profiles/{profile_id}/enforcement/evaluateEvaluate a supplied security event against enforcement rules
GET/profiles/{profile_id}/enforcement/infoEnforcement file/config info
GET/profiles/{profile_id}/enforcement/rules/listCompiled enforcement rules
PUT/profiles/{profile_id}/enforcement/rules/{rule_id}/editAdd or replace one enforcement rule
DELETE/profiles/{profile_id}/enforcement/rules/{rule_id}/deleteDelete one enforcement rule
POST/profiles/{profile_id}/enforcement/reloadReload enforcement rules
POST/profiles/{profile_id}/detection/evaluateEvaluate a supplied security event against detection rules
GET/profiles/{profile_id}/detection/infoDetection file/config info
GET/profiles/{profile_id}/detection/rules/listCompiled detection rules
PUT/profiles/{profile_id}/detection/rules/{rule_id}/editAdd or replace one detection rule
DELETE/profiles/{profile_id}/detection/rules/{rule_id}/deleteDelete one detection rule
POST/profiles/{profile_id}/detection/reloadReload detection rules
GET/profiles/{profile_id}/plugins/listProfile plugin config plus registry descriptors
GET/profiles/{profile_id}/plugins/infoPlugin subsystem info for the profile
GET/profiles/{profile_id}/plugins/{plugin_id}/infoOne plugin config and descriptor
PATCH/profiles/{profile_id}/plugins/{plugin_id}/editEdit one plugin config
GET/profiles/{profile_id}/assets/statusProfile asset readiness
GET/profiles/{profile_id}/assets/infoProfile asset descriptors
POST/profiles/{profile_id}/assets/ensureDownload/verify profile assets
GET/profiles/{profile_id}/mcp/infoProfile MCP config info
GET/profiles/{profile_id}/mcp/servers/listProfile MCP servers
PUT/profiles/{profile_id}/mcp/servers/{server_id}/editAdd or replace one MCP server
DELETE/profiles/{profile_id}/mcp/servers/{server_id}/deleteDelete one MCP server
GET/profiles/{profile_id}/mcp/servers/{server_id}/tools/listTools for one MCP server
POST/profiles/{profile_id}/mcp/servers/{server_id}/refreshRefresh one MCP server
PATCH/profiles/{profile_id}/mcp/servers/{server_id}/tools/{tool_id}/editEnable/disable or edit one MCP tool
POST/profiles/{profile_id}/mcp/servers/{server_id}/tools/{tool_id}/callCall one MCP tool
MethodPathPurpose
GET/versionService version
GET/statsFull telemetry dump (all sessions)
GET/service-logsService log tail
GET/triageDebug triage bundle
GET/panicsPanic log summary
GET/host-logs/{name}Named host log
GET/settings/infoUI/application settings
PATCH/settings/editEdit settings-owned preferences
GET/corp/infoCorporate constraint/reporting config
PUT/corp/editReplace corporate config
POST/corp/validateValidate corporate config
POST/corp/reloadReload corporate config

Install registers the service and places host binaries under ~/.capsem/bin/. The service owns asset resolution and reports missing/downloading/ready state to the UI and CLI. Provider credentials are configured in normal user/corp settings or brokered from runtime security events; there is no setup wizard authority path.

~/.capsem/
bin/ capsem, capsem-service, capsem-process, capsem-mcp, capsem-gateway, capsem-tray
assets/ manifest.json, vmlinuz-{hash16}, initrd-{hash16}.img, rootfs-{hash16}.erofs
run/ service.sock, service.pid, gateway.token, gateway.port, instances/
update-check.json Self-update cache (24h TTL)
settings.toml UI/application preferences
corp.toml Enterprise constraints/reporting config (optional)
profiles/ Profile-owned assets, rules, MCP, plugins, VM defaults

capsem update checks the release-channel health index at release.capsem.org/health.json for binary freshness and selects the matching .pkg or .deb installer metadata for the current install layout. With --yes, it downloads the selected installer into ~/.capsem/updates/installers/, verifies size plus SHA-256, prints the tested package-manager apply command for audit, and executes that command through sudo. VM asset refresh is separate: capsem update --assets hydrates missing kernel/initrd/rootfs bytes from the installed or overridden manifest, verifies BLAKE3 hashes, and keeps hash-named files deduplicated.

Installs that shipped before this packaged binary updater cannot be made self-updating by changing release.capsem.org; those binaries do not contain the package apply path. They need one manual .pkg or .deb upgrade into a version with the updater before later binary releases can move independently from VM asset releases.

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 for the service-unavailable screen
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)