One repo, many AI coding agents, zero collisions — Switchyard's
fleetCLI gives every agent an isolated git worktree, with collision detection before you merge.
fleet spawn two agents · fleet list the whole fleet · fleet check catches the collision before anyone merges · full-quality mp4
Two AI coding 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. Switchyard (published as @switchyardhq/switchyard; the installed command is fleet) gives each agent its own git worktree and branch, tracks them centrally, and flags collisions between agents before anyone merges.
(The same flow as the demo video, in skimmable, copy-pasteable form.)
$ fleet spawn claude
Spawned agent claude
branch: fleet/claude (from main)
worktree: ~/project/.fleet/worktrees/claude
Point your agent at it:
cd ~/project/.fleet/worktrees/claude
$ fleet list
┌────────┬──────────────┬──────┬───────┬───────────────┬───────────┬───────────────┬──────────────────────────┐
│ AGENT │ BRANCH │ BASE │ +/- │ CHANGES │ VALIDATED │ LAST ACTIVITY │ WORKTREE │
├────────┼──────────────┼──────┼───────┼───────────────┼───────────┼───────────────┼──────────────────────────┤
│ claude │ fleet/claude │ main │ +3/-0 │ clean │ passed │ 12m ago │ .fleet/worktrees/claude │
│ codex │ fleet/codex │ main │ +1/-2 │ 4 uncommitted │ — │ just now │ .fleet/worktrees/codex │
└────────┴──────────────┴──────┴───────┴───────────────┴───────────┴───────────────┴──────────────────────────┘
$ fleet check
1 collision risk detected:
┌───────────────────┬───────────────┬───────────────┐
│ FILE │ AGENTS │ VERDICT │
├───────────────────┼───────────────┼───────────────┤
│ src/api/routes.ts │ claude, codex │ will conflict │
└───────────────────┴───────────────┴───────────────┘
Verdicts from git merge-tree simulation of each agent pair's committed work; uncommitted edits can't be simulated and stay blocking.npm install -g @switchyardhq/switchyardRequires Node.js >= 18.17 and git >= 2.31. The installed command is fleet.
fleet validateandfleet dashboardare onmainand ship in the next release — they are not in the published0.4.0. Everything else below works on the npm build. To use them today, install from source:git clone https://github.com/MohammedAlkindi/Switchyard && cd Switchyard && npm install && npm link.
cd your-repo
fleet init # config, ignore entry, and the agent-facing docs
fleet spawn claude # isolated worktree on branch fleet/claude
cd .fleet/worktrees/claude # point your agent here and let it work
fleet check # any files also touched by other agents?
fleet sync claude # base moved on? catch the branch up
fleet validate claude # run your test command, record the result
fleet dashboard # agents, validation, collisions — one live pane
fleet exec claude -- npm test # run commands in the worktree without cd'ing
fleet diff claude # review the branch before merging
fleet merge claude # merge into your current branch + clean up the agent
fleet undo # …and roll that merge back if it was a mistake
fleet pr claude # …or push it and open a PR via gh instead| Command | Description | Key flags |
|---|---|---|
fleet init |
Set the repo up for the fleet workflow: starter .fleetrc.json, .fleet/ in .git/info/exclude, the Claude Code skill in .claude/skills/, and a protocol block in AGENTS.md. Idempotent — re-run after upgrading to refresh the agent-facing docs |
--check verify without writing; exits 1 on drift (CI-friendly), --force overwrite an existing .fleetrc.json, --json machine-readable output |
fleet spawn <agent> |
Create a worktree in .fleet/worktrees/<agent>/ on a new branch fleet/<agent>, then provision it (copyOnSpawn / postSpawn below) |
--from <branch> base branch (default: current branch) |
fleet list |
All active agents: branch, base, ahead/behind, uncommitted count, last activity | --json machine-readable output |
fleet status <agent> |
One agent in detail: uncommitted files, diff stat vs base, ahead/behind | --json machine-readable output |
fleet check |
Table of files touched by more than one agent. On git ≥ 2.38 each shared file gets a merge-simulation verdict — files whose committed changes merge cleanly are reported but don't block or fail the check. Exits 1 on real collision risks (CI-friendly) | --lines only count overlapping line ranges, --files-only skip simulation; flag any shared file, --json machine-readable output |
fleet diff <agent> |
Full diff of the agent's branch against its base | --base <branch> diff against a different branch |
fleet sync <agent> |
Merge the agent's base branch into its branch, catching it up. A conflicting merge is aborted — never left half-done | --all sync every registered agent in one sweep, continuing past per-agent failures; exits 1 if any failed |
fleet validate <agent> |
Run the configured validate command in the agent's worktree and record the result against the exact commit it certifies. fleet list shows the record; fleet merge trusts a passing one instead of re-running. Refuses a worktree that is dirty before or after the command — a record certifies a commit |
--all validate every agent, continuing past per-agent failures, --json machine-readable output; exits 1 on any failure |
fleet exec <agent> -- <cmd> |
Run a shell command inside the agent's worktree (e.g. fleet exec claude -- npm test) |
--all run in every worktree sequentially; exits 1 if any run fails |
fleet merge <agent> |
Check for collisions, require a passing validation record when validate is configured (running it if missing or stale), run the preMerge hook, merge the agent's branch into the current branch, then remove the worktree and branch. A conflicting merge is aborted — never left half-done. Overlaps that provably merge cleanly no longer block; predicted conflicts and uncommitted overlaps still do |
--no-clean keep the worktree and branch, --delete-branch explicit form of the default cleanup |
fleet undo |
Roll back the last fleet merge: reset the target branch, restore the agent's branch, worktree, and state entry, including after interrupted post-merge cleanup. Single-level; refuses if history moved on |
— |
fleet pr <agent> |
Push the agent's branch to origin and open a pull request with the GitHub CLI — the review-based alternative to a local merge |
--title <t>, --base <branch>, --draft |
fleet remove <agent> |
Remove the worktree; refuses if there are uncommitted changes | --force discard changes, --delete-branch also delete the branch |
fleet clean |
Remove agents whose branches are fully merged into their base | --dry-run list only, --stale <days> also remove long-idle agents (clean worktrees only; their branches are kept) |
fleet watch |
fleet list, re-rendered live until Ctrl+C |
--interval <seconds> refresh rate (default 3) |
fleet dashboard |
One live pane for the whole fleet: the agent table with validation states, per-agent touched-file counts, and the full collision report with merge-simulation verdicts | --once print a single frame and exit (scripts, CI logs), --interval <seconds> refresh rate (default 3) |
fleet doctor |
Diagnose git version, state file validity, orphaned worktrees, and stale entries. Exits 1 if problems remain | --fix repair: rebuild state from git worktree list, adopt/remove orphans, prune stale entries; --json machine-readable output |
fleet completion <shell> |
Print a completion script for bash, zsh, or fish (agent names are a snapshot from generation time) |
— |
fleet mcp |
Serve the read-only fleet tools to an AI agent over MCP (stdio). Not run by hand — see Use it from an AI agent | — |
All commands work from the main checkout or from inside any agent worktree.
list, status, check, and doctor all take --json for machine-readable output, so agents and CI can consume Switchyard state directly — e.g. a merge gate:
fleet check --json || exit 1 # exit code alone is enough for CI
fleet list --json | jq -r '.[].name' # enumerate active agents
fleet init --check # exits 1 when the agent-facing docs drifted
fleet validate --all # exits 1 unless every agent's tip passes
fleet dashboard --once # one full-fleet frame, for CI logsfleet check --lines refines collision detection from files to line ranges: two agents editing disjoint parts of one file are reported separately instead of blocking. Ranges are computed against each pair's merge base — exact when both agents share a base, a documented heuristic otherwise (see docs/architecture.md).
On git ≥ 2.38, fleet check upgrades from "same file" to "would actually
conflict": each pair of overlapping agents is merged in memory with
git merge-tree, and cleanly merging overlaps are demoted to an informational
list (they no longer exit 1). --files-only restores plain file-level
behavior; older git falls back to it automatically. JSON output carries
prediction: "merge-tree" | "files" so scripts know which semantics ran.
Everything above assumes a human at a terminal. fleet mcp gives the agents
themselves a way to see the fleet: it serves Switchyard state over the
Model Context Protocol on stdio, so an agent
can check for collisions before it starts editing rather than discovering
them at merge time.
Point an MCP client at it:
{
"mcpServers": {
"switchyard": {
"command": "fleet",
"args": ["mcp"]
}
}
}Without a global install, use "command": "npx", "args": ["-y", "@switchyardhq/switchyard", "mcp"].
| Tool | Arguments | Returns |
|---|---|---|
fleet_list |
— | Every active agent: branch, base, worktree path, ahead/behind, uncommitted count, last activity |
fleet_status |
agent |
One agent in detail: record, ahead/behind, uncommitted files, diffstat vs base |
fleet_check |
lines?, filesOnly? |
Files touched by more than one agent, with merge-simulation verdicts |
fleet_lock_status |
— | Whether a fleet command is currently mutating the repo |
Each returns the same object the matching --json flag prints, so the CLI and
the MCP surface can never disagree about what the state is.
The demo GIF above shows the human side. This is the same collision from the
agent's side — a real session against fleet mcp, two agents having both
rewritten src/api/routes.ts:
The reply's content block carries the same object fleet check --json prints:
{
"collisions": [
{ "file": "src/api/routes.ts", "agents": ["claude", "codex"], "verdict": "conflicts" }
],
"prediction": "merge-tree",
"agentsChecked": 2,
"cleanMerges": []
}"verdict": "conflicts" is the agent's cue to stop and coordinate — reached
before it wrote a line, rather than at merge time.
There is no fleet_spawn, fleet_merge, or fleet_remove. Agents can observe
the fleet; they cannot join or change it. Provisioning and merging stay human
actions in this release.
That is a real limitation, not a technicality, and it has a failure mode worth
naming: an agent that goes looking for a spawn tool, finds none, and falls back
to a raw git worktree add has produced exactly the untracked, uncoordinated
state Switchyard exists to prevent. The server therefore says so at handshake
time, and the shipped skill says so again — an agent should ask for
fleet spawn <name> instead.
A pleasant consequence: since spawn is not exposed, the postSpawn hook
(arbitrary shell from .fleetrc.json) is not reachable from an agent at all.
The tools report state; they cannot convey that you are expected to check
before editing rather than before merging, or that provisioning is something
to ask a human for. That convention is the actual product, and fleet init
installs it in two forms:
| Artifact | Audience |
|---|---|
.claude/skills/switchyard/SKILL.md |
Claude Code, which loads the full skill on demand |
A marked block in AGENTS.md |
Any agent that reads AGENTS.md up front — Codex, Cursor, and others |
Two texts rather than one generated from the other, because the audiences differ: a skill loaded on demand can afford a hundred lines, an always-read file cannot.
Both are package-managed and refreshed on every fleet init, so upgrading the
package and re-running is enough to keep them current. In AGENTS.md only the
region between <!-- switchyard:begin --> and <!-- switchyard:end --> is
rewritten — the rest of the file is yours and is never touched. .fleetrc.json
is treated the opposite way: it is your file, so init never overwrites it
without --force.
If the markers are ever half-deleted or inverted, init refuses rather than guessing where your content ends.
An optional .fleetrc.json at the repo root sets per-repo defaults. Precedence everywhere: CLI flag > .fleetrc.json > built-in default.
{
"$schema": "https://unpkg.com/@switchyardhq/switchyard/schema/fleetrc.schema.json",
"defaultBase": "main",
"watchInterval": 3,
"autoClean": false,
"copyOnSpawn": [".env"],
"postSpawn": "npm ci",
"preMerge": "npm run lint",
"validate": "npm test"
}$schema— optional; points editors at the config's JSON schema for autocomplete and validation. The schema ships with the package (node_modules/@switchyardhq/switchyard/schema/fleetrc.schema.json).defaultBase— base branch forfleet spawnwhen--fromis not passed (built-in default: the current branch).watchInterval— refresh interval forfleet watch, in seconds (built-in default: 3).autoClean— whentrue, every successfulfleet mergealso runs afleet cleansweep for other fully merged agents (built-in default:false).copyOnSpawn— repo-root-relative files/directories copied into every new worktree byfleet spawn. Worktrees don't carry gitignored files, so a fresh one has no.envor local config — this fixes that. Missing entries are skipped with a note.postSpawn— shell command run inside the new worktree afterfleet spawn(e.g.npm ci), so the worktree is ready to work in. A failing hook is reported but the worktree is kept.preMerge— shell command run inside the agent's worktree beforefleet mergestarts (e.g.npm test). A non-zero exit aborts the merge before anything is touched.validate— shell commandfleet validateruns inside an agent's worktree and records per commit (e.g.npm test). When set,fleet mergerequires the agent's tip to hold a passing record, running the command itself when the record is missing or stale.preMergestill runs at merge time regardless —validateis the recorded, skippable gate;preMergeis the always-run hook.
A malformed config file is a hard error with the offending key named; a missing one is fine.
Note
postSpawn, preMerge, and validate run shell commands straight from the repo's .fleetrc.json — the same trust you already extend to a repo's npm scripts or git hooks. Review that file before running fleet spawn, fleet merge, or fleet validate in a repository you didn't author (details in SECURITY.md).
fleet spawn runs git worktree add under the hood: each agent gets a real, separate directory with its own checkout of a dedicated fleet/<agent> branch, so one agent's git reset physically cannot touch another agent's files. A single gitignored .fleet/state.json in the main repo maps each agent to its branch, base, and worktree, and fleet check uses it to diff every agent branch against its base (git diff base...branch, plus uncommitted edits) and cross-reference the changed files. Switchyard also adds .fleet/ to .git/info/exclude automatically, so it never dirties the repos it manages. Design rationale and limitations live in docs/architecture.md.
PRs are welcome. Clone, npm install, npm test — every command is tested against real throwaway git repositories, and any change to a command must come with such a test (no git mocks; see CLAUDE.md and AGENTS.md for the ground rules). Commits follow Conventional Commits. Read docs/architecture.md before structural changes, and docs/deployment.md for the release process.
MIT © Mohammed Alkindi — see LICENSE.
