Screenshots
See It in Action
Roost is a headless daemon — one hub, every session, zero per-session processes
Features
One Daemon, Not Twenty-Two Processes Per Session
Every Claude Code session used to spawn its own private copy of all 22 stdio MCP servers configured across the fleet — measured at 92 processes and 5.85 GB of RSS with just 4 concurrent sessions open, extrapolating to roughly 270 processes and 17 GB at a dozen-session working set. Roost replaces that with a single daemon, roostd, that serves every logical server at http://127.0.0.1:3462/<server>/mcp over Streamable HTTP. Sessions carry plain URL entries instead of spawn commands, and tool names (mcp__<server>__*) never change, because the prefix comes from the config key, not the transport. Behind the router, roostd spawns at most one child per backend — lazily, shared by every session, reaped after idle — and answers tools/list from a persisted catalog snapshot without waking anything at all.
The Problem
22 Servers, Spawned Once Per Session, Forever
92 Processes, 5.85 GB, Four Sessions
Measured on the founder's Mac with just 4 concurrent Claude Code sessions open: 92 MCP server processes, 5.85 GB of total RSS, 43–94 MB per server. At the routine working set of a dozen sessions, that extrapolates to roughly 270 processes and 17 GB — doing nothing but holding identical idle copies of the same wrappers.
All Stateless, All Byte-Identical
Every fleet MCP server is a thin wrapper that translates tool calls into REST calls against the tool's own backend — auth lives in env/config, not per-connection. Twelve copies of the same server hold zero session-specific state between them; they are byte-identical processes wasting byte-identical memory.
22 Interpreter Startups Before a Single Tool Works
Before Roost, every new session paid the cost of spawning all 22 stdio servers fresh — 22 Python interpreter startups before the first tool call could even resolve. Deferred tool loading had already solved the context-window half of the many-servers problem; the process half remained.
Architecture
One Daemon, Path-Per-Server Routing
roostd — A Single Shared Hub
One asyncio process (uvicorn + Starlette), binding
127.0.0.1only. Per configured server, roostd mounts an independent Streamable HTTP endpoint at/<server>/mcp, backed by a relay whose handlers forward to that backend's one shared client session.Tool Names Never Change
Each stdio stanza in
~/.claude.jsonbecomes a plain URL entry, but the config key — and therefore themcp__<server>__*prefix every session and every CLAUDE.md already references — is untouched.roostctl initlifts every stanza verbatim and keeps a.mcp.direct.jsonescape hatch alongside it.Lazy Spawn, Never on tools/list
A backend child spawns only on its first real
tools/call, with exponential backoff and a circuit breaker if it keeps failing. Coldtools/listis served from the catalog snapshot instead — the invariant that keeps a fresh session's tool listing fast.Catalog Snapshot Cache
Every server's initialize result plus its tools/resources/prompts lists are persisted to disk and served in microseconds. The snapshot invalidates on a real spawn diff, an explicit
roostctl refresh, or a staleness check against each server'swatch_paths.Shared-Child Multiplexing, No Hand-Rolled ID Remapping
All forwards for a backend funnel through its single SDK
ClientSession, which assigns its own monotonically increasing request IDs — cross-session collisions are impossible by construction, not merely unlikely. Progress notifications relay back only to the session that made the call.
Reliability
Self-Healing by Default, Reversible in Seconds
launchd KeepAlive
The default start mode runs roostd as a launchd user agent with
KeepAlive— it restarts itself on crash and survives a reboot without a human re-running a script. Akill -9against a live daemon was verified to auto-respawn in about six seconds.Per-Backend Circuit Breaker
A child that dies mid-request fails those in-flight calls with the child's exit code and last stderr lines, then respawns on the next call — every other backend keeps running untouched. Repeated failures trip a circuit breaker rather than retrying forever.
roostctl verify — The Parity Gate
Before any session config is ever flipped onto the hub,
roostctl verifyspawns each server directly and diffs its tools/list byte-for-byte against what roostd serves. The rule is absolute: every configured server must pass before a single Claude config changes.One-Command Revert
roostctl revertrestores the pre-flip direct-spawn configs from a timestamped backup in seconds. Because only new sessions read the flipped config, any session already open when something goes wrong was never affected in the first place.
Observability
A Quiet Hub Is Still a Legible Hub
JSONL Request Log, INFO on Every Transition
Every call is logged — timestamp, session, server, method, tool, duration, outcome — and every spawn, reap, refresh, and circuit open/close emits an INFO milestone. A watcher polling the log should never see minutes of silence while the hub is active.
roostctl status / top
A live per-server table: mode, PID, RSS, warm-or-cold, last-used timestamp, call count, and p50 latency — the same view whether you're checking one backend or auditing the whole fleet's memory footprint at a glance.
roostctl doctor
One command checks port availability, launchd registration, catalog snapshot ages, and file-descriptor headroom against the socket count a dozen sessions times 22 endpoints actually needs — the fast way to rule out infrastructure before debugging a tool call.
Validation
Measured, Not Estimated
22/22 Parity, Verified Three Times
roostctl verifypassed 22/22 across two integration runs plus once more inside the live config flip — tool names and input schemas identical to direct spawn for every server, combining to more than 1,000 tools total (courier alone contributes 128, trellis 111, beacon 86).~95% Memory Reduction, Measured
Pre-flip baseline on the same machine: 92 processes and 5.85 GB RSS at 4 sessions. Post-flip, roostd itself runs 59–83 MB RSS with a sample warm child (fulcrum) at 75 MB — one daemon plus a small warm set, at any session count, instead of 22 processes multiplied by every open session.
Recovery Verified: kill -9, Twice
Killing a warm child mid-call returns a clean JSON-RPC error and the very next call respawns it successfully. Killing roostd itself gets it back via launchd KeepAlive with no session restart required — both recovery paths were exercised directly, not assumed.
How It Works
Handshake, Spawn, Relay, Reap
Step 1: Handshake
A new session's tools/list for all ~22 servers is served straight from the catalog snapshot — no child process wakes up, no interpreter starts. Cold listings resolve in milliseconds instead of after 22 live handshakes.
Step 2: Spawn
The first real tools/call to a backend spawns exactly one child for it, with exponential backoff and a circuit breaker if it keeps failing. Every other session already connected shares that same child from this point forward.
Step 3: Relay
Requests multiplex through the backend's single SDK ClientSession, which owns its own monotonic request IDs — cross-session collisions are impossible by construction. Progress notifications route back only to the session that made the call.
Step 4: Reap
A backend idle past its TTL (30 minutes by default; longer for a keep-warm set like lattice and cortex) is reaped automatically. The next call respawns it — steady-state memory tracks actual usage, not configuration.
Technical Specifications
Under the Hood
Daemon (roostd)
- Python 3.12 + uv, built on the official
mcpSDK on both sides - Streamable HTTP frontend — one session manager per configured server, mounted at
/<server>/mcp - Loopback-only: binds
127.0.0.1:3462, nothing exposed beyond the workstation - One shared
ClientSessionper backend relays every forwarded call to its single child - Reserved paths:
/healthz,/status, loopback-only/admin/refreshand/admin/restart
- Python 3.12 + uv, built on the official
Lifecycle
- Lazy spawn on first tools/call only — never on tools/list
- Idle reap per server (default 30-minute TTL;
keep_warmoverride for the hot set) - Exponential backoff plus a circuit breaker on repeated spawn failures
- Catalog snapshot per server, invalidated by real-spawn diff, manual refresh, or watch-path staleness
- Per-backend concurrency semaphore (default 8) with a FIFO queue
CLI (roostctl)
init— imports existing stdio stanzas from~/.claude.jsonand the project.mcp.jsonstatus/top— per-server mode, PID, RSS, warm/cold, last used, calls, p50verify— the parity gate: diffs live tools/list against a direct spawn, server by serverflip/revert— swap Claude configs onto Roost, or restore the originals in secondsrefresh/restart/doctor— re-snapshot a server, bounce a child, or check port/launchd/fd health
Deployment
- Managed host process, not a container — must exec the fleet's macOS venv interpreters directly
- Default start: a launchd user agent with
KeepAlive— self-heals on crash, survives reboot --backgroundnohup-plus-PID fallback for machines without the Full Disk Access grant- PID file and JSONL logs under the stack's standard managed-services convention
- Escape hatch:
roostctl revertrestores direct-spawn configs from a timestamped fallback backup
Development
100% Built by Claude
Every tool in the Renkara fleet was built by Claude (Anthropic) working alongside a single human supervisor. Every line of code, every test, every deployment: AI-authored with human direction. The leverage factor across the fleet runs in the 20x–50x range, with individual sessions regularly exceeding 100x.
See the daily leverage records for per-task numbers across the full build history.