Architecture¶
Gatemini 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 | gatemini | Default MCP client integration |
| Direct | gatemini --direct | Single-session debugging with no daemon |
| Daemon | gatemini 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
gatemini 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.
Accept loop and shutdown¶
After initialization, the daemon accepts client connections and creates a fresh GateminiServer 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/gatemini.sock |
| macOS and fallback | /tmp/gatemini-$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 Gatemini, and Gatemini 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 GateminiServer → 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.