Switchyard v0.4

Signal box · multi-agent git operations

Every agent gets
its own track.

Switchyard is a CLI for running several AI coding agents — Claude Code, Codex, Cursor, however many you drive — on one git repository at once. Each agent works in an isolated worktree on its own branch, and fleet check simulates every merge before anyone attempts it.

npm install -g @switchyardhq/switchyard

Installs the fleet command · Node ≥ 18.17 · git ≥ 2.31 · MIT

main main fleet/claude · .fleet/worktrees/claude fleet/codex · .fleet/worktrees/codex

CHECK no shared files between agents — junction clear

Why this exists

Two AI agents on one checkout ends badly. This project exists because Codex silently ran a git reset on main mid-merge while Claude Code was mid-task on the same files — the merge state vanished and neither agent noticed.

The failure mode isn't exotic: two agents, one working tree, no isolation. Branches alone don't help; they share one directory and one index. Worktrees give each agent a real, separate directory with its own files, its own index, its own HEAD. A git reset in one physically cannot disturb the others.

The working timetable

The core loop, in departure order. Every halt is one command.

  1. 01
    fleet init

    Open the line. Writes the starter config, ignores .fleet/, and installs the docs that teach your agents the convention — a Claude Code skill and an AGENTS.md block. fleet init --check verifies it all in CI without writing a byte.

  2. 02
    fleet spawn claude

    Branch off. A real worktree appears at .fleet/worktrees/claude on branch fleet/claude, provisioned by your copyOnSpawn files and postSpawn hook. Point the agent at it and let it work.

  3. 03
    fleet check

    Read the signal before the junction. Every pair of agents that touched the same file is merged in memory with git merge-tree — a real three-way merge, no worktree touched. Conflicts stop you before the work is doubled, not after.

  4. 04
    fleet sync --all

    Hold the interval. After anything lands, every other agent is behind its base — one sweep catches the whole fleet up. A dirty worktree or a conflicting merge is reported and skipped; it never strands the rest.

  5. 05
    fleet merge claude

    Bring it home. Collision gate first, then your preMerge hook, then the merge — aborted cleanly on conflict, never left half-done. The worktree and branch are cleaned up after. Regret it? fleet undo rolls the whole thing back.

The same flow, live:
$ fleet list
┌────────┬──────────────┬──────┬───────┬───────────────┬───────────────┐
│ AGENT  │ BRANCH       │ BASE │ +/-   │ CHANGES       │ LAST ACTIVITY │
├────────┼──────────────┼──────┼───────┼───────────────┼───────────────┤
│ claude │ fleet/claude │ main │ +3/-0 │ clean         │ 12m ago       │
│ codex  │ fleet/codex  │ main │ +1/-2 │ 4 uncommitted │ just now      │
└────────┴──────────────┴──────┴───────┴───────────────┴───────────────┘

$ fleet check
1 collision risk detected:
┌───────────────────┬───────────────┬───────────────┐
│ FILE              │ AGENTS        │ VERDICT       │
├───────────────────┼───────────────┼───────────────┤
│ src/api/routes.ts │ claude, codex │ will conflict │
└───────────────────┴───────────────┴───────────────┘

Three aspects, straight from the simulation

fleet check doesn't guess from filenames. On git ≥ 2.38 it runs a real in-memory merge for every pair of agents sharing a file, and reports one of three verdicts.

will conflict

The simulated merge produced conflict markers. Stop and coordinate — pick a different file, or let the other agent's work land first. Exits 1, so CI stops too.

uncommitted

Another agent has unsaved edits there. Simulation can't see work that was never committed, so the signal fails closed rather than guessing it's safe.

clean

Both agents touched the file and the committed sides merge cleanly. Reported for awareness, but it doesn't block the junction — and a wrong call is still caught by the merge's own abort-on-conflict net.

The full roster

Open the line
fleet initConfig, ignore entry, and the agent-facing docs. --check verifies without writing; exits 1 on drift.
Run the fleet
fleet spawn <agent>Isolated worktree + branch fleet/<agent>, provisioned via hooks.
fleet list / watchEvery agent: branch, ahead/behind, changes, validation state, last activity. --json for scripts.
fleet dashboardAgents, validation states, and collision verdicts in one live pane. --once for CI logs.
fleet status <agent>One agent in detail: uncommitted files, diffstat vs base.
fleet exec <agent> -- <cmd>Run a command inside a worktree — or every worktree with --all.
Guard the junction
fleet checkMerge-simulated collision report across all agent pairs. --lines for line-range precision.
fleet diff <agent>The branch's full diff against its base, for review before merging.
fleet sync <agent> / --allCatch one agent — or the whole fleet — up with its base. Conflicts abort cleanly.
fleet validate <agent> / --allRun the repo's validation command and record the result per commit. Merge trusts a passing record.
Bring it home
fleet merge <agent>Gate, hook, merge, clean up. Aborts on conflict, never left half-done.
fleet undoRoll back the last merge: branch pointer, agent branch, worktree, state.
fleet pr <agent>Push the branch and open a pull request via the GitHub CLI instead.
Keep it healthy
fleet cleanSweep fully merged agents; --stale <days> retires idle ones.
fleet doctorDiagnose state/reality drift; --fix repairs what can be repaired.
fleet mcpServe read-only fleet state to the agents themselves, over MCP.

Let the agents read the signals

fleet mcp serves the fleet to the agents themselves over the Model Context Protocol: fleet_list, fleet_status, fleet_check, fleet_lock_status — read-only by design, so an agent can check for collisions before it starts editing, not discover them at merge time.

And fleet init makes the convention reach them: it installs a Claude Code skill and writes a protocol block into AGENTS.md that any agent — Codex, Cursor, anything that reads the file — picks up. Spawning, merging, and cleanup stay human actions.

Point an MCP client at it:
{
  "mcpServers": {
    "switchyard": {
      "command": "fleet",
      "args": ["mcp"]
    }
  }
}

Clear the junction.

npm install -g @switchyardhq/switchyard

Source on GitHub · Changelog · Architecture notes