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.
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:
- load
.env - load config
- initialize tracing
- resolve secrets
- create tracker, registry, and backend manager
- load cached tools and usage stats
- register aliases and composite tools
- 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.
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.
The flow is:
- clean up a stale socket if the PID is dead
- try a fast-path connect
- if needed, acquire the lock
- connect again in case another proxy won the race
- spawn
prismgate serve - wait for the socket to become connectable
- 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
SIGTERMSIGINT
Shutdown sequence:
- stop accepting new clients
- wait for connected clients to drain, up to
daemon.client_drain_timeout - notify background tasks
- stop backends and wait for in-flight calls, up to
health.drain_timeout - 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 PrismGateServer → call_tool_chain → BackendManager::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.