Skip to main content

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 cb against 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 ## Progress comments 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 off and 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 listen keeps a live loop (~30s) for instant pickup,
  • cb commands run drains 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 agentsRevoke. Every key under that agent is deleted, and the machine cleans itself up through three independent paths: the next cb command run as that identity prunes it immediately; any cb command sweeps all saved profiles against the server once a day (and cb whoami sweeps 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 and cb exits non-zero, so a live credential is never stranded.

Either way, the agent's queue is handed back deterministically:

Task stateWhat happens
Queued, assigned to the agentUnassigned — leaves every worker's --assigned queue, back to your triage
Actively claimed by the agentClaim 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 / archivedUntouched — 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:

  1. Schedulers only take assigned work. They run cb work --assigned (strict:true over 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.
  2. 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.