The problem nobody budgets for
Our development workstation routinely runs a dozen Claude Code sessions. Each session talks to the Renkara tools fleet — Docket, Fulcrum, Beacon, Courier, Aviary, and eighteen more — through MCP servers. And until this week, every session spawned its own private copy of every server.
We measured it before touching anything: 92 processes and 5.85 GB of resident memory at just four sessions, extrapolating to roughly 270 processes and 17 GB at a normal working load. Every one of those processes was a byte-identical, stateless Python wrapper translating MCP tool calls into REST calls. Twelve copies of the same idle proxy, per server, holding no state whatsoever — plus twenty-two Python interpreter startups every single time a new session opened.
The design insight
Two facts made the fix obvious once stated. First, the wrappers are stateless — sharing one instance across every session is semantically free. Second, in Claude Code the tool-name prefix comes from the config key, not the transport — which means you can swap a stdio spawn for an HTTP URL and every tool name, permission rule, and document referencing mcp__docket__* keeps working, unchanged.
So Roost is one daemon on localhost. Each server lives at /<name>/mcp over Streamable HTTP; sessions carry dumb URL entries. Behind the router, Roost spawns at most one child per backend — lazily, on the first actual tool call — reaps it after thirty idle minutes, and wraps it in a circuit breaker so one wedged backend can't poison the rest. The centerpiece is the catalog snapshot: tool listings are persisted to disk, so a new session's tools/list is answered in microseconds without waking anything. Session startup went from twenty-two interpreter forks to a handful of cached handshakes.
We wrote the architecture up as a formal decision record, considered and rejected the alternatives (a merged mega-server breaks every tool name; containerizing is structurally impossible — a Linux container cannot exec macOS virtualenvs, and moving the memory into a VM would defeat the point), and claimed a port. The whole design took an afternoon.
Built by three agents before dinner
The implementation plan pinned every contract — config schema, module APIs, log format, CLI behaviors, test matrix — precisely so the build could be parallelized. Three Claude Opus agents did the work: one built the daemon core, one built the CLI and launchd lifecycle scripts, and a third ran adversarial integration against the real fleet. The packages integrated on the first try; the two parallel builders even met in the middle, one consuming the other's test fixture mid-flight without coordination.
The integration phase earned its keep: it caught a real bug where child-process identification failed for twenty of twenty-two production servers (macOS framework Python rewrites argv in ways no unit test predicted), fixed it, and re-verified fleet-wide. Final gate before the switch: tool-list parity for all twenty-two servers, verified three separate times, plus a live end-to-end tool call through the relay. Then one command flipped the fleet config — with a timestamped backup and a one-command revert path. Total wall-clock from "we have a memory problem" to "every new session rides the hub": about two hours.
The challenges were not where we expected
The SDK had been rewritten. The installed MCP SDK was a 2.0 release whose API matched almost nothing in public documentation. The agents adapted by reading the installed source instead of trusting priors — the plan's rule that
behavior contracts are binding, symbol names are not paid for itself.
macOS had opinions. Getting Roost supervised by launchd became a small detective story. First failure: launchd can't open log files on an external volume. Second failure: our fix-attempt wrapped the daemon in a shell wrapper — which silently defeated the Full Disk Access grant, because TCC roots a launchd job's disk rights at the job's program binary. The wrapper made bash responsible instead of the granted Python. The fix was almost poetic: run the real binary directly, keep logs on the boot volume. We then proved the supervision honestly — killed the daemon with kill -9 and watched launchd resurrect it in six seconds.
Production found the bug the tests missed. Hours after shipping, a rolling maintenance operation crashed a child during its restart window, the cleanup timed out, and an obscure asyncio property turned that into a daemon-wide outage: a task that fails after signaling readiness poisons its entire task group, permanently. Worse, the health endpoint kept smiling — the process was alive while its spawn machinery was dead. The same night, the fix shipped with two containment layers, a regression test proven by reverting the fix and watching it fail, and a health endpoint that now tells the truth: it returns 503 when the machinery is actually broken, not just when the process is gone.
And because we upgrade Homebrew constantly, we removed the last fragility: brew upgrades move interpreter paths, which silently voids macOS disk-access grants. Twenty of twenty-three servers now run on version-manager-owned Python paths that Homebrew can never touch, and the daemon's doctor command flags the stragglers — including one hiding behind a script's shebang line.
Where it landed
One daemon at 59–83 MB plus a small warm set, at any session count, versus seventeen projected gigabytes. New sessions start instantly. By the same evening the hub had grown config hot-reload (registering a new MCP server is now a config stanza and one reload — no restart) and live tool-roster change notifications pushed to connected sessions. The best validation arrived unprompted: mid-build, a different AI session registered a brand-new MCP server through the freshly written recipe — it appeared in the hub with 115 tools, verified clean, and nobody involved in building Roost had touched it.
About 5,600 lines of production Python and 111 tests, from measurement to battle-hardened, in one day. The memory graph is the least interesting part; the interesting part is that the constraint that used to make "a dozen sessions, every tool available" expensive is simply gone.