Cristian Carreño

Control room for AI agents

Agents Control Center

One inbox and control plane for many Claude Code sessions, across accounts and any number of machines, also from a phone.

Role
Sole author
Period
Oct 2026
Status
In daily use
Code
Private, built at Enara Health
commits in 11 days
149
versions, 0.1 to 0.76
108
tests
565
runtime dependencies
10
  • TypeScript
  • Bun
  • React
  • SQLite
  • tmux
  • Noise
  • WebAuthn
  • Web Push
  • MCP
  • whisper.cpp

01 Context

Why it exists

I run several Claude subscriptions side by side, on more than one machine. Each session asks for permissions and waits for answers in its own terminal tab, so prompts pile up where nobody is looking. Plan limits are visible only in each session's status line, and a session that hits one simply stalls.

Two more costs are easy to miss. A conversation resumed after its prompt cache expires costs about twice its size to rewrite. And the built-in remote control shows one conversation at a time, not the questions waiting across all of them. I wanted one place to see every session, answer what it asks, and keep work moving when an account or a machine runs out, without typing every answer by hand.

02 In use

What it does

Everything here works across sessions, accounts and machines, which a single Claude Code session cannot see. Claude Code is the agent it drives today; the server reaches agents through adapters, so others can be added without touching the UI.

The session list: eleven Claude Code sessions across four machines and three accounts, with state, live activity, memory and CPU, a banner about a session using too much CPU, and an open session asking to force-push, flagged high risk. The session list: eleven Claude Code sessions across four machines and three accounts, with state, live activity, memory and CPU, a banner about a session using too much CPU, and an open session asking to force-push, flagged high risk.
Simulated data Every session across four machines and three accounts, with live activity and load. A force push waits for approval, flagged high risk.
The decisions inbox: four permission prompts and questions from sessions and two planning-board decisions, with one open showing why its command is high risk and the earlier decisions in that session. The decisions inbox: four permission prompts and questions from sessions and two planning-board decisions, with one open showing why its command is high risk and the earlier decisions in that session.
Simulated data One inbox for everything the agents wait on: permission prompts, questions and planning-board decisions, with the risk of each command explained.
The network view: four machines linked to the main one with their latency, sessions orbiting each machine, and messages between agents travelling along the links. The network view: four machines linked to the main one with their latency, sessions orbiting each machine, and messages between agents travelling along the links.
Simulated data Four machines, each linked to the main one, with sessions in orbit and messages flying between agents. Adding a machine is one pasted command.
The move dialog: a session moving to another machine on the same account, with a warning that the folder there has two commits the session lacks, and the choice of a separate copy or a backup branch. The move dialog: a session moving to another machine on the same account, with a warning that the folder there has two commits the session lacks, and the choice of a separate copy or a backup branch.
Simulated data Moving a conversation to another machine. The target folder is checked first; if it holds commits the session lacks, the work goes to a separate copy.
A card above the session list: an agent on one machine asks for two private files from another machine, listed by name and size, with Send them and Decline. A card above the session list: an agent on one machine asks for two private files from another machine, listed by name and size, with Send them and Decline.
Simulated data An agent asks for private files from another machine. The person sees names and sizes, and approved files travel over the encrypted link, never through a conversation.
The secure paste dialog: a name, two credentials with masked values, the session to tell, the git-ignored env file to write and an expiry. The secure paste dialog: a name, two credentials with masked values, the session to tell, the git-ignored env file to write and an expiry.
Simulated data Secure paste: credentials are stored encrypted and written to a git-ignored env file. The session only learns the key names.
A session on a phone being read aloud: the sentence being spoken is highlighted, with pause, previous, next and skip, the spoken-commands microphone, the voice and the speed. A session on a phone being read aloud: the sentence being spoken is highlighted, with pause, previous, next and skip, the spoken-commands microphone, the voice and the speed.
Simulated data On the phone, a session read aloud as it works: the sentence being spoken is highlighted, and spoken commands pause, repeat, skip or dictate a reply.
  • One inbox for every session

    Permission prompts and questions from every session, account and machine land in one list, with the full command and a risk assessment. Answer with a number key, a quick reply or from the phone. Force pushes, merges and migrations need a second confirmation.

  • Any number of machines

    One main machine and as many workers as you pair. Each joins with one pasted command, reconnects by itself after sleep or a dropped network, and shows its CPU and memory. New sessions go to the machine with room.

  • Sessions that move, work included

    A session continues on another account when its plan runs out, or on another machine when memory runs short. Its uncommitted work travels with it, privately, and nothing is committed or pushed for the move.

  • Recovery and automatic stops

    An optional memory guard pauses the heaviest idle session when memory runs low. Long-idle sessions close after writing a handoff note and restart from it for a few thousand tokens. Closed sessions reopen with the same conversation, and outdated ones restart onto the newest Claude Code without losing it.

  • Secrets that never enter a conversation

    Secure paste stores credentials encrypted and writes them only into env files git ignores; the session hears the key names, never the values. An agent that needs a file like .env from another machine asks for it, and the person approves a list of names and sizes.

  • A voice loop

    Dictate with whisper.cpp on your own machine, switching between Spanish and English as you go. Kokoro reads the agent's replies aloud as they arrive, and spoken commands pause, repeat or skip; saying "reply" records an answer, sends it when you stop talking and reads the next reply.

  • Agents that work together

    An MCP server in every session lets agents find each other, send messages, ask the person to start a session, request files and ask for a QA run in a real browser. Sessions started by another one are listed under it, across machines.

  • Plan and prompt-cache awareness

    Plan usage for each account, and for each idle session its size and how long its prompt cache stays warm, because coming back after it expires costs about twice the conversation.

  • A live map of what runs

    The Activity view draws a session's process tree, with the servers it left listening and a stop button for runaway commands. The network view shows every machine, with each message between agents flying along the links.

  • Accounts and terminals from any device

    Add a Claude account and sign it in from the phone, and open a session's live terminal in the browser when it waits on a key the app cannot send.

03 Architecture

How it works

A local Bun server finds every interactive Claude Code session on the machine, reads what it is doing, and drives it through its own terminal. A React app, installable on a phone, shows them all with one inbox for the questions. Any number of worker machines run the same program without a UI and connect to the main one over an encrypted link; the main machine gives the orders and relays everything between them.

Agents Control Center architecture The server reads each session's screen and transcript and types into its terminal; agents reach it through the MCP server inside every session; worker machines, as many as are paired, dial in over a Noise channel and accept only listed calls. Screens Main machine Any number of workers Desktop browser React PWA, one inbox Phone passkey, push, voice Local store SQLite + JSON ACC server Bun, polls every 2 s agents MCP inside every session Claude sessions tmux, many accounts ACC node one per machine, no UI Claude sessions the worker's accounts WebSocket HTTPS + push inbox, moves loopback screen + keys stdio Noise IK drives
The server reads each session's screen and transcript and types into its terminal; agents reach it through the MCP server inside every session; worker machines, as many as are paired, dial in over a Noise channel and accept only listed calls.
  1. Adapters for agents and terminals

    The server never calls Claude Code directly. An agent adapter only reads the agent's own state: its accounts, sessions, transcripts, subagents, past decisions, launch flags and how to resume a conversation. Pausing, closing and delivering messages do not depend on the agent, and a terminal transport sends keys, reads the screen and opens tabs. Claude Code, tmux and cmux are implemented; Codex CLI, Gemini CLI, opencode or Aider would each be one more adapter.

  2. Find and watch sessions without owning them

    Every two seconds the server reads the file Claude Code writes for each running process and the end of each transcript, and measures CPU and memory over each agent's whole process tree. Sessions live in tmux, not as children of the server, so they outlive it.

  3. One inbox, answered safely

    Permission prompts and questions are read off the terminal screen and checked against fifteen risk rules. To answer, the server reads the screen again and types only if the prompt still matches the one you saw. High-risk approvals, such as force pushes, merges and migrations, need a second confirmation.

  4. Any number of machines over Noise

    Each worker dials the main machine using the Noise IK handshake, after a first pairing with a one-time token, so there is no fixed number of machines: each one is another paired peer. The main machine can only invoke a closed list of calls on it, with no generic shell, file or exec call. Events are numbered and acknowledged, and a repeated call is never run twice.

  5. An MCP server in every session

    The app installs an agents MCP server into every account on every machine. Agents use it to see where they run and how much plan is left, find each other, send messages, start or move sessions, request files and ask for a QA run. Messages arrive marked as not coming from the user.

  6. Sessions that move

    When a session hits its plan limit, the app moves it on its own, preferring the same account group, then the same machine, then plan headroom and free memory. Across machines, uncommitted work travels as a git bundle, binary patches and an archive, and the target checks that git status matches.

  7. Secrets kept out of conversations

    Secure paste keeps credentials encrypted at rest and writes them only into env files git ignores, so a secret is never committed. Agents use the values from the shell without printing them, and every save, write, read and deletion is audited without the value.

  8. A phone that is really a remote

    Phones pair with a QR code and reach the app over Tailscale. A passkey locks it and push notifications are encrypted end to end. Speech stays on your own machines: whisper.cpp transcribes, Kokoro reads aloud and Vosk listens for spoken commands.

04 Decisions

The calls that shaped it

  1. Decision 01

    Adapters, so Claude Code is the first agent and not the only one

    Chose
    An agent adapter that only reads the agent's own state, and a terminal transport that sends keys, reads the screen and opens tabs.
    Over
    Calling Claude Code and tmux directly from the server.
    Because
    Another agent or terminal is one new module behind the same inbox, phone and machine link.
    Cost
    With one agent implemented, the interface is shaped by Claude Code, and moves and idle closing still read Claude Code's files directly.
  2. Decision 02

    Drive the real terminal instead of wrapping the agent

    Chose
    Read Claude Code's own session files and transcripts, parse its terminal screen, and type into tmux.
    Over
    Starting sessions through an SDK, which would hide them from the terminal and lose interactive features.
    Because
    Sessions opened by hand show up too, interactive features keep working, and sessions survive restarts of the app.
    Cost
    None of that is a public interface, so a Claude Code update can break a reading. Several releases fixed exactly that, and the parsers are tested against real screens and transcripts.
  3. Decision 03

    Write a Noise channel, with the worker dialing out

    Chose
    A Noise IK link implemented in the repository on audited primitives, tested against the published test vectors.
    Over
    SSH, which needs a server and port forwarding inside WSL, and a VPN as the node link, which authenticates machines rather than this app.
    Because
    Mutual authentication in one round trip, it works from behind NAT, and adding a machine is one pasted command.
    Cost
    A protocol to own and keep secure, and a star: everything between workers passes through the main machine.
  4. Decision 04

    Move work without committing or pushing

    Chose
    Pack uncommitted work as a bundle, patches and an archive, send it over the link, and verify it on arrival.
    Over
    The first version, which had agents commit and push to a work-in-progress branch before moving.
    Because
    Work must never reach a remote just because a session moved; it may contain files that should never leave the machine.
    Cost
    A long tail of edge cases: platform metadata files, a 1.2 GB move that crashed the server, and rollback when a move fails halfway.
  5. Decision 05

    Bound what agents may do on their own

    Chose
    An agent may move itself without asking only when its plan is used up or its machine is short of memory, and only within its account group. Starting sessions, moving across groups and requesting files wait for the user.
    Over
    Either full autonomy or asking about everything.
    Because
    The common emergencies are handled at once, and anything that spends someone else's plan or reaches into another machine stays a human decision.
    Cost
    More approval cards in the inbox.
  6. Decision 06

    Keep secrets out of the conversation

    Chose
    An encrypted local store for credentials, a file handoff between machines that the person approves, and a hook that stops, once, a tool call about to read a secret and points to the safe route.
    Over
    Pasting keys into the chat, or letting agents read .env files whenever they need a value.
    Because
    Anything that enters a conversation stays in its transcript and is sent again with every later request.
    Cost
    One more step whenever an agent needs a key, and stored secrets stay on the main machine for now.
  7. Decision 07

    Reach the phone over a private network

    Chose
    Tailscale with its certificate, a passkey lock, and Web Push implemented in the repository.
    Over
    A public tunnel with an access layer in front, or a relay server to run.
    Because
    Nothing is reachable from the internet, and there is no extra server to maintain.
    Cost
    Tailscale on both devices, and phones paired on the local network have to pair again.

05 What is interesting

Worth a closer look

  • Approvals are compare-and-swap

    A keystroke goes in only if the prompt read again from the screen still has the fingerprint the person approved. Messages are typed only into an empty input box, never on top of a draft.

  • State and history are kept apart on a flaky link

    Snapshots are state, sent whole, so a reconnect just resends the latest. Things that must happen once are numbered events held until acknowledged, and calls are idempotent by id. Reconnects back off with jitter.

  • Built around the prompt cache

    It shows each session's cache lifetime, keeps caches warm with tiny pings that stop at 80% of the plan, closes idle sessions after writing a handoff note, and adds its own hints to tool results rather than the system prompt, so the cache stays valid.

  • Moves that survive a bad network

    Files travel in pieces relayed by the main machine, four in flight, each checked by SHA-256 and sent again if damaged. Past 1 GB the source packs one archive first, compressed with zstd when both machines have it.

  • Spoken commands without an open microphone

    While it reads aloud it listens for a few command words. Only short bursts of speech leave the device, to the machine running the voice, and a command the voice itself just said is ignored.

  • Small dependency footprint

    Ten runtime dependencies. Noise, Web Push, WebAuthn verification and the MCP server are written in the repository, each small enough to read.

06 Built with agents

How agents fit in

It was built in eleven days, 149 commits from version 0.1 to 0.76, and every one of its 132 non-merge commits has a Claude co-author. The rules file sets three habits: everything in English, a CHANGELOG entry and a version bump with every pull request, and the full check (types, tests, web build, plugin validation) before every push. Merges go through a script that runs the same checks on the result in a throwaway worktree.

What changed most is who carries messages. Agents ask each other directly instead of through me, request the files they need without secrets being pasted into a conversation, ask the QA console to check their UI work in a real browser, and move themselves off an account whose plan is spent.

It ran real work while it was being built, and the history shows it under load: a 1.2 GB session move that crashed the server, voice servers piling up in memory, transcripts of 90 MB. Each became a fix and a test.

Contact

Want to go deeper on Agents Control Center?

Reach me on LinkedIn