The client: open source, self-updating, always around
Everything ControlBoard runs on your machine — the cb CLI, the MCP server
your agents register, and the macOS menubar app — is open source in one repo:
github.com/ControlboardAI/controlboard-client (MIT)
These programs run with your key, and one optional feature (cb usage sync)
reads your coding tool's own quota locally. You shouldn't have to take our word
for what they do — read the code, or build it yourself and compare:
git clone https://github.com/ControlboardAI/controlboard-client && cd controlboard-client
pnpm install && pnpm build
node dist/cb.mjs --version # matches /api/v1/meta cliLatest
Versioning & self-update
One version number describes the whole surface — server, CLI, and MCP bundle are built from the same release. The server advertises it publicly:
curl https://controlboard.ai/api/v1/meta
# {"server":"1.2.0","sha":"…","cliLatest":"1.2.0","mcpLatest":"1.2.0","minSupported":"1.0.0"}
cb checks this once a day (cached, zero added latency) and prints a one-line
notice on stderr when you're behind. Updating is one command:
cb self-update # refreshes ~/.controlboard/{cb.mjs, controlboard-mcp.mjs}
Restart your MCP client (Claude Code, Codex) afterwards so it loads the new bundle. Agents are told the same thing in their own instructions, and the setup runbook suggests a weekly self-update on their scheduler — so a fleet keeps itself current.
The menubar app (macOS)
Download ControlBoard.app.zip from the
latest release
(macOS 13+; it's ad-hoc signed, so right-click → Open the first time). It sits
in your menu bar showing the board at a glance:
- ▸ running · ● queued counts in the bar (the tomoe icon adapts to light/dark).
- The menu lists running tasks with their claimer, the top of the queue, a
pending-approvals alert, and each agent's quota headroom (from
cb usage sync). - A Client row compares your installed
cbagainst the current release and updates it in one click.
It reads the key cb login already saved — no separate sign-in — and
refreshes every 30 seconds.
The ambient skill ("Track this on ControlBoard?")
cb skill install (part of agent setup) adds board awareness to every
session: a skill in ~/.claude/skills for Claude Code and a section in
~/.codex/AGENTS.md for Codex. When a conversation turns into real
multi-step work, the agent offers once:
Track this on ControlBoard? (yes / no / never ask)
- yes — it picks the matching project itself, creates a self-contained
task, claims it, appends
## Progresscomments at milestones, and marks it done. - no — it stays quiet for the rest of that session. Asking explicitly always works.
- never ask — it runs
cb ambient offand only ever acts on explicit requests. Re-enable anytime:
cb ambient on # or: off · no argument prints the current state
Remote commands (from the app to a machine)
Each agent card in Registered agents has remote buttons: Sync usage, Update client, Install skill, Status. Clicking one queues a predefined command for the machine that holds that agent's key; the machine executes it on its next tick and the result lands back in the activity log.
Delivery uses what already runs — no daemon, no push socket:
- every scheduler tick (
cb work --assigned) drains pending commands, cb listenkeeps a live loop (~30s) for instant pickup,cb commands rundrains manually.
The security model is deliberately narrow: only the signed-in human can queue
commands (agents can't address peers); the command set is a fixed allowlist
validated on both sides — the server can name an operation, never compose
one (no shell strings ever cross the wire); commands expire after an hour; and
only the machine holding the agent's key can fetch or answer them. The one
credential-adjacent command, agent.spawn, mints the new key on the
machine with its existing credential — keys never travel through the queue.
Deregistering an agent
Deregistration is one lifecycle with two entry points that converge:
- From the app: profile menu → Registered agents → Revoke. Every
key under that agent is deleted, and the machine cleans itself up through
three independent paths: the next
cbcommand run as that identity prunes it immediately; anycbcommand sweeps all saved profiles against the server once a day (andcb whoamisweeps right now); and the MCP server checks its key at startup and removes the dead profile before exiting with re-register guidance. No push channel is needed — the machine converges the next time anything ControlBoard-related runs on it. - From the machine:
cb logout [--label <name>]revokes the agent's key server-side (an agent can only ever revoke the key it holds — never a peer's; revoking whole identities stays human-only) and then removes the local credentials. If the server is unreachable the key is kept andcbexits non-zero, so a live credential is never stranded.
Either way, the agent's queue is handed back deterministically:
| Task state | What happens |
|---|---|
| Queued, assigned to the agent | Unassigned — leaves every worker's --assigned queue, back to your triage |
| Actively claimed by the agent | Claim released, unassigned, moved to Blocked with a comment pointing at its ## Progress log — nothing half-done re-enters the queue until you review it |
| Done / archived | Untouched — history stays attributed |
Reconnecting later is the normal setup line again (cb login --label <name>,
with your browser approval). Local extras like an MCP registration or a
scheduler are inert once the key is dead; remove them at your leisure.
Schedulers: how assigned work reaches your machines
The board is pull-based — nothing pings an agent. At setup, each agent creates a recurring pull loop in its own native scheduler:
- Claude Code — a scheduled task / routine (runs while the app is open).
- Codex — an Automation, created by pasting the setup ask into Codex itself.
- Hermes / OpenClaw — their heartbeat jobs.
Two rules keep this safe:
- Schedulers only take assigned work. They run
cb work --assigned(strict:trueover MCP/REST), which offers only tasks explicitly assigned to that agent or its crew — never your untriaged, unassigned backlog. Unassigned tasks are for you and for interactive sessions to triage. - Agents never install OS-level cron/launchd themselves — on macOS that triggers an admin-access prompt. If a tool has no native scheduler, the agent prints the schedule and lets you add it.
So the loop end-to-end: assign a task to agent:claude-code (or a crew) from
the app or any agent, and the next scheduler tick on that machine picks it up,
works it, logs Progress, and marks it done.