---
name: "skill-fleet-dispatch"
type: skill
license: CC BY-NC-SA 4.0
description: Sweep Todo across all active delivery projects for tickets owned by the connector-surface fleet — Pixel, Reach, Sonar, Scout, Aide — test each candidate for eligibility (a leaf, no open blocked-by relation, no unmet dependency, and an input the runner can actually resolve), and run each eligible one via skill-dispatch-run. The Cowork-surface complement to the Forge delivery loop, which already handles F0-RGE code tickets. Use this whenever running the daily fleet-dispatch pass, or on demand ("dispatch the fleet now"). Excludes F0-RGE (owned by routine-delivery-loop), PUL-5E (propose-only, drained by skill-ops-retro) and human. An operator label (the separate operator group, set alongside the executor) names the vertical that owns the outcome and does not by itself exclude a ticket.
---

# fleet-dispatch

Sweeps Todo across the delivery projects for tickets whose `agent`-group label names a connector-surface agent, and runs each one through `skill-dispatch-run`. This is the daily complement to the Forge delivery loop: Forge already drains `F0-RGE` Todo automatically; this closes the same gap for Pixel, Reach, Sonar, Scout, and Aide, so a ticket routed to any of them doesn't just sit in Todo until Aled notices.

## Trigger

Scheduled daily task (`task-fleet-dispatch`), or on demand when asked to dispatch the fleet now.

**Scope follows the invocation — in an interactive session, a project in play is the default.** The scheduled task has no conversation context and always sweeps board-wide. An on-demand request is different: where it arrives mid-conversation with one project in play — its tickets just created, triaged or discussed — and names no scope of its own, "run the fleet dispatch" means *on that project*. Run it with that project as the scope (Behaviour step 1), and say so in one line before the first write: *"Running fleet dispatch on a1.Firmup only — say 'board-wide' to sweep everything."* Run board-wide from a conversation only when the request says so ("the daily dispatch", "the whole board", "everything"), or when no single project is in play; where it is genuinely unclear which the operator means, ask before running wide rather than after. A board-wide run is the one that needs confirming, because its writes land on tickets the operator had no reason to expect touched. (Worked case: an interactive session working a1.Firmup — A1-372/373/374 just created and triaged — was asked to "run the fleet dispatch", swept workspace-wide as written, and acted on MRP-48 and MBN-269 while flagging four a1.MeirionPritchard tickets, none of which the operator expected to see; 2026-08-25, APP-1602, 2026-09-22.)

## Behaviour

1. **Sweep scope.** Todo across all active delivery projects on the delivery teams named in `convention-linear`'s team table — **Apps, MBN, A1, Mr Pritchard**. **Where this list and that table disagree, the table wins**: the team set changed on 2026-07-14 (careerOS demoted to projects under Apps, MBN promoted to its own team), and a scope list that predates a restructure misses a whole team's work silently — the one failure a sweep cannot report. MBN matters here specifically: it is where the venture's brand and growth work lives, so a scope that omits it makes Reach's and Pixel's queue invisible to the dispatch that exists to run it (worked case: the only two `R3-ACH` tickets in the queue sat unseen — APP-548; the sibling class — APP-521, APP-550). Never the Pipeline team (Scout's own pipeline work is separate from this dispatch).

   **The sweep shape is one `list_issues` per dispatch-set label, workspace-wide, filtered to `state: Todo` — five calls, Pipeline excluded client-side.** This supersedes the earlier "compact-field `list_issues` per project, never per team" instruction. `list_issues` does take a compact `fields` param (per `convention-linear` / APP-742), so field selection is available — but that alone does not justify walking projects: a per-project sweep across the four delivery teams is 30-plus calls, Apps alone holding eighteen projects including dormant `gtd.*` and `cOS.*` ones. The label + state workspace-wide read wins on its own merits, and a compact `fields` read complements it rather than forcing per-project walking.

   The per-project rule existed to dodge the overflow trap in `convention-linear` *Reading Linear safely* — and a **label + state** filter cannot hit it: the dispatch-set labels are narrow, `Todo` is a started-type state (not the backlog-type state whose filter overflows), and the payloads are small. So the label-filtered workspace-wide read is both cheaper and **structurally unable to miss a team** — which is the failure the scope rule was really guarding against, and the one a sweep cannot report on itself (APP-548, APP-521, APP-550). Pull a ticket's full body with `get_issue` only once it is selected to dispatch. (Worked case: the 2026-07-18 run swept in five calls and found exactly the two live candidates — MBN-250 and APP-524 — including the MBN work the scope rule exists to protect. APP-587.)

   **Read comments with a small limit first.** The eligibility test in step 3 depends on comment reads, and `list_comments` at its default limit has **timed out at 180s** — twice in one run, on two tickets read in parallel — succeeding immediately on retry at `limit: 10`. A timed-out comment read degrades the sweep silently (an unmet dependency simply goes unseen), so ask for ten comments first and widen only if the thread's tail is genuinely needed (APP-587).

   **An optional scope narrows the result, never the reads.** A run may carry a `project` scope (one or more project names) or a `team` scope, given in the request ("run fleet dispatch on a1.Firmup") or taken as the interactive default (*Trigger*). Apply it **client-side, to the rows the five label + state reads return**: add `project` and `team` to the compact `fields` set, and drop any row outside the scope before the eligibility test in step 3. Do not replace the five reads with per-project or per-team reads. That is the per-project walk this step retired, and the label + state read is what keeps the sweep from silently missing a team (APP-548, APP-587). The scope removes candidates from consideration only: an out-of-scope ticket is neither dispatched nor skipped nor flagged, and no comment is written on it. The end-of-run report opens by naming the scope that ran, "board-wide" included, and gives a one-line count of Todo candidates outside it, so a scoped run that finds nothing is not read as an empty board. (APP-1602, 2026-09-22.)

2. **Dispatch set — the agent-group values this sweep runs.**
   - **Runs:** `P1-XEL` (Pixel), `R3-ACH` (Reach), `SO-N4R` (Sonar), `SC-0UT` (Scout), `A1-DE` (Aide).
   - **Excludes, always:**
     - `F0-RGE` — owned by `routine-delivery-loop`. Never two writers on one ticket.
     - `PUL-5E` — propose-only; drained by `skill-ops-retro`, not run.
     - `human` — Aled's own work.
     - **An `operator`-group label excludes nothing on its own.** Since 2026-09-03 the operator values (`operator-a1`, `operator-apps`, `operator-bara`, `operator-bottrader`, `operator-mrp`) live in their own `operator` label group, set independently of the executor, so a Todo ticket may carry `P1-XEL` + `operator-bara` — Pixel work that BARA owns — and is dispatched normally. The former exclusion tested for an `operator-*` value *inside* the `agent` group; that test now matches nothing and must not be relied on (APP-1103). What it protected — a ticket the operator is holding for a judgement — is expressed by state, not by label: such a ticket rests In Review or Blocked (`convention-linear`, *Executor*), outside this sweep's Todo scope. Operators still drive their own loop and dispatch through Relay; this sweep never selects on the `operator` group at all.
   - **Anomaly, not a run candidate:** `R3-LAY` sitting on a ticket in **Todo** is a triage marker that should already have been evicted (see `convention-linear` *Agent routing*) — surface it for re-triage in the end-of-run report; never run it as if it were a real executor.
   - Filter with the agent-group label's **bare value** (e.g. `P1-XEL`, not `agent:P1-XEL`) — the same read-filter trap `convention-linear` names for every Linear-reading skill.

3. **Eligibility — test every candidate before dispatch.** Membership of the dispatch set is *selection*, not eligibility. Mirroring `skill-exec` (*Eligibility scan*), a selected ticket is **eligible** only if it is a **leaf ticket** (never `type:epic` — epics are outcomes, closed by Aled when their children are done), **has no open blocked-by relation** (the blocker ticket is not Done or Canceled), has no unmet dependency named in a PM comment, and **names an input the runner can actually resolve** (the fourth test, below). Skip an ineligible ticket, name it and its reason in the end-of-run report, and move on — never move it to Blocked. A ticket correctly waiting on its blocker is not blocked work; it is work not yet due, and marking it Blocked would fabricate a problem for Aled to clear.

   **Why the structural tests are load-bearing.** The four-placement model (`skill-triage`, `convention-ticket`) offers **placement 2 — sequencing dependency**: a refined leaf whose spec is complete and whose only outstanding gap is ordering rests in **Todo + executor + blocked-by**, and is documented as safe *because the runner self-skips while the blocker stands*. That self-skip was real for Forge and only for Forge — `skill-exec` tests blocked-by explicitly, while this sweep filtered on label and state alone and `skill-dispatch-run` step 3 claims whatever it is handed. So one canon placement silently meant two different things by executor: on `F0-RGE` it parked a ticket safely until its blocker cleared; on `P1-XEL` / `SO-N4R` / `R3-ACH` / `SC-0UT` / `A1-DE` it handed blocked work straight to the next daily dispatch, and nothing in the loop reported that it had. A rule stated executor-agnostically must hold executor-agnostically, or it is a trap for the triage pass that follows it correctly. (Worked case: APP-560 (IA/wireframes, `SO-N4R`) and APP-525 (design exploration, `P1-XEL`) were both fully specified, both carried correct blocked-by relations on Aled's positioning sign-off in APP-524, and both read as clean placement-2 candidates. Routing them to Todo would have started the IA and the exploration ahead of that sign-off, against the strict staging he set on 2026-07-15. The triage run parked them at placement 3 instead — defensible, but reached by reading `skill-exec`'s source rather than by following the placement rule, which is not a method that scales — APP-567.)

   **This test is the belt, not the braces.** It fails a mis-placed ticket safe; it does not license placing fleet work at placement 2 as a matter of course. A fleet runner reads only its own ticket (`skill-dispatch-run` *Guardrails*), so a fleet leaf whose brief depends on another ticket's output cannot be run from the blocker's output at all — that output has to be lifted into its body first, which makes it a harvest dependency and rests it at placement 3. Both remain true: the harvest rule is why fleet work usually shouldn't sit at placement 2, and this test is why it is safe when it does.

   **Input resolvability — the fourth test.** The first three tests are about the ticket's *position* in the graph; this one is about whether its inputs exist at all. A candidate is **ineligible** if the only actionable input it names cannot be resolved from the ticket itself — a Figma reference given as a bare node id with no file key or URL, a "match the comps" with no comp attached or linked, a built-site comparison with no address, an asset behind a credential the fleet does not hold. Skip it, name it in the report under **not ready — input unresolvable**, and leave its state alone. **One disposition is distinct from not-ready and is reported separately: *re-route to `human`*.** Where the input the ticket names is not a missing link anyone could paste in but something **no connector could ever supply** — the Owner's own eyes on a named physical display, a device in his hand, a judgement only he can make — the ticket is not un-ready, it is mis-routed: the correct executor is `human`, and no enrichment will change that. Report it under **re-route to `human`** with the reason, so `skill-triage`'s consumer for standing not-ready comments moves it to Refinement + `human` as the operator's own work rather than as a ticket awaiting a file key. This sweep still writes no state. (Worked case: A1-358 — "confirm 2px holds on the 2025 panel" across two named physical displays, labelled `P1-XEL`, held as not-ready alongside five fixable absences on 2026-08-17 — APP-991.)

   **Structural ineligibility is a separate disposition from not-ready — escalated once, never re-skipped.** An unresolvable input has two shapes and they clear differently. A **transient** absence is one the ticket could carry and does not yet — a missing file key, an unattached comp, a built-site comparison with no address — and it clears the moment someone pastes the link, so carrying it in the report and staying silent on the ticket is exactly right. A **structural** one is not an absence at all: the surface that owns the ticket can never reach the named input, whatever anyone writes into the body. Pixel's surface is Figma plus Cowork with **no repo access** (`agent-pixel`, *Surface* — the connector surface carries no GitHub credentials, APP-651), so a ticket whose only actionable input is a code repo is ineligible **by construction**, and every future sweep will find it ineligible for the same reason and write nothing. Separate them by asking what would clear it — and there are **three** answers, not two: if the answer is a link anyone could paste, it is transient; if the answer is a **different surface, or a credential the fleet does not hold**, it is structural; and if the answer is **an attended session** — someone present to answer an interactive approval prompt — it is structural on a different axis, because the input and the credential are both in hand and it is the *run mode* that does not match.

   **Run mode is the third structural axis, and it is read from a marker, never inferred from prose.** `task-fleet-dispatch` is an unattended scheduled task by definition, so a ticket whose only write path is an approval-gated connector call — `use_figma`, the one write-capable Figma tool, is the standing instance (`skill-dispatch-run`; APP-963) — can never be completed by this sweep, however complete its brief. Attendance appears nowhere in canon today, so an approval-gated ticket reads as a clean candidate on all four tests and the sweep is left two bad options: re-derive "must run attended" from a multi-week comment thread every single day, or miss it and claim straight into a fresh **Blocked · Urgent · Aled** stop on the same absence each sweep — the recurring-Urgent cost the pre-claim check exists to convert into one report line. **The marker is a label: `dispatch:attended-only`.** It is set once — by the operator, or by the leg that recorded the gated-call stop — on a ticket confirmed to need an interactive session, and this sweep tests for it in the compact `list_issues` read that already carries the labels, so the test costs no comment-thread read at all. A candidate carrying it is **ineligible**: skip it, write no comment, leave its state alone, and report it in step 5 under **not ready — needs attended session**, naming the gated call. **That disposition is distinct from the other two and must not be collapsed into either:** unlike *re-route — surface cannot reach the input* there is no surface to re-route to, and unlike *not ready — input unresolvable* no link will ever clear it — what clears it is the Owner running the ticket attended, which is a scheduling act, not an enrichment. Where the label is absent but the ticket's thread plainly says the work must run attended, the sweep's one write is to **ask for the label in its report** rather than to pay the thread read again next sweep; once the label is on, the daily cost is zero. (Worked case: A1-344 — *MIGRATION: retire the v1 header stack*, `P1-XEL` — sat in Todo on 2026-09-22 as a clean candidate on all four tests: a leaf, no `blockedBy`, its one open Figma judgement call already settled, its input on paper resolvable. Two standing 2026-08-18 comments on the ticket say the opposite — *"it must not be picked up by unattended dispatch"* and *"Owner: this can run attended now … run A1-344 attended so the approval can be granted interactively"* — and that run avoided the claim only by reading roughly a dozen comments back to 2026-08-15. A1-344 carries no attended-only label today, so setting it is the ticket-side half of this change. APP-1642, 2026-09-22; sibling of APP-963, which fixed `skill-dispatch-run`'s behaviour on *hitting* the gate but added no pre-claim signal, and distinct from MBN-257's missing-access family, APP-1472.) A structurally ineligible ticket is reported under **re-route — surface cannot reach the input**, naming the surface and the class of input it cannot reach, and it is escalated **once** — a single comment stating the structural constraint, so `skill-triage`'s consumer for standing not-ready comments can move it to a surface that holds the input or to `human`, the same route the *re-route to `human`* disposition above already takes. This sweep still writes no state. (Worked case: MBN-257 — *AUDIT: theme-files design-system consistency*, `P1-XEL` in mbn.brand, whose criteria require reading the `assoc-one/mbn-theme` theme code and whose own notes say it "needs read access" to that repo. Flagged not-ready by the 2026-09-09 run, surfaced unchanged on 2026-09-15, and re-read on 2026-09-18 still Todo on `agent:P1-XEL` with no update since 2026-09-15. Three sweeps, one comment, no route out — APP-1472.)

   **The test is "can the input be obtained", never "is the brief good enough".** It fails only on a reference the ticket *names and cannot resolve* — a bounded, checkable condition of the same kind as blocked-by. It does not judge whether acceptance criteria are strong, whether the spec is complete, or whether the work is worth doing. Those are refinement's calls and `skill-dispatch-run`'s, and pulling them into selection would turn eligibility back into a prose read, which is exactly what the candidate-selection discipline forbids. Keep the line where it is stated: a named-but-unreachable input fails here; a thin brief does not.

   **The fifth test — a time reference the clock has already passed.** The four tests above ask where a ticket sits and whether its inputs exist; none asks whether its brief still means what it meant when it was written. A brief naming a relative window — "at 20:00 today", "after tonight's other two evening sessions" — resolves against the day it was filed, not the day the sweep reads it, and a same-day ask that rests in Backlog overnight has missed its own window before any sweep reaches it. So before claiming, resolve every day or time reference in the brief against the ticket's `createdAt` and the session clock. A reference to a window that has already elapsed makes the ticket ineligible: report it under **not ready — stale timing**, leave its state alone, and comment once asking whether the Owner handled it in person and whether it needs re-timing. The disposition is distinct from the three above, because what clears it is a new time from the Owner rather than a link, a surface or an attended session. It costs most on a calendar-booking ticket, where a literal run writes an entry for a time already past, which is the one result an unattended calendar writer must not produce. A reference the clock has not yet reached is live and dispatches normally. (Worked case: APP-1845, A1-564 and APP-1847 — three `A1-DE` holds filed live at the 2026-09-29 stand-up, naming "at 20:00 today", "after 20:00 today" and "after tonight's other two evening sessions" — reached Todo on 2026-09-30 at 06:17–06:18, about ten hours after their window closed, and passed all four tests as clean candidates. A1-566, filed in the same batch and naming "Thursday", was still live and dispatched normally, which is what separates a timing defect from a block on this ticket family. The ninth fleet-dispatch run caught the three only by comparing "today" against the session clock before dispatching, and all three were still in Todo, unassigned, on `A1-DE` a day later — **observed** 2026-10-01 — APP-1887. A1-406, Canceled, is an earlier instance of the same class. The promotion lag that produced them, Backlog to Todo the next morning, is `skill-triage`'s side of the same case and is not addressed here. The calendar is not readable from the refine surface, so the booking outcome is reported, not observed.)

   **Why the check has to happen before the claim.** Without it, an un-runnable ticket is *eligible*: `skill-dispatch-run` claims it (its step 3), discovers the missing input at the run (step 4), and stops at its Blocked fail-safe (step 6) — **Blocked · Urgent · `human` · Aled**. That is the right behaviour for a blocker found mid-run and the wrong shape for one knowable from the ticket body before a single connector call is made. The cost is not the wasted claim; it is that every sweep re-pays it, because clearing the Block returns the ticket to Todo without adding the missing link, so the next run claims and Urgent-pings again on the same absence. A pre-claim check converts a recurring Urgent into a line in a report. (Worked case: A1-295 and A1-306, both `P1-XEL` in a1.MeirionPritchard · Work section, each named a Figma reference — node 2513-8731, comment #63 — with no file key, no URL and no built-site reference, so a bare node id resolved to nothing. Both passed the structural tests, both were claimed, and both were Blocked · Urgent · Aled within the same minute on 2026-08-10. A1-295 was then returned to **Backlog** by the Owner an hour later, which is the confirmation that it was never ready for Todo rather than a ticket that met a genuine blocker — APP-762.)

   **This is the belt; the intake gap is upstream.** Both tickets reached Todo from Figma comments without the file link a runner needs, and no canon artefact owns that intake path (APP-743, open in the Pulse queue). The readiness test stops the churn at the dispatch end. It does not make an unrefined ticket runnable, and it is not a reason to leave the intake unfixed — an un-ready ticket surfaced here belongs back at the refinement gate, which is triage's write to make, not this sweep's.

4. **Per-ticket isolated dispatch.** For each eligible ticket, hand off to a per-ticket isolated context that loads only that ticket's owning agent's profile and `skill-dispatch-run` — the parent sweep never holds every agent's full skill set loaded at once. Run `skill-dispatch-run` (see that skill for the claim → resolve → run → hand-off cycle). Different agent labels may run concurrently (see `skill-dispatch-run` *Guardrails*); never run two tickets under the same agent label at once — **claim-by-state** (the move to In Progress) is the idempotency lock, exactly as `skill-exec` uses status as the lock for `F0-RGE`.

5. **End-of-run report — and confirm each handoff landed.** Short summary, grouped by outcome: dispatched (ticket → agent → capability skill run), completed / handed to In Review, skipped (ineligible — wrong state, wrong label, or an open blocker, named), **not ready** (input unresolvable but transiently so, with the missing input named), **re-route — surface cannot reach the input** (structurally ineligible: the surface named, the class of input it cannot reach named, and the one escalation comment stated as written or already standing), **missed handoff** (a candidate previously claimed by this loop and returned to Todo without reaching a hand-off), blocked (with the blocker named), and any anomalies — a `R3-LAY`-in-Todo ticket, or an `agent:*` label in the dispatch set with no capability skill able to resolve a match. A clean run with nothing eligible reports exactly that, not silence.

   **Every ticket this run dispatched must have left Todo.** `skill-dispatch-run` ends each ticket at **In Review** + `human` + assigned to Aled (its step 5) or at **Blocked** (its step 6); either way the ticket leaves Todo. So a ticket this run dispatched that is *still in Todo* when the run ends is a **failed handoff**, not a completed one — report it as such and name it, rather than counting it as dispatched and moving on. This check reads state, not content, which is exactly what makes it this sweep's to own: it needs no judgement about whether the work is finished.

## Guardrails

- **Never touches `F0-RGE`.** That queue is Forge's, drained by `routine-delivery-loop` — this sweep must never claim, comment routing intent onto, or otherwise act on an `F0-RGE` ticket.
- **Never touches Pipeline.** Scout's pipeline captures and qualifies through its own skills (`skill-pipeline-sweep`, `skill-pipeline-qualify`); this dispatch is delivery-project Todo only.
- **Never runs `PUL-5E` or `human` tickets.** Those lanes have their own owners and cadence; running them here would be a second writer. An `operator`-group label is ownership, not a lane — it neither selects nor excludes (step 2); the old `operator-*`-in-the-`agent`-group exclusion is retired with the 2026-09-03 group split (APP-1103).
- **Never dispatch an ineligible ticket, and never mark one Blocked.** Selection is by label and state; eligibility is the leaf / blocked-by / unmet-dependency / input-resolvability test in Behaviour step 3. Skip and report — the blocker is already tracked, and a second Blocked ticket about it is noise, not signal.
- **Never claim a ticket in order to Block it.** A claim is this loop asserting the ticket can be run; a claim followed immediately by a Blocked · Urgent · Aled stop, on grounds legible in the ticket body before the claim, spends the Owner's attention on a check the sweep could have made for free. Blocked remains correct for a blocker discovered *during* the run — a connector refusal, a credential, a requirement too ambiguous to act on safely (`skill-dispatch-run` step 6). The readiness test only moves the pre-knowable class earlier, where it costs a report line instead of an Urgent.
- **One comment, never a repeat ping.** Where a candidate is skipped as not ready, at most one comment ever names the missing input on that ticket; if such a comment already stands and nothing material has changed, the sweep stays silent and carries the item in its report instead. This is `convention-linear`'s standing shape for a surfaced item — escalate by age and priority, never by restating the same comment each run — and it is what stops a daily sweep turning a single missing link into a daily notification. **The rule is about repetition, not about silence — and it has one carve-out.** Where the skip is **structural** rather than transient, the one comment that stands must be the structural one. If the comment already on the ticket names only a missing input, replace it **once** with the structural statement — the surface, the class of input it cannot reach, and the re-route that is needed — and then stay silent for good. One comment per ticket still holds; which comment it is, is decided by the disposition, not by which sweep got there first. Silence on a structurally ineligible ticket that carries no re-route comment is the failure this carve-out exists to stop: the guardrail then reads as a fix, and the composite of a correct readiness test and a correct anti-ping rule is a ticket re-skipped on every sweep for ever, with nothing in the queue tracking the gap (APP-1472; MBN-257, three sweeps from 2026-09-09 to 2026-09-18).
- **Never re-run a ticket whose deliverable has already landed — and never infer that from a comment's content.** A connector-fleet ticket sitting in **Todo** with its deliverable already posted reads to this sweep as un-started work, so the next pass has every reason to pick it up and redo it: duplicate work, and possibly a second, contradictory draft laid over one already awaiting Aled's call. The fix is *not* for this sweep to read comment bodies and guess whether one contains a deliverable — that is inference-by-feel, exactly what the candidate-selection discipline warns against, and it would make the dispatch set depend on prose. The fix is that the handoff lands where the work finishes: `skill-dispatch-run` step 5 already ends every run at In Review + `human` + Aled, which removes the ticket from this sweep's scope deterministically and makes it visible as an Owner item. (Worked case: APP-524 sat in Todo, `SO-N4R`, unassigned, with a full positioning draft posted asking Aled to approve, amend, or reject. `convention-linear` (*Issue states*) already names the correct resting shape — an item whose only outstanding need is the Owner's review belongs In Review, carrying `human`, assigned to Aled — it simply was not applied at the handoff. `skill-coordinate` Pass 4 is the natural cleanup home but is scoped to *stranded In Review*, so no pass owned the Todo case and coordinate left it untouched rather than improvise — APP-566.)

  **The two deterministic signals, and the one residual case.** The open question that recurrence left — is there a state-only signal that beats reading prose? — has two partial answers, both cheap, both already to hand:
  - **The `agent` label is the guard.** The group is single-select, so the handoff `skill-dispatch-run` step 5 performs — In Review + `human` + Aled — *evicts* the executor value, and the ticket leaves this sweep's selection set on the label filter alone, before state is even considered. This is why the handoff must change the label and not only the state: a ticket left on `SO-N4R` in Todo is, to every state-only reading, un-started work. (Worked case: APP-524 stayed in Todo on `SO-N4R`, unassigned, from 2026-07-15, and was re-selected by the sweep on 2026-07-23, 07-24, 07-27, 07-29, 07-31 and 08-10 — six runs, each held out of dispatch by an operator reading this guardrail rather than by any rule the loop could execute. It was moved to In Review + `human` + Aled on 2026-08-10; that single write removed it from the set deterministically and cost nothing thereafter — APP-657, APP-566.)
  - **State history separates a returned run from an un-started one — and age separates an abandoned claim from a live one.** A candidate whose `stateHistory` shows a prior **In Progress** under a dispatch-set label has been claimed before and has come back to Todo without reaching a hand-off. That is a **missed handoff**, not fresh work, and the default is to leave it: do not claim it, name it in the report, and leave it to the run that abandoned it or to the Owner. This reads a structured field on the `get_issue` already made before dispatch, so it costs nothing extra and involves no prose. **Two named releases clear the guard, and without them the default is a permanent skip.** Stated bare, "leave it to the run that abandoned it" names a run that no longer exists — a dispatch run is a single session and holds no claim past its own end — so a Todo ticket carrying a prior claim is skipped on every sweep for ever, and `skill-coordinate` Pass 4 (*Abandoned dispatch claim*) cannot reach it either while that case keys on In Progress. The two releases are:
    - **An explicit re-arm marker, good for exactly one sweep.** The re-arm is a comment on the ticket whose first line is the literal marker `[re-arm] dispatch claim released — one sweep`, followed by the date and who released it. It is the operator's write, or the owning `operator-<venture>` leg's, made when the coordinate Pass 4 surface is confirmed, and it is what that pass's prescribed release — **Todo + [agent] with the claim explicitly re-armed for one sweep** — resolves to. The two artefacts must name this same marker: a release the releasing pass prescribes and this loop cannot recognise is the disagreement that produced the recurrence. The marker clears the guard only where its comment is **newer than the most recent In Progress → Todo transition** in `stateHistory`, and it is **consumed by the claim** — the run that claims on a re-arm names the marker comment in its dispatch line, and a second abandonment needs a fresh marker. This is a literal-string read of one comment, not a prose judgement.
    - **Age, where the abandoning run is provably over.** Because a run is a single session and never spans days, a claim returned to Todo longer ago than one full daily sweep cannot still be held by a live run. Where the return to Todo is **more than three days old** (the same rung `skill-coordinate` Pass 4 uses for this family), the ticket still carries its dispatch-set `agent` value, it is unassigned, and `stateHistory` shows **no In Progress under any dispatch-set label since that return**, the prior claim is orphaned and the ticket is **re-claimable without a re-arm**. Claim it and report it under **dispatched — re-claimed after an orphaned prior claim**, naming the abandoned claim's date, so the re-claim is visible rather than silent. The do-not-redo-finished-work half of the guard is untouched: a ticket whose deliverable landed has had its executor value evicted by the hand-off (*The `agent` label is the guard*, above), so a ticket still carrying the executor value in Todo is by construction one that never handed off. This stays a state-only test — a date, a label, an assignee and a state history, with no comment body read beyond the literal re-arm marker.
    - Both releases exist because the guard's own remedy landed in one half only. APP-1357 described the trap in both states and its remedy was authored as coordinate's In-Progress-keyed Pass 4 case; MBN-263 — `R3-ACH`, mbn.brand — had been returned to **Todo** on 2026-09-11, three days before APP-1357 closed Done on 09-14, so it sat in the uncovered half and was untouched by any dispatch run for the eleven days that followed, until the Owner asked directly why the fleet had not worked it. Worse than uncovered: this guard prescribed no re-arm marker at all while coordinate prescribed one, so a Todo ticket carrying a prior claim was a permanent skip by construction (APP-1645, 2026-09-22; APP-1357).
  - **The residual — a deliverable posted by hand — has no state-only trace.** Where the work was done outside the loop there is no claim in the state history and no label change: APP-524 went Backlog → Refinement → Todo and never entered In Progress, so neither signal above would have caught it. That case cannot be caught here without inferring from comment bodies, which this sweep does not do. It belongs to whoever posted the deliverable, and the standing ask is unchanged — post the deliverable and perform the handoff in the same action, so the resting state and the work agree.
- **Never guesses a capability skill on the dispatch-run's behalf** — that resolution, and the ambiguous-match Blocked stop, live in `skill-dispatch-run`; this sweep only selects *eligible* tickets, it doesn't second-guess how they're run.
- **Widen the dispatch set deliberately, not by default.** Start with the agents whose skills are mature and whose connectors are wired; add an agent value to the dispatch set only once its capability skills and connector access are ready, not automatically because the label exists in the `agent` group.
- **Sweeps by label + state, workspace-wide — never by walking projects.** Five `list_issues` calls, one per dispatch-set label, `state: Todo`, Pipeline dropped client-side. A label+state filter is overflow-immune and cannot silently miss a team, which is what the old per-project rule was actually protecting. `list_issues` does take a compact `fields` param (per `convention-linear` / APP-742), so a compact read complements this workspace-wide sweep rather than forcing the old per-project walk (APP-587). A `project` or `team` scope filters that result client-side and never turns the sweep into a project walk. In an interactive session the default scope is the project in play, and a board-wide run from a conversation is confirmed first (*Trigger*; APP-1602).
- **Reads comments at a small limit first.** `list_comments` at the default limit has timed out at 180s and degraded the eligibility test silently; start at `limit: 10` (APP-587).
- Idempotent per run: a ticket already In Progress under its agent label is mid-run and is skipped, never re-claimed.

## Setup

- Deployed as a Cowork scheduled task (`task-fleet-dispatch`), daily.
- Connectors: Linear (the sweep and every ticket write) plus each dispatched agent's own connector, wired per agent as it's added to the dispatch set — Figma (Pixel, Sonar), Gmail/Shopify (Reach), Gmail (Scout), Calendar (Aide).
- Start with a narrow dispatch set and widen as trust and connector coverage grow, per the locked decision (APP-448, 2026-07-06): day one runs only the agents whose skills and connectors are ready.

On finish, run skill-ops-retro capture on this run's friction: **search the Pulse queue first and add the evidence to a same-root note if one exists, otherwise file a new FRICTION note** for the drain pass to assess, in capture step 3's literal shape — team Apps, project os.Claude, `FRICTION:` title, labels `task` + `work:configuration` + `PUL-5E`, Backlog, unassigned — filing the friction, never the run report.
