deckspace.dev / DeckSpace / docs / swarm-and-worktrees

Swarm and worktrees

One goal, several agents, one git worktree each. A run you do not want is deleted, not unpicked.

Swarm lives at Tools → Swarm. You give it a goal, it splits that goal into tasks, and each task gets its own branch, its own git worktree and its own terminal pane. Your checkout is never touched until you merge something into it.

Starting a swarm run needs an active subscription. Opening the room and reading a previous run does not.

What the worktree buys you

A builder never works in your project directory. For each task the app runs git worktree add -b <branch> <path> HEAD and points that builder’s pane at the new checkout. Branches are named swarm/<goal-slug>-<rand>-<n>.

The consequence is the whole point: every run is disposable. Discarding a builder is one button, and it removes the checkout and deletes the branch. There is nothing to revert in your own tree, because nothing was ever written there.

ActionWhat runsResult
Mergecommit the worktree → snapshot your repo → git merge --no-ff → remove the worktree → git branch -Dwork lands on your branch; the worktree and its branch are gone
Discardgit worktree remove --forcegit branch -Deverything that builder did is deleted, committed or not
Cleanupthe same removal, for a task whose worktree already vanishedthe leftover branch is deleted and the task leaves the room

--force is deliberate. It overrides git’s own “contains modified files” refusal, so Discard throws away uncommitted work in that worktree with no second prompt. That is the trade you are making for a checkout you never have to unpick.

One thing is shared rather than duplicated: if your project root has a node_modules directory, each new worktree gets a symlink to it instead of its own copy. On Windows that symlink needs privilege and is best-effort — when it fails, the worktree simply has no node_modules and a builder that needs one has to install it.

How many agents you actually get

The number in the launch form is a ceiling, not a promise. Three different limits sit between what you type and what opens.

LimitValueWhere it is enforced
Roster field in the Swarm room1–12the input clamps as you type; the presets are Solo 1, Squad 5, Fleet 12
Split across providers mix12 totalthe per-provider counts are summed and the list truncated to 12
The launch itself12the builder list is truncated before any worktree is created — this is the real cap
Asking the assistant to launch a swarmdefault 8a separate setting, orchestrator_hard_cap, itself clamped to 1–12; the Swarm room does not read it
Panes on screen16the grid maximum, shared with every other pane you have open

So the same request can produce different fleets depending on how you make it: typing 12 into the room gives 12, while telling the assistant “launch 12 builders” gives 8 unless you raise the hard cap.

Without Claude Code installed, a swarm is one agent

The task list comes from a headless planning call, and that call only runs when Claude Code is one of your detected CLIs. If it is not, the planner returns a single task — your goal, verbatim — and the swarm opens one builder no matter what number you asked for. The log line says Plan: 1 builder task(s), and that is your only warning.

The builders themselves can be any CLI. It is the planning, scouting, reviewing and PR-writing steps that are Claude-Code-only, because they are invoked as <bin> -p <prompt>.

If the pane grid is already at 16 when a swarm launches, the extra panes are silently not created. The branch and the worktree still exist, the task still appears in the room, and the log reads Pane for <branch> didn't start in time after an eight-second wait. Close some panes and use Reopen pane on the task.

Who is in a swarm

A swarm is not only builders. Four other roles can run, each with its own requirement.

RoleHow it runsRequires
Scoutone read-only headless pass before builders start; its report is saved to project memory as swarm-scout and pasted into every builder’s promptClaude Code, and the Scout first checkbox. Otherwise logged as skipped.
Plannerone headless call that returns tasks with file ownershipClaude Code. Otherwise one task.
Live coordinatora real claude pane that reads builder progress and messages them backClaude Code, more than one task, and the Live coordinator checkbox
Builderone pane per task, working directory set to that task’s worktreeany detected agent CLI
Reviewer / PR writerheadless, on demand, per task from the task cardClaude Code. Without it, Review returns “no reviewer agent available” and PR returns the task title with no description.

Builders and the coordinator talk over a message bus. An agent prints a line beginning SIGMA:: with a verb (STATUS, DONE, BLOCKED, SAY, ROLLCALL and three more) and it is delivered into the target pane’s input as [swarm builder-2→you] …. Anything after SIGMA:: that is not one of the known verbs, or is a truncated JSON fragment, is dropped — terminal redraws would otherwise read as messages.

What permission the builders launch with

The Swarm room has no per-run autonomy picker. Builders launch at auto-edit, or at full when the app-wide walk-away toggle is on. They never launch in plan mode, because a read-only agent cannot implement anything.

That means a swarm is always editing files without asking. See What walk-away does for what each level sends your CLI, and note that five of the eleven supported CLIs have no autonomy flag at all — a builder running one of those will sit at its own permission prompt inside its worktree.

The Sandbox checkbox does not cover the builders

Read this before you assume the fence is up

The Swarm room’s Sandbox agents checkbox is passed to the headless calls — Scout, planner, Reviewer, PR writer. It is not passed to the builder panes. A builder pane is a shell with the agent command typed into it, and that spawn path does not wrap the process in Seatbelt or bwrap; only panes where the CLI itself is the pane’s process, and headless runs, are wrapped.

What a builder does have is the destructive-command guard: OPX_GUARD=1 plus PATH shims for rm, git and sudo. That catches the common accident. It is not a kernel boundary, and PATH can be re-derived.

The checkbox is also hidden entirely when no sandbox backend exists, so on a machine that reports none you will not see the option at all rather than see it and have it do nothing.

PlatformBackendWhat it does to a temp-dir worktree
macOSsandbox-execavailable writes are denied everywhere, then re-allowed for the working directory and all of /private/var/folders — which is where every worktree lives, so this does not fence one builder off from another’s checkout
Linuxbwrapavailable if bwrap is installed; /tmp is replaced with an empty tmpfs and only this run’s own directory is bound in
Windowsnone the argv is returned untouched; the checkbox is not shown

Overlap warnings are advisory

The planner is asked for tasks that touch different files, and each task carries the list of paths it owns. Before dispatch the app counts how many tasks claim each path and logs a line for any path claimed twice:

⚠ Overlap: src/api/routes.ts owned by >1 builder — they may collide.

That is a log line. It does not stop the launch, it does not narrow the ownership, and it compares only what the planner declared — not what the builders actually go on to edit. Treat it as a prompt to intervene, not as a guard.

The real collision check happens later, at review time: merging is predicted with git merge-tree --write-tree against your current HEAD, without mutating anything, and the conflicting paths are listed on the task card.

Reviewing and picking a winner

Each task card carries its own buttons. Order matters less than knowing which of them touch git.

ButtonWhat it doesTouches your branch
Open / Reopen panefocuses that builder’s terminal, or opens a fresh one on the surviving worktreeno
Reviewcommits the worktree, predicts the merge, runs your project’s checks, then has a reviewer agent return APPROVED or CHANGES_REQUESTEDno — the commit is in the worktree, on its own branch
PRcommits, then writes a PR title and body from the diff. Create PR appears alongside it only when the gh CLI is available.no
Mergecommits, snapshots your repo, merges, then removes the worktree and branchyes
Discarddeletes the worktree and branchno

A predicted conflict or a failed verification downgrades a clean APPROVED to changes requested, and the Merge button relabels itself Merge anyway. Clicking it asks you to confirm the named conflicting files; anything other than an explicit yes cancels the merge and nothing happens.

Merging takes a snapshot first

Before the merge runs, the app writes a checkpoint commit of your working tree. If the merge goes wrong you can restore it from the Review room. The A/B room described below does not do this.

Comparing two implementations side by side

Swarm splits one goal across builders. When you instead want the same prompt tried two ways, use A/B Mode in the Background room, then read the result in Tools → A/B Compare.

  1. In Background, tick A/B Mode and pick agent A and agent B. The schedule selector is disabled — an A/B pair always runs once, immediately.
  2. Both jobs run headless in the scheduler thread, each in its own bgjob/… worktree, with a 30-minute wall-clock cap per job.
  3. When both are done, the Compare button appears. Each side lists the files that branch changed and renders the selected one in a read-only inline diff editor.
  4. Agent A wins / Agent B wins merges the winner’s branch and removes the loser’s worktree and branch.
Two limits worth knowing before you rely on it

It needs a git repository. A background job in a non-repo folder still runs, but it gets no branch — and with no branch the compare panes read “Run not complete yet” even though the run finished, and both winner buttons stay disabled. There is nothing to compare and nothing to merge.

Picking a winner does not snapshot your repo first. Unlike the Swarm room’s Merge, the A/B merge goes straight to git merge. A failure is reported as a toast and left for you to resolve by hand.

The jobs run in the scheduler thread while the app is open. Quit DeckSpace and they stop.

A builder’s directory is not your project

Worktrees are created under the operating system’s temp directory, in a path shaped like this:

<os temp>/opxspace-worktrees/<repo-name>-<8 hex>/<branch-slug>-<8 hex>

The hashes are there because slugging is lossy: a/b and a-b would otherwise collide, and so would the same branch name in two different repositories.

This has one consequence that catches people writing scripts, tools and integrations against DeckSpace: a builder pane’s working directory is a temp path, not your project root. Grouping or filtering panes by directory silently drops every worker in the fleet. Panes belong to a workspace by their workspace, not by where they happen to be checked out. The app’s own phone-remote roster is keyed on the workspace for exactly this reason — and it reports each worker pane’s temp worktree path in its project field, which is the shape you have to read around.

After a reload: adoption, not resumption

Panes and their processes die with the app. When you reopen the Swarm room, the previous run is adopted, not restarted: the plan, the branches and the worktrees come back, and nothing is spawned. No pane is recreated and no agent is relaunched.

The room reconciles the saved run against what git still has registered. Worktrees live in a temp directory, and macOS reaps that directory after a few days, so a saved worktree routinely no longer exists. Those tasks get a worktree missing chip and lose their Review, PR and Merge buttons — there is nothing left to review. The honest moves are the two you are left with:

  • Mark complete — files the task as finished without touching git.
  • Cleanup — deletes the leftover branch and drops the task.

Two other things are checked on adoption. If the swarm is no longer in the local database (a different machine, a wiped database) the run is cleared and only your preferences survive. And the subscription gate is re-checked, so a lapsed subscription gets an explanation in the room rather than a silently empty one.