Skip to main content

Broadcasting & Runs

How a Fleet broadcast works: live keystroke mirroring, per-target variable resolution and overrides, supervised runs with durable history, and what happens when a target rejects the write.

Reviewed

Two Broadcast Paths

Once a fleet is armed, everything you type in the primary terminal goes to every armed peer. There are two paths, and they behave differently enough to be worth naming.

  • Raw input mirroring replicates keystrokes as you type. It is a live stream with no payload and nothing to replay.
  • Structured submission is what Enter does. The composed draft becomes one payload per target, with variables resolved per worktree, and the result is tracked as a supervised run.

Arming is the consent boundary for both. There is no confirmation step on submit — once a fleet is armed, submit just submits. Destructive payloads, large pastes, and multi-line text all fan out immediately. If you want to check what each target will receive before sending, the per-target preview is the review surface.

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

The input bar itself tints amber on the fleet primary, in the same amber family as the pill, so the shell around what you are typing says "Enter broadcasts" at the point of typing rather than only in a chip above it.

Note
Typing in a follower pane does not broadcast. The primary is the only origin. This is a deliberate escape hatch for when you need to send something to one armed terminal without disarming the others first.

Submitting with Enter

Pressing Enter in the focused primary sends the composed draft to every armed peer. The progress counter on the ribbon shows completed/total while the fan-out is in flight, except on a small fleet where the broadcast finishes inside the Doherty threshold, in which case the counter is suppressed to avoid a flicker. A successful, uncancelled fan-out fires a one-shot flash on the ribbon and a short confirmation sound.

A second Enter while a broadcast is in flight pre-empts the first. The earlier run is recorded as superseded rather than silently dropped.

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 becomes clickable when the current draft contains at least one {{variable}}. Click it to open the Fleet broadcast preview popover. Each row is one armed target, showing 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 this broadcast. The pill grows a divergence dot whenever any override or skip is pending, so you can tell at a glance that the fan-out is no longer uniform. Pressing Esc inside an open textarea reverts that target to the resolved default and blurs the field rather than closing the popover.

Overrides and skips are per-broadcast. Enter snapshots them at press time, so a popover that closes mid-send cannot change what was already captured, and the store is cleared once the send settles. They are also reconciled against the armed set: a pane that leaves the fleet — by manual disarm, or by auto-prune when it is trashed or its PTY dies — drops its override and its skip flag on the way out. Without that, a disarmed-then-rearmed pane would carry a stale skip and quietly miss the next broadcast.

Skipping every target is handled explicitly: the Enter is absorbed, nothing is dispatched, and the announcer says the broadcast was skipped because all targets were excluded.

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. Names are case-insensitive.

VariableResolves ToExample
{{issue_number}}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 the worktree name if no branch is set)feature/issue-412

A variable that cannot be resolved for a given target — no linked issue, no worktree association — resolves to an empty string, and the preview row flags it. A name Daintree does not recognize is left in the text unchanged rather than blanked, so a typo reaches the agent visibly instead of silently vanishing.

Supervised Runs

A structured broadcast does not end when the bytes land. Daintree tracks it as a run: one submission, followed by a watch phase that follows each target's agent state until every target settles. Settling means leaving working or directing — an agent that comes back waiting for input has settled, because it is done needing the machine and started needing you.

StatusMeaning
submittingThe fan-out is still writing to targets.
watchingEvery write has landed; targets are still working.
completedEvery sent target settled.
cancelledYou aborted mid-submission.
failedNo target accepted the write.
supersededA newer broadcast pre-empted this run before it settled.

The ribbon reuses the progress counter's slot for this. While a run is watching it shows live counts — 3 working · 1 waiting · 2 done — and a finished run leaves a dismissible Run finished summary. A run that failed outright reads Run failed · nothing sent. Cancelled and superseded runs render nothing, because the announcer and the progress counter already covered them.

The count chip's armed list is the per-target drill-down for the same run: a pane whose write was rejected carries a Send failed marker beside its live agent-state badge.

Run History

When a run finalizes it appends a record to a durable history kept in the main process, alongside the records that recipe runs write. Because it lives outside the renderer it survives reloads and window eviction. Read it at Settings → Integrations → Run history.

A fleet record carries the run's outcome, a short preview of the draft, target and success and failure counts, and a per-target list with each pane's title and final state. References are stored as snapshots taken at run time and never re-resolved, so a record stays readable after the terminal is closed or the worktree is deleted. The buffer is a ring capped at 100 records, roughly 200 KB on disk. Scrollback and git state are deliberately not recorded; the record is the automation outcome, not a transcript.

Reading a Run from an Agent

fleet.getRunStatus returns the current run in one call over MCP: per-target submission outcome, whether each failure was permanent or transient, a live agent-state snapshot, settled flags, and aggregate counts. It is read-only and dispatches nothing.

Note
There is deliberately no MCP tool that broadcasts to a fleet. An agent orchestrating its own fan-out sends one terminal.sendCommand per terminal and watches with batched terminal.getStatus calls or a bounded wait. The agent state in a run snapshot is a heuristic and a parsed check result is not a process exit code — confirm both before acting on them.

Large Broadcasts and Cancelling

Daintree batches large broadcasts so the renderer stays responsive. When a payload reaches 100 KB and there are more than five targets, the fan-out runs in batches of five, yielding to the event loop between batches so the main thread can render and drain IPC.

While a batched broadcast is in flight the ribbon shows a Cancel button next to the progress counter. Cancelling stops the remaining batches and dismisses the counter.

Failure Handling

Broadcasts 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 PTY is gone or invalid, so retrying would do nothing. No failure chip is recorded for these, because disarming would auto-dismiss it immediately.

Transient errors leave the armed set intact and surface a failure banner above the ribbon reading Broadcast failed, naming how many terminals rejected the write and, when both happened in the same broadcast, how many unreachable terminals were disarmed alongside them. A Retry failed button re-dispatches the last payload to the still-failed IDs only, through fleet.retryFailures. Re-entrancy is guarded, so retries cannot stack.

Raw keystroke broadcasts cannot be replayed — there is no persistent payload for a stream of keystrokes. When the failure was raw input, the banner says so ("Single keystrokes can't be replayed") and omits the retry button entirely rather than showing a dead one.

Note
The two paths classify unknown errors differently, on purpose. On a structured submit an unrecognized rejection is treated as transient, because it may be an infra hiccup where your retry is the right recovery, and wrongly disarming would silently drop fleet membership. On the raw keystroke path a missing errno means a single keystroke that is not worth replaying, so the target is disarmed instead.