Cortex Conductor

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.

1 callcreates the whole plan: dependencies, file locks, routes, gates
4 enginesclaude · codex · gemini · agy, chosen per task with fallbacks
1,358unit tests across six packages, plus 267 hook tests
5 / 5live worker run against a real hub stack

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.

BILLBilling v2 · 8 tasks
STEP 1 / 10

The lead writes the plan

wire · /ws/conductor · protocol 2📋 = lead notice

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.

  1. 01

    The lead writes the plan

    Claude Code — or any MCP client — sends the feature as a single cortex_task_tree_create call: keys and dependsOn, the files each 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"] }
    ])
  2. 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.

  3. 03

    A worker runs it in its own worktree

    worktree→setup→engine→testCommand→commit→push→PR→review

    Each 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 with gh, or hands back a compare URL when gh isn't there.

  4. 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 in context.reviews.
    2 strikesTwo rejections or two failures escalate to Needs you.
  5. 05

    Gates hold the line

    A gate is a node in the graph with an approver. approver: "lead" closes with cortex_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.

route 1 · mini
agy · claude-sonnet-5-5-low
agy exits 3 with RESOURCE_EXHAUSTED. The worker commits WIP PDF: quota handoff and pushes.
quota_exhausted
route 2 · fallback
agy · gemini-4-argon-low
Attempt 2 starts on the same branch, with the WIP commit as its parent.
continues
route 3 · project default
claude · opus
Every chain ends with the project's default route, on any machine that has it.
last resort
nothing left
wait_reason: capacity
The task waits with a wait_until. The lead is told once; the dispatcher retries when the window resets.
never silent

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.

api/billing/ SCHEMAclaude·opusDONE PDFagy·gemini-4-argon-lowREVIEW APIcodexRUNNING HOOKclaude·sonnetHELD UIclaude·sonnet DOCSgemini E2Eclaude·opus → sonnet SHIPgate · approver user
critical path shared files gate done in review running held
  • 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>

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.

CapabilityState
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.

dashboard-api523
conductor-worker478
dashboard-web171
shared-mem977
hub-mcp67
cortex-agent42
hook tests267
16defects caught in review and fixed before merge — 9 hub, 7 worker
5 / 5live worker run: register, PR, quota handoff, resume
107commits across 4 plans, each task reviewed
+32.7klines across 210 files, −2.8k removed

Know before you lean on it

  1. Quota output is verified for agy only. Claude, Codex and Gemini limits are matched against fixtures until a live hit confirms them.
  2. Locks are only as good as files. A task that doesn't list what it touches isn't protected from conflicts.
  3. agy takes about two minutes to start a task, because it starts every MCP server it has. Trim its server list to shorten that.
  4. Use separate keys for the lead and workers. Notices are delivered once per key, to whichever session asks first.
  5. User gates live in the dashboard, and it has no login. Anyone who can open it can approve one.
  6. Windows workers await a manual run of the checklist; the end-to-end test is skipped there.