Skip to content

Architecture

PrismGate runs as a shared daemon with lightweight per-session proxies. The daemon owns backend connections and the live registry; each client process only bridges stdio to the daemon over a Unix socket.

IPC architecture

Process model

There are three operating modes:

Mode Entry point Purpose
Proxy prismgate Default MCP client integration
Direct prismgate --direct Single-session debugging with no daemon
Daemon prismgate serve Foreground daemon process

Proxy mode is what most clients use. It performs no backend setup and no registry initialization itself. Its job is to connect to the daemon or start it when needed.

Initialization order

The heavy initialization path is shared between direct mode and daemon mode in src/main.rs.

Actual order:

  1. load .env
  2. load config
  3. initialize tracing
  4. resolve secrets
  5. create tracker, registry, and backend manager
  6. load cached tools and usage stats
  7. register aliases and composite tools
  8. spawn background workers

Background workers include:

  • backend startup
  • health checker
  • config watcher
  • optional admin API

Early socket binding

Daemon mode does something important before that initialization completes: it binds the socket early.

Daemon lifecycle

That ordering matters because proxies can connect while initialization is still in progress. Bytes sent by the client wait in the kernel socket buffer until the accept loop starts.

Proxy startup and reconnect behavior

Proxy startup is coordinated with a non-blocking flock on the socket lock file.

Proxy startup

The flow is:

  1. clean up a stale socket if the PID is dead
  2. try a fast-path connect
  3. if needed, acquire the lock
  4. connect again in case another proxy won the race
  5. spawn prismgate serve
  6. wait for the socket to become connectable
  7. bridge stdio to the socket

The proxy also caches the MCP initialize request and initialized notification. If the daemon restarts, the proxy reconnects and replays that handshake so the client session can continue with less disruption.

After the handshake, the proxy forwards newline-delimited JSON-RPC messages while tracking request IDs in both directions. If the daemon connection drops while client requests are in flight, the proxy sends retryable JSON-RPC errors for those request IDs, reconnects to the daemon, replays the handshake, and keeps the client stdio transport open. A quiet client is still treated as a live session while requests are in flight. Once a connection is fully quiescent, the proxy sends an MCP ping liveness probe; responsive idle clients stay connected, while clients that keep stdio open but do not answer the probe are reaped so their daemon session can release backend resources.

Accept loop and shutdown

After initialization, the daemon accepts client connections and creates a fresh PrismGateServer per client. Those server instances are cheap because they share the real state through Arcs.

Shutdown triggers:

  • idle timeout with zero active sessions
  • SIGTERM
  • SIGINT

Shutdown sequence:

  1. stop accepting new clients
  2. wait for connected clients to drain, up to daemon.client_drain_timeout
  3. notify background tasks
  4. stop backends and wait for in-flight calls, up to health.drain_timeout
  5. remove socket, lock, and PID files

Socket paths

Socket resolution is deterministic so proxies and daemon always look in the same place.

Platform Default path
Linux with XDG_RUNTIME_DIR $XDG_RUNTIME_DIR/prismgate.sock
macOS and fallback /tmp/prismgate-$UID.sock

Sibling paths are also used for:

  • lock file
  • PID file

Backend ownership

The daemon owns:

  • live backend instances
  • the shared tool registry
  • recent-call and latency tracking
  • config watch state
  • optional admin HTTP routes

Clients never talk directly to backend MCP servers. They talk to PrismGate, and PrismGate forwards or orchestrates calls on their behalf.

Session identity

Each proxy connection receives a unique session ID (monotonically increasing u64 from an AtomicU64 counter in the accept loop). This ID is threaded through PrismGateServercall_tool_chainBackendManager::call_tool so that dedicated instance pools can route calls to the correct per-session backend instance. Direct mode uses session ID 0.

Dedicated instance pools

Backends configured with instance_mode: dedicated get a per-session instance pool instead of sharing a single backend. The pool pre-warms idle instances, lazily spawns on demand up to a configurable cap, and recycles instances on session disconnect. See Backend Management for details.