cd /news/ai-agents/i-run-5-claude-code-clis-from-one-co… Β· home β€Ί topics β€Ί ai-agents β€Ί article
[ARTICLE Β· art-82654] src=dev.to β†— pub= topic=ai-agents verified=true sentiment=Β· neutral

I run 5 Claude Code CLIs from one control plane. Here's the plumbing.

A developer built a control plane that supervises multiple Claude Code CLI processes from a single dashboard, enabling five agents to run concurrently without becoming the message bus. The system uses a Flask app to spawn and monitor claude subprocesses, streams output via Server-Sent Events, and handles browser connection limits and Windows command-line truncation by writing context to files. The developer chose to drive the CLI rather than the API to keep costs on the existing subscription and preserve the agent's built-in capabilities.

read12 min views1 publishedAug 1, 2026

Claude Code is great at being one agent in one terminal on one repo. The trouble starts at

agent number three. I had five projects going, five terminal windows, and every morning I'd

sit down and spend ten minutes just working out which agent was mid-task, which was blocked

waiting on me, and which had quietly finished an hour ago.

The thing nobody tells you about running multiple agents is that you become the message bus. Every agent routes through your attention. Scale that to five and the agents aren't

The obvious move is to hit Anthropic's API directly and build your own agent loop. I

deliberately didn't. Two reasons.

First, cost and trust: driving the claude

CLI means the work runs on your existing Claude

subscription, not a metered API key. It costs exactly what Claude Code costs you today, and

nothing routes through a server I run. For a tool people install on their own machine, "your

subscription, your keys, your data" isn't a feature β€” it's the whole deal.

Second, the CLI already is the agent. It has the tool-use loop, the permission model, the

file editing, the MCP support. Re-implementing that against the raw API is throwing away the

best part. So the design flipped: instead of being the agent, the control plane spawns and supervises claude CLI processes and gets out of the way.

That one decision β€” supervise a subprocess instead of call an API β€” is what made the rest of

this interesting.

The core is a Flask app. Each project maps to a claude

process. When you dispatch a task,

the server spawns (or reuses) that project's process, streams its stdout, and pushes the

tokens to the browser over Server-Sent Events. The dashboard is just N of those live feeds

tiled on a grid, each with a status: working, waiting on you, or idle.

Sounds simple. Here's where it got complicated -

The dashboard wants a live stream per project. Open six projects and the seventh tile just…

hangs. That's not a bug in your code β€” it's the browser. Chromium caps concurrent

connections to a single origin at six, and a long-lived SSE stream holds one open the entire

time. Five streaming agents plus one stray reconnect and you've hit the ceiling.

The fix is to treat streams as scarce: open an SSE connection only for a project that's

actively running

, and close it the moment the turn completes rather than holding it

open "just in case." Idle projects don't get a stream at all β€” they get polled cheaply. Once

streams are tied to activity instead of to tiles, the cap stops mattering.

To give an agent context, the natural instinct is to pass it as a CLI argument. On Windows,

cmd.exe

truncates the whole command at 8,191 characters, silently. A decent system prompt

plus a project's memory blows past that instantly, and you get a corrupted invocation with

no error β€” the agent just starts wrong.

The fix: never pass context inline. Write it to a file and hand the CLI

--append-system-prompt-file . Same trick solves a second problem β€” the shell mangling quotes

and newlines in a big prompt. File in, path as the argument, done. Obvious in hindsight;

cost me an evening.

You can spawn a fresh claude

per turn, or keep one process alive across turns. Fresh-per-turn

is clean but slow and forgetful. A persistent process is fast and keeps its working state β€” so

that's the default. The sting: session-teardown logic that you assumed ran per-turn now only

runs when the process actually exits. Anything you hung off "end of turn" β€” writing memory,

releasing a stream, updating status β€” has to be re-anchored to the real lifecycle events, or

it silently stops firing. Persistent processes are worth it, but they move where "done"

happens, and every assumption downstream of "done" has to move with it.

The plumbing above makes many agents runnable. It doesn't make them good. The real pain with

long-lived projects is that every session starts from zero β€” the agent re-reads the repo to work

out where you left off, re-derives decisions you already made, and burns turns rebuilding context

you both had yesterday. Solving that, not the tiling, is what makes running many agents

sustainable instead of just possible. It's the piece I've iterated on the most, and the piece

that took the longest to get right. Here's the pipeline.

Write β€” summarize from the real transcript, not the screen. At the end of a session (and,

because the process is persistent, at checkpoints during it β€” otherwise a hard kill loses the

thread), a cheap model condenses what happened into a few lines: decisions made, what's done,

what's next. The detail that matters is the source. The stdout you see scrolling in the

terminal has already dropped tool results and the model's own reasoning. The full-fidelity

record is the CLI's on-disk transcript (.jsonl

), so the summarizer reads that and only falls

back to the stdout tail if it has to. Summarize from the screen and your memory is missing

exactly the parts that mattered.

Store β€” two regions, different owners. The per-project MEMORY.md

is deliberately split. On

top, a small human-curated index β€” byte-preserved, the machinery never rewrites it. Below, a

sentinel-delimited managed region that only the write pipeline touches. Keeping the trustworthy

hand-written notes physically separate from the auto-captured churn is what lets you actually

rely on the file instead of babysitting it. Writes are per-project locked and atomic, so five

agents checkpointing at once never corrupt it.

Read β€” deterministic, not model-chosen. At the next dispatch a fixed slice of that memory is

injected as a read-floor before the agent does anything β€” it doesn't get to decide whether to

look. So it opens already knowing the state of the project instead of spelunking for it.

Trim β€” less is actually more. This is the counter-intuitive one. There's a hard byte budget

before the model effectively stops absorbing the injected context; push past it and recall gets

worse, not better. So the trim is ruthless and line-keyed β€” a lossless mechanical floor with a

model-driven condense above it β€” and everything it evicts goes to a permanent, searchable

archive that is never deleted. Cold storage, not a trash can: the working memory stays small

and sharp, and nothing is actually lost.

Two things I got wrong first, and they share a root cause. One: letting the agent freely rewrite

its own memory β€” it drifts, bloats, and within a week you can't trust a line of it. Two: assuming

more memory is better β€” see the trim. Both mistakes come from treating memory as a scratchpad the

agent controls, instead of a managed store the control plane keeps on the agent's behalf.

Once I flipped that ownership, the whole thing got trustworthy.

Once the control plane supervises processes and remembers state, a new thing becomes possible:

agents that keep working after you close the tab. A scheduler fires tasks on a cron; a

"charter" agent gets a standing goal and works it unattended in reversible steps, asking before

anything irreversible. That's the payoff of not being the message bus anymore β€” the work keeps

moving while you're away from the keyboard, and you check in from your phone to answer the one

thing that actually needs you.

The question people ask next is "can two agents work on the same project at once?" The shallow

version is about file conflicts, and it has a boring answer: give each agent its own git worktree

and branch, and merge at the end.

But the interesting version isn't conflict β€” it's coordination. Two agents working on

interdependent features, not the same files. Agent B rebuilds something Agent A just finished.

Agent B picks an approach that A's just-landed change made obsolete. They never touch the same

line; they're just blind to each other.

And here's the trap: worktree isolation makes that worse. Isolation buys edit-safety by

putting each agent in its own checkout, so a sibling's landed commit is less visible, not more.

Safety and awareness pull in opposite directions, which means you can't design one without the

other.

I sat down to design this properly and the useful surprise was that the missing piece wasn't

infrastructure β€” it was delivery. The substrate was already there: a durable append-only

message bus, a shared knowledge store, SSE fan-out, a reconciler daemon pattern, and the same

read-floor injection the memory system uses. What didn't exist was the semantics on top: agents

publishing intentions ("about to touch the SSE slot manager") and completions ("landed

it, sha abc123, these paths"), and β€” the hard part β€” those events being pushed into a sibling's

context while it's mid-work, instead of sitting in a bus nobody polls.

So I built it. Two pieces, and they have to exist together.

Awareness. Agents publish intentions and completions to a per-project bus, and the server

does the publishing on their behalf wherever it can β€” deriving a completion from a commit's

touched paths rather than trusting an agent to remember a protocol. Siblings receive it as a

SIBLING ACTIVITY

block in the read-floor, ranked by target-overlap: how much what you're

touching intersects what they're touching. The gate matters more than the mechanism β€” an

unfiltered firehose just teaches agents to ignore the section, exactly like a too-chatty

notification. Top-3, overlap-ranked, live agents only.

Isolation. Each concurrent agent gets its own git worktree and branch, with merge-back on

completion. The scoping detail I like most: only the 2nd+ concurrent agent is isolated. A

project with one agent β€” the overwhelmingly common case β€” pays nothing at all. Complexity that

only materializes when you actually need it.

Two rules ended up load-bearing, both learned the boring way: conflicts escalate and are never auto-resolved, and

The thing I got wrong first was a race in the coordination state file. Two threads β€” the

awareness daemon and a dispatch β€” both did read-modify-write on the same JSON, and dedup records

silently vanished. Classic, and worth saying out loud because "agents coordinating" sounds like

an AI problem and the actual bug was a missing lock and a non-atomic write. Most of this work is

ordinary systems engineering wearing a novel hat.

What's still off: the third delivery rung. A sibling landing a change on paths you declared

intent to modify could interrupt your turn β€” the mechanism is built, but it ships default-off

until I've watched the quieter rungs behave. Interrupting a working agent is the kind of feature

that's very easy to regret, and I'd rather earn it with evidence than assume it.

Here's the part I haven't solved yet, and it might matter most. Right now an agent hands the

turn back to you too eagerly β€” it hits the first fork or the first thing it's unsure about and

bounces the decision up. When you're supervising five of them, that's the message-bus problem

sneaking back in through the side door: you're no longer routing tasks, but you are the

completion-checker for every half-finished turn.

The direction I'm building toward is an agent that pushes to genuinely complete a task β€”

exhausts the reversible, obvious steps on its own and only comes back when it hits something that

truly needs a human: an irreversible action, a real ambiguity, a missing decision. Not reckless

autonomy β€” bounded autonomy, with the same "ask before anything irreversible" rule the standing

agents already follow. A finished turn you can review beats a half-finished turn you have to

unblock. It's not shipped yet; it's the next thing on the list, and I think it's the difference

between "many agents you babysit" and "many agents that actually carry work."

It runs on your machine, so if the machine sleeps, the agent sleeps β€” and it tells you it did.

There's no always-on hosted tier, on purpose: I didn't want to sit in the middle of your API

traffic or your data. It's local-first, and that cuts both ways.

I bundled all of the above into an open-source tool called Clayrune (MIT). If you want to

see the shape without installing anything, there's a click-through demo:

πŸ‘‰ [https://clayrune.io/demo](https://clayrune.io/demo) β€” and the source (Flask, the subprocess supervisor, the memory

pipeline) is at [https://github.com/ronle/clayrune](https://github.com/ronle/clayrune).

I started this by saying that with many agents you become the message bus. The control plane

fixed the obvious half β€” I'm no longer the thing routing tasks and remembering state. The

coordination layer took a real bite out of the harder half: the agents aren't wholly blind to

each other anymore, so cross-agent context doesn't have to route through my head.

But I want to be careful not to overclaim, because the interesting part is what didn't get

solved. Agents now know what their siblings are doing. They're still not very good at deciding

what to do about it β€” "defer, adapt, or ask" is a judgment call, and right now that judgment is

a paragraph of context and a hope. The awareness plumbing turned out to be the tractable half;

the reasoning on top of it is wide open. And the related problem I mentioned earlier β€” agents

that push a task to genuine completion instead of bouncing every fork back at you β€” is still

ahead of me.

So the direction I'd point at isn't "run more agents." It's that the bus should terminate at

other agents, not always at a human β€” and then those agents should be good enough at reading

it that you don't have to referee. The first half is buildable today. The second half is the

actual frontier.

If you're running more than a couple of agents, I'd genuinely like to hear how you're handling it β€” because I don't think one dashboard is the last word on this.

── more in #ai-agents 4 stories Β· sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/i-run-5-claude-code-…] indexed:0 read:12min 2026-08-01 Β· β€”