Cortex Hubmulti-machine agent orchestrationmerged to master · 2026-10-17
Write the plan once. Let every machine work it.
A lead agent turns a feature into a graph of tasks in one call. Workers on your Macs and Linux boxes pick them up — each in its own git worktree, with Claude, Codex, Gemini or Antigravity. When an engine runs out of quota, the work moves on with its commits. Nothing waits silently, and every result comes back for review.
Replay
Watch one plan cross three machines
Eight tasks, three workers, one quota limit, one rejected review and a human gate at the end. Step through it or let it play.
The lead writes the plan
The plan is illustrative. The message names, route strings and 📋 notice texts are the ones the hub and the worker exchange; the quota handoff follows the worker's live test.
How it works
From one call to merged work
The lead plans, the hub decides, workers execute, and nothing reaches “done” without a verdict.
-
01
The lead writes the plan
Claude Code — or any MCP client — sends the feature as a single
cortex_task_tree_createcall: keys anddependsOn, thefileseach task will touch, the engine and model per task, a fallback chain, an optional pinned machine, and gates. The hub rejects cycles before anything runs.cortex_task_tree_create(tasks: [ { key: "BILL", title: "Billing v2", kind: "group" }, { key: "SCHEMA", parentKey: "BILL", title: "Invoices table + migration", files: ["db/schema/"], executor: "claude", model: "opus", priority: "high" }, { key: "PDF", parentKey: "BILL", title: "Invoice PDF renderer", files: ["lib/pdf/"], executor: "agy", model: "claude-sonnet-5-5-low", fallback: [{ executor: "agy", model: "gemini-4-argon-low" }] }, { key: "API", parentKey: "BILL", title: "Invoice endpoints", dependsOn: ["SCHEMA"], files: ["api/billing/"], executor: "codex" }, // HOOK, UI, DOCS, E2E … { key: "SHIP", parentKey: "BILL", title: "Ship billing v2", kind: "gate", approver: "user", dependsOn: ["E2E", "DOCS", "PDF"] } ])
-
02
The dispatcher decides — deterministically
Assignment is a pure function,
selectAssignments(tasks, workers, quota, now), so every decision can be reproduced in a test. A task is ready when its dependencies are done and no running task holds a path under the same prefix. Priority goes first; a task returns to the machine that last ran it; pins and declines are honoured; routes that are out of quota are skipped. -
03
A worker runs it in its own worktree
worktree→setup→engine→testCommand→commit→push→PR→reviewEach task gets
~/.cortex/worker/<agentId>/wt/<taskId>. The worker runs the optional setup command, then the engine, then the test command — keeping the last 200 lines — commits<key>: <title>, pushes, and opens a PR withgh, or hands back a compare URL whenghisn't there. -
04
Review closes the loop
A finished task is in review, not done. The lead hears about it on its next Cortex tool call and gives a verdict.
approveDone. Dependents become ready.reject + feedbackBack to the same branch, feedback incontext.reviews.2 strikesTwo rejections or two failures escalate to Needs you. -
05
Gates hold the line
A gate is a node in the graph with an approver.
approver: "lead"closes withcortex_task_review.approver: "user"closes only from the dashboard — the MCP tool refuses it — so an agent cannot wave its own release through.
Quota
Running out of quota is a routing event, not an outage
Every task carries a chain of routes. A limit on one machine, engine and model sends the task down the chain — with its commits — and ends at the project default before it ever waits.
RESOURCE_EXHAUSTED. The worker commits WIP PDF: quota handoff and pushes.wait_until. The lead is told once; the dispatcher retries when the window resets.Recorded where it happened
A limit is stored per host × executor × model, and a model ending in * covers a family. The worker's modelScope widens one hit to claude-sonnet-5-5-*, so siblings aren't retried blindly.
Nothing lost on the way
The worker commits and pushes before it reports the limit. The next engine — on this machine or another — picks up from that commit instead of starting over.
Never silently stuck
A task that can't run says why: capacity with a time, or no_worker naming the routes nobody online can serve. Each reaches the lead once, not every tick.
… tail of any Cortex tool result the lead receives
---
📋 Conductor:
E2E "Billing e2e tests" is waiting for capacity: every route is out of quota until 2026-10-17T14:00:00Z.
SCHEMA "Invoices table + migration" is ready for review — https://github.com/acme/billing/pull/41 (tests attached)
Five notice kinds reach the lead this way: review_ready, escalated, capacity, gate_ready and no_worker.
Correctness
Built for machines that sleep, drop and reconnect
Laptops close, Wi-Fi drops, a worker gets started twice. None of that should duplicate work, lose it, or leak a key.
Attempt fencing
Every message carries its attempt. A worker that wakes up late and reports attempt 1 can't overwrite attempt 2 — the lifecycle refuses stale attempts.
Leases and reclaim
Offline for 30 minutes, or holding a lease past 24 hours, and the worker gets task.revoked; the task goes back to the queue. The scheduler checks every 60 seconds.
Files are locks
Two tasks whose files share a path prefix never run at the same time. The plan view draws the overlap as an orange dashed edge, so you see the contention before it happens.
Handoff commits
Quota, revoke or failure: the branch is pushed before the hub hears about it. A review rejection returns to that same branch, so history stays one line per task.
Keys stay out of reach
Credentials are scrubbed from git remotes and output, redacted from logs, and CORTEX_API_KEY is removed from the environment the engines run in.
One identity per worker
The registry is keyed by connection. Starting the same worker again replaces the old one (close 4000); the same agent id on another host is refused (close 4009).
Dashboard
See the critical path before it slips
The /conductor plan view draws the task graph by dependency depth, with a lane per machine and one for the queue.
- Route chips on every node —
agy·gemini-4-argon-low— so you see which engine and model a task is on right now. - Progress bar split into done, in review, running, ready, waiting and blocked.
- Needs you panel for escalations and user gates, with approve, reject and clear-escalation in place.
- Workers and a fallback tally showing which machines are online and how often routes fell through.
- Reroute a task — executor, model, fallback, files or dependencies — without recreating it.
- Phase view for plans grouped into milestones, with a lane per machine plus the queue.
Setup
One line per machine
Run it inside a clone of the project. The script fetches the worker from your own hub, so every machine runs the version its hub expects. Node 20 or newer; no global install.
curl -fsSL https://raw.githubusercontent.com/lktiep/cortex-hub/master/scripts/run-agent.sh | CORTEX_API_KEY=<worker-api-key> bash -s -- start --daemon --hub https://<your-mcp-host>
$env:CORTEX_API_KEY="<worker-api-key>"; iwr -useb https://raw.githubusercontent.com/lktiep/cortex-hub/master/scripts/run-agent.ps1 -OutFile $env:TEMP\run-agent.ps1; powershell -NoProfile -ExecutionPolicy Bypass -File $env:TEMP\run-agent.ps1 start --daemon --hub https://<your-mcp-host>
Replace <worker-api-key> and <your-mcp-host>. In the dashboard, Settings → Run a worker shows both lines with your hub's address already filled in. Give workers their own API key, separate from the lead's.
start --daemonRegister with the hub and wait for tasks.statusOnline state, engines found, current task.logsThe worker's log, with secrets redacted.doctor --probeCheck git, gh, Node and every engine — and actually call them.updateFetch the bundle the hub is serving now.hub-urlShow or change the hub this worker talks to.stopFinish cleanly; the hub requeues anything unfinished.Status
Where it stands — honestly
✅ means a test on master covers it and passes. 🔄 means it works but rests on a check nobody has run yet. 📋 means planned.
| Capability | State |
|---|---|
| Hub | |
| Plan trees and cycle check | ✅ |
| Dispatcher: dependencies, priority, file locks | ✅ |
| Per-task routing and fallback | ✅ |
| Quota record, wait reason, lead notice | ✅ |
| Leases and offline reclaim | ✅ |
| Review on the same branch, escalation | ✅ |
| User and lead gates | ✅ |
| Workers | |
| Worker on macOS and Linux, worktree → PR | ✅ |
| Engine: agy, with verified quota output | ✅ |
| Engines: claude, codex, gemini — quota patterns from fixtures | 🔄 |
| Worker on Windows | 🔄 |
| Dashboard & setup | |
| Plan view | ✅ |
| Extension finds the hub URL | ✅ |
| Setup guide followed end to end on a fresh machine | 🔄 |
| Next | |
| Per-task cost tracking · autostart on boot | 📋 |
| Semantic capability matching · editing a task's description | 📋 |
Proof, not adjectives
Passing tests on the merged branch, per package.
Know before you lean on it
- Quota output is verified for agy only. Claude, Codex and Gemini limits are matched against fixtures until a live hit confirms them.
- Locks are only as good as
files. A task that doesn't list what it touches isn't protected from conflicts. - agy takes about two minutes to start a task, because it starts every MCP server it has. Trim its server list to shorten that.
- Use separate keys for the lead and workers. Notices are delivered once per key, to whichever session asks first.
- User gates live in the dashboard, and it has no login. Anyone who can open it can approve one.
- Windows workers await a manual run of the checklist; the end-to-end test is skipped there.