Fleet
Arm a set of agent terminals and broadcast to all of them at once, with per-target variable resolution, confirmation gates, and bulk shortcuts for accept, reject, interrupt, restart, and kill.
What Is Fleet?
Fleet is Daintree's coordinated multi-terminal mode. You arm a set of agent terminals, and from then on every action in the primary terminal repeats across all of them: live keystrokes, command submissions, and bulk operations like accept, reject, interrupt, restart, and kill. The armed set is sticky. It stays around until you disarm or exit, so Fleet is a mode you enter, not a one-shot shortcut.
Only agent terminals are eligible. Dock terminals, browser panels, dev preview panels, and trashed or backgrounded panels cannot join a fleet, and the armed set auto-prunes when an eligible panel goes away. The model is three steps: arm a set, broadcast a draft, review the results.
Starting a Fleet
There are several ways to arm terminals. The arming ribbon appears at the top of the content area once two or more terminals are armed, and that is the moment Fleet becomes visible.
Arming Individual Terminals
The fastest path is to focus a terminal and toggle it into the fleet with a keyboard shortcut. The shortcut works globally, so you can arm terminals from anywhere in the app.
| Action | Shortcut |
|---|---|
| Arm or disarm focused terminal | Cmd+J |
| Arm or disarm focused panel (in the panel grid) | X |
| Arm all eligible terminals in current worktree | Cmd+Alt+A |
| Action | Shortcut |
|---|---|
| Arm or disarm focused terminal | Ctrl+J |
| Arm or disarm focused panel (in the panel grid) | X |
| Arm all eligible terminals in current worktree | Ctrl+Alt+A |
| Action | Shortcut |
|---|---|
| Arm or disarm focused terminal | Ctrl+J |
| Arm or disarm focused panel (in the panel grid) | X |
| Arm all eligible terminals in current worktree | Ctrl+Alt+A |
The Fleet Picker
The Fleet Picker is the cold-start surface for selecting several terminals at once. Click the Zap icon in the sidebar to open it. A centered modal appears with the heading "Select terminals to arm", a fuzzy search input, and a list of every fleet-eligible terminal grouped by worktree.
Search matches against terminal title, worktree name, branch, file path, and recent scrollback content, so you can find a target by what is happening inside it, not just what it is called. Each worktree group has a parent checkbox, with an indeterminate state when only some of its terminals are selected.
The picker's keyboard model (substitute Ctrl for Cmd on Windows and Linux):
| Key | Action |
|---|---|
| Tab | Move focus from search into the list |
| Space | Toggle the focused terminal |
| ↑ / ↓ | Move between rows |
| Cmd+A | Select all visible matches |
| Cmd+Shift+I | Invert the current selection |
| Shift+Click | Range select from the last clicked row |
| Esc | Clear search on first press, close on second |
Footer controls show the current selection count and offer Select all, Select agents (adds every terminal currently in a working, waiting, or directing state), and Deselect all. To the right of those, a segmented Replace or Append control determines what happens on confirm. Replace clears the current armed set and arms only the picker's selection. Append adds the picker's selection to the current set.
Preset Selections
Once two or more terminals are armed, the arming ribbon's … selection menu offers one-click presets that arm matching terminals additively. The built-in presets cover the common coordination patterns:
- All waiting (this worktree): every terminal in this worktree currently waiting for input
- All waiting (all worktrees): every waiting terminal anywhere in the project
- All working (this worktree): every terminal currently running an agent task
- All working (all worktrees): same, but project-wide
- All in this worktree: every eligible terminal in the active worktree
There is also a sidebar shortcut. When a sidebar filter is active, an Arm N matching button appears alongside the worktree filter controls. It dispatches the same arm-matching action the presets use, so you can build a fleet out of whatever filter view you are currently looking at.
The Arming Ribbon
The arming ribbon is the amber bar that appears across the top of the content area once the armed count reaches two. It has a 2px amber left-edge stripe and slides in with a short spring entrance. From left to right, it holds an exit button, the count chip with worktree dots, the broadcast progress counter, a cancel button (visible only during a large batched broadcast), the selection menu, and an exit button labelled with the exit chord for the current OS.
The Count Chip
The count chip shows how many terminals are armed, an "in fleet" label, a row of small worktree-color dots marking which worktrees the armed set spans, and an exited count if any of the armed terminals are no longer running. Click the chip to open a popover that lists every armed terminal in arm order. Each row has a focus button, which jumps to that terminal, and a disarm X.
An Add panes… button at the bottom of the popover switches the popover content to an inline picker so you can extend the fleet without leaving the chip. The Escape behavior is layered. The first press clears any search, the second returns the inline picker to list mode, and the third closes the popover.
Exiting Fleet Mode
The exit chord is Cmd+Esc on macOS and Ctrl+Esc on Windows and Linux. The ribbon's Exit button shows the chord for the current OS as a hint. A bare Esc on any ribbon-owned control (the exit button, count chip, or selection menu trigger) also exits, as long as there is no pending confirmation. The X button on the left edge of the ribbon disarms everything immediately.
After exiting, focus returns to the last terminal that was armed, and a brief ring animation pulses around its panel so you can see where you ended up.
Broadcasting Commands
Fleet has two broadcast paths. Raw input mirroring sends every keystroke from the primary terminal's input bar to all armed peers as you type. Pressing Enter then submits the resulting draft as a structured payload, with per-target variable resolution and a confirmation gate for anything risky.
Live Keystroke Mirroring
When two or more terminals are armed and you start typing in the primary terminal's input bar, an amber Mirroring to N peers pill (the drafting pill) appears in that input bar. Every keystroke in the primary is replicated live into the input bars of all armed peers. Backspaces, arrow keys, and selections all travel through.
Submitting with Enter
Pressing Enter in the focused primary sends the composed draft to every armed peer. If the draft contains
no {{variables}} and trips no safety gates, the broadcast goes out immediately. The
progress counter on the ribbon shows completed/total while the fan-out is in flight. The one
exception is a small fleet where the broadcast completes within the Doherty threshold, in which case the
counter is suppressed to avoid flicker.
Broadcast history is per-project. Press ↑ and ↓ in the primary input bar to walk previous broadcasts the same way you walk shell history.
Per-Target Preview and Overrides
The drafting pill is clickable only when the current draft contains at least one {{variable}}. Click the pill to open the Fleet broadcast preview popover. Each row in the popover is one armed target. It shows an include checkbox, the terminal's title,
the raw draft with amber spans on the variables, and an editable textarea holding the resolved payload that
target will actually receive.
Edit a textarea to set a per-target override. Uncheck the include checkbox to skip that target for the current broadcast. The pill grows a divergence dot whenever any override, skip, or unresolved variable is pending, so you can tell at a glance that the fan-out is no longer a uniform send. Pressing Esc inside an open textarea reverts that target to the resolved default and blurs the field.
Per-target overrides and skips are ephemeral. As soon as a broadcast leaves the ribbon, the override store clears, so the next broadcast starts from resolved defaults again.
{{...}}, the broadcast goes straight to every peer with no preview, since there is nothing
to diverge on.Recipe Variables in Fleet Broadcasts
Fleet broadcasts resolve a small set of variables per target, using each terminal's worktree context. This is the "mail merge" path. You write one draft, and every armed terminal substitutes its own values before anything reaches the agent.
| Variable | Resolves To | Example |
|---|---|---|
{{issue_number}} | GitHub issue number linked to this target's worktree | #412 |
{{pr_number}} | Pull request number linked to this target's worktree | #418 |
{{number}} | Issue number if set, otherwise PR number | #412 |
{{worktree_path}} | Absolute path to this target's worktree directory | /Users/you/proj/.worktrees/feature |
{{branch_name}} | Branch name (falls back to worktree name if no branch is set) | feature/issue-412 |
Variables that cannot be resolved for a given target (no linked issue, no worktree association, an unknown variable name) resolve to an empty string and surface as an unresolved warning in the preview popover. The drafting pill's divergence dot counts unresolved variables, so a partially-resolvable broadcast triggers the divergence confirmation path described below.
{{project_root}} or {{endpoint}}, that is the remote-compute namespace
and it does not resolve inside a Fleet broadcast.Broadcast Safety Gates
A broadcast confirmation triggers whenever the draft, or any per-target resolved payload, crosses one of Daintree's safety thresholds:
- The payload is multiline.
- The payload exceeds 512 bytes of UTF-8.
- The payload matches the destructive pattern:
rm -rf,sudo,git clean -f,DROP TABLE,TRUNCATE TABLE,chmod -R,mkfs,dd if=, or a fork-bomb literal. - Any individual per-target payload (after override resolution) crosses the same byte threshold or matches the destructive pattern.
There are two confirmation paths depending on whether the broadcast is uniform or divergent.
Warnings-Only Confirmation
When the broadcast trips a warning but every target receives the same payload, the ribbon body is replaced with an inline confirmation strip: Send to N (reason), with Cancel and Send broadcast buttons. The reason text names the gate that fired ("multiline", "long", "matches destructive pattern"). Enter confirms, Esc cancels.
Divergence Confirmation
When per-target overrides, skips, or unresolved variables are pending, the broadcast opens a full confirmation dialog instead of an inline strip. The dialog shows a scrollable list of every target with the exact payload it will receive, an "Edited" badge for any target with an override, a skip indicator for unchecked targets, and a warning chip on targets with unresolved variables.
The divergence path exists so the per-target preview is never glossed over. If you set an override or skip, you confirm against the actual list, not a single summary line.
Cancelling an In-Flight Broadcast
Daintree batches large broadcasts so the renderer stays responsive. When a payload exceeds 100 KB and the target count exceeds five, the fan-out runs in batches of five with a microtask yield between batches. While a batched broadcast is in flight, the ribbon shows a Cancel button next to the progress counter. Cancelling stops the remaining batches from going out and dismisses the progress counter.
Fleet Deck
Fleet Deck is the multi-column grid that takes over the content area when your armed set spans multiple worktrees. It exists so you can see every terminal that is receiving keystrokes, instead of broadcasting blind to panes that live in worktrees you are not currently viewing.
Fleet Deck activates automatically the first time you start typing into a primary terminal whose armed
peers cross worktree boundaries, as long as the Fleet scope mode is set to its default of "scoped". You can
also enter it explicitly with the Focus selection action in the ribbon's … menu, which dispatches the fleet.scope.enter action.
Inside Fleet Deck, the content grid is a CSS grid with 4px gaps, a fixed column count, and a 360px minimum
panel width. Each panel's title gets a worktree prefix so you can tell siblings apart, for example feature-auth - Claude Code. The panels are follower-only. Their input bars are locked, and the
maximize, minimize, and add-tab actions are disabled. The primary terminal stays the input source.
Exit with fleet.scope.exit, or by exiting Fleet mode entirely. On exit, the content area
returns to whichever worktree was active before Fleet Deck took over, using a race-safe token so a
mid-flight worktree switch does not strand you on the wrong screen.
fleetScopeMode setting), broadcasts still reach armed terminals in other
worktrees, but the content area does not switch, so you will not see them updating. Scoped mode makes
broadcasting safer by removing invisible receivers.Quick Actions
Once a fleet is armed, a set of bulk action shortcuts operates on every eligible armed terminal at once. Each action reads the agent state of each target, so it only applies to terminals that are in a state to receive it.
| Action | Mac | Windows / Linux | Acts On | Confirmation |
|---|---|---|---|---|
| Accept | Cmd+Y | Ctrl+Y | Waiting armed agents | Never |
| Reject | Cmd+N | Ctrl+N | Waiting armed agents | Five or more waiting |
| Interrupt | Esc Esc (double-tap) | Esc Esc (double-tap) | Working, running, or waiting armed agents | Three or more targets |
| Restart | Cmd+Shift+R | Ctrl+Shift+R | All live armed terminals | Always (with session-loss warning) |
| Kill | Cmd+Shift+K | Ctrl+Shift+K | All live armed terminals | Always |
| Trash | Cmd+Shift+Backspace | Ctrl+Shift+Backspace | All live armed terminals | Five or more targets |
Interrupt is bound as a 350ms double-tap on Esc rather than a standard chord. A chord would collide with the escape stack the rest of the app relies on for closing popovers and dialogs, so the double-tap handler runs separately to avoid that LIFO interference.
Confirmation prompts replace the ribbon body with a single-line strip: a description like Reject 6 agents? or Restart 4 agents? (3 with active sessions), plus Cancel and confirm buttons. Confirmation will not fire while focus is in a text input or an xterm surface, so an active typing session never gets a confirmation prompt stolen out from under it.
Cluster Attention Pill
The Cluster Attention Pill lives in the ContentDock toolbar above the panel grid. It appears only when two or more agent terminals share a notable state, so you don't have to scan for agents that need the same action. Only one pill shows at a time. When several clusters exist, the pill picks the highest-priority one: prompt beats error beats completion, with ties broken by size, then recency.
| Cluster | Appears when | Color |
|---|---|---|
| Prompt | Two or more agents are waiting with a prompt | Amber (clock icon) |
| Error | Two or more agents exited with a non-zero code | Red (alert icon) |
| Completion | Two or more agents completed within the last 30 seconds | Green (check icon) |
Each pill carries an arm button that arms every member of the current cluster in one click, and an X button that dismisses it. Dismissals are per-signature and session-only: the same cluster stays hidden, but a new cluster, with different members or timestamps, surfaces again. Completion clusters dissolve on their own after 30 seconds if you leave them alone.
Saved Fleets
Common armed sets can be saved and recalled later. Saved fleets live in projectSettings.fleetSavedScopes, scoped to the current project, and split into two sections
in the ribbon's selection menu: Pinned (snapshot fleets) and Smart-Sets (predicate fleets).
Snapshot Fleets
A snapshot stores a frozen list of terminal IDs. On recall, Daintree arms every stored ID that still exists and drops any that are no longer eligible (terminals you have since trashed or replaced). If every stored ID is gone, the row is dimmed and recall is guarded, so you can see at a glance that the fleet has gone stale.
Smart-Sets (Live Rules)
A Smart-Set stores a predicate instead of an ID list. The predicate has two fields: a scope
(current worktree or all worktrees) and a state filter
(all, working, waiting, or finished). On recall, the
predicate re-evaluates against the current set of panels and arms whatever matches right now. Smart-Sets
never auto-delete and never go stale, since the result is recomputed every time.
The built-in presets in the selection menu (All waiting, All working, and the rest) are also predicate fleets. Daintree dedupes them out of the Smart-Sets list to avoid showing the same rule twice.
Saving and Managing Saved Fleets
The selection menu has an inline save form. Enter a name, toggle between Snapshot and Live rule, and for Live rules pick a scope and state filter. Save writes the entry to project settings and adds it to the matching section in the menu.
Each saved fleet row shows a live pane count: current matches for predicates, currently-recallable IDs for snapshots. Delete is always confirmation-gated.
The relevant actions are fleet.saveNamedFleet, fleet.recallNamedFleet, and fleet.deleteNamedFleet. They are dispatchable from both the action palette and the ribbon. The
action palette is the only way to recall a saved fleet from a cold start, since the ribbon does not appear
until two or more terminals are armed.
Failure Handling
Broadcasts can fail for two distinct reasons, and Daintree treats them differently.
Permanent PTY errors (EPIPE, EIO, EBADF, ECONNRESET, ENOTCONN, ENXIO, EINVAL) auto-disarm the
failing target. The terminal's PTY is gone or invalid, so retrying would do nothing. No failure chip is
recorded, since it would auto-dismiss immediately.
Transient errors leave the armed set intact and surface a FleetFailureBanner in the ribbon area. The banner names the failed targets and offers a Retry failed button, which re-dispatches the last broadcast payload to the still-failed IDs only. Re-entrancy is guarded, so you cannot stack retries on top of each other.
Raw keystroke broadcasts cannot be replayed. There is no persistent "last payload" for a stream of keystrokes, so if the failure was raw input, the retry chip is suppressed since there is nothing to send. For structured Enter submissions, retry works against the captured payload.
Keyboard Shortcuts
The complete Fleet shortcut reference:
| Action | Mac | Windows / Linux |
|---|---|---|
| Arm or disarm focused terminal (global) | Cmd+J | Ctrl+J |
| Arm or disarm focused panel (in the panel grid) | X | X |
| Arm all eligible in current worktree | Cmd+Alt+A | Ctrl+Alt+A |
| Accept waiting | Cmd+Y | Ctrl+Y |
| Reject waiting (palette fallthrough if none) | Cmd+N | Ctrl+N |
| Restart all | Cmd+Shift+R | Ctrl+Shift+R |
| Kill all | Cmd+Shift+K | Ctrl+Shift+K |
| Trash all | Cmd+Shift+Backspace | Ctrl+Shift+Backspace |
| Interrupt (double-tap, 350ms window) | Esc Esc | Esc Esc |
| Exit Fleet mode | Cmd+Esc | Ctrl+Esc |
Related
- Recipes: saved workspace presets, plus the recipe variable namespace that the Fleet broadcast variables mirror
- Worktrees: the worktree context that powers per-target variable resolution
- Keyboard Shortcuts: the global shortcut reference, profiles, and chord sequences
- Terminals & Panels: what counts as a fleet-eligible terminal and how the panel grid behaves
- AI Agents: running multiple agents side by side, which is the workflow Fleet was built for