---
name: "skill-dispatch-run"
type: skill
description: 'Run a single Todo ticket through whichever connector-surface agent owns it — Pixel, Reach, Sonar, Scout, or Aide — the Cowork-surface sibling of skill-exec. Use this whenever skill-fleet-dispatch hands off an eligible ticket: load the owning agent''s profile, pick the one capability skill that matches the work, claim the ticket, run it on that agent''s connector surface, and hand off in the shape the invoked skill defines. Never invoked directly by a schedule — always called per-ticket by skill-fleet-dispatch.'
license: CC BY-NC-SA 4.0
---

# dispatch-run

The generic ticket-runner for the connector-surface fleet. Where `skill-exec` is Forge's ticket-runner against a repo, `skill-dispatch-run` is the equivalent for Pixel, Reach, Sonar, Scout, and Aide against their own connectors (Figma, Gmail, Shopify, Calendar). It does not itself define what the work *is* — each agent's capability skills (`skill-brand-system`, `skill-content-studio`, `skill-discovery-audit`, and so on) do that; this skill's job is to read a ticket, resolve which capability skill applies, run it, and hand off cleanly.

## Trigger

Called per-ticket by `skill-fleet-dispatch` for each eligible ticket in its sweep. Not a standalone schedule — it has no trigger of its own.

## Behaviour

For the ticket handed to it:

1. **Identify the owning agent.** Read the ticket's `agent`-group label (`P1-XEL`, `R3-ACH`, `SO-N4R`, `SC-0UT`, or `A1-DE`) and load that agent's profile (`agent-pixel`, `agent-reach`, `agent-sonar`, `agent-scout`, `agent-aide`) for its mission, remit, and — critically — its **Composition** skill list.
2. **Resolve which capability skill to run.** A connector-surface agent's skills are *capability* skills ("define a brand system", "run a discovery audit"), not ticket-runners — the ticket itself doesn't name a runner the way an `F0-RGE` ticket implicitly means "run skill-exec". Resolve the match in this order:
   - An explicit `skill:<name>` hint left by triage or a PM comment on the ticket — prefer this when present **and the owning agent's Composition holds the named skill**. A hint naming a skill outside the owning agent's Composition (checked against the profile loaded in step 1) is **discarded, not honoured and not treated as ambiguity**: note the discard in the hand-off comment, fall through to the label/title test below, and let the ambiguous-match stop fire only on *that* test. Honouring it would run a skill the agent does not hold; treating it as "no unambiguous match" would take the Blocked path on a ticket that has one clean candidate. (Worked case: A1-295, routed `P1-XEL`, hinted `skill-design-parity` — in Prism's Composition, not Pixel's; the 2026-08-22 run fell through to `skill-product-design` on the `work:experience` label and title, ran, and handed off normally — APP-1043.)
   - The ticket's `work:*` label or title matched against the owning agent's Composition list. **A Composition list gives skill names, not purposes** — `agent-pixel`'s is a bare comma-separated list, and `agent-roster` carries no per-skill purpose lines for any agent — so a name-only read resolves a ticket whose deliverable is "define a rubric" or "restate a figure" no better than a guess, and the earlier wording of this bullet, which claimed otherwise, sent runs to two places that do not hold it (APP-1637).
   - **Before Blocking, read the candidate skills' own frontmatter and the owning agent's Reads list.** Each capability skill states its own purpose, and its read modes, in its frontmatter `description` — that is the one copy of a skill's purpose that cannot drift from the skill. `skill-brand-system`'s description names reading or **scoring** an existing system against the design-system standard, which is the clean match a name-only read misses. The owning agent's **Reads** list (the standards and conventions it owns) often names which skill owns a mode outright: `standard-design-system`'s *Scoring an existing system* section states that scoring an existing design system pass/partial/fail per bar **is** `skill-brand-system`'s read mode. A Blocked stop taken without these two reads is a lookup failure dressed as a capability gap. (Worked case: APP-1622, "define the design-system quality scoring model and dimension set", `work:product` on `P1-XEL` — no Pixel skill name means "define a quality scoring rubric", and the resolution became unambiguous only on reading `standard-design-system`; a run with less time to cross-reference would plausibly have Blocked a ticket that had one clean answer — APP-1637.)
   - **Where the fitting skill sits outside the owning agent's Composition, re-route — never stretch, never Block.** Sometimes the reads above *do* find the fitting skill, and it is simply not this agent's: it sits in another agent's Composition, or it is operator-owned by design. That is a **remit** gap, not a lookup miss, and it has two wrong answers. Stretching a Composition skill to cover it runs a skill outside its own trigger and hides the gap inside a completed ticket; Blocking it treats a ticket with a known correct owner as though nobody could run it. So: **report a re-route to that owner** — name the fitting skill, name whose Composition holds it (another `agent`-group executor, or the operator via `operator-<venture>`), and hand off for the re-route rather than running. Do not re-label or re-assign the ticket yourself; this runner resolves and runs, it does not re-route work (that is `skill-fleet-dispatch`'s *re-route to `human`* shape, APP-991). A re-route is a clean, legible outcome; a stretched run is not. (Worked case: MBN-302, "Restate £500 budget envelope and unit economics without Quivo cost line", `R3-ACH`, 2026-09-22 — the body and a triage comment both named `skill-unit-economics`, which is the operator's by its own Setup ("Owned by the operator; `operator-bara` first") and is in no fleet agent's Composition; the hint was correctly discarded, the label/title test found nothing, and the run stretched `skill-campaign-report` past its own trigger on a sibling-ticket precedent rather than naming the re-route — APP-1638.) **A re-route is not a licence to grow a Composition.** A capability is added to an agent only where this shape **recurs**; one occurrence is a re-route, not a missing skill.
   - **If no unambiguous match exists, do not guess.** Move the ticket to **Blocked**, set priority **Urgent**, label `human`, assign Aled, and comment which candidate skills were considered and why none was a clean match. Stop on this ticket — this is a real gap (a capability skill needed, or a hint missing), not a judgement call to force.
3. **Claim.** Move the ticket to **In Progress** and clear the assignee — status is the lock, the same pattern `skill-exec` uses. A ticket In Progress under its owning agent's label is being worked and is never re-grabbed.
4. **Run.** Execute the resolved capability skill against the ticket's embedded acceptance criteria, on the owning agent's own connector surface (Figma for Pixel, Gmail/Shopify for Reach, Figma/FigJam for Sonar, Gmail/Linear for Scout, Linear+Calendar for Aide). Never borrow another agent's connector or another repo's code path. On the Figma surface, read `convention-figma` first and load the Figma rulebooks it names (`figma-use` before any write; `figma-generate-library` or `figma-generate-design` on top by task) — a `use_figma` call without them is the structurally-poor-build failure APP-614 was raised against, and the build is reviewed against `convention-figma`'s bar by `skill-design-parity` before it reaches Aled. Where the run's output lands in a **Linear document** rather than a ticket, a Figma file, or a store, follow *Editing a Linear document* below before writing anything — that surface has a data-loss shape the others do not.

   **A surface that refuses a write fails the run loudly — it never stalls it.** Where a connector call this ticket's work depends on is refused, is gated behind an approval no unattended run can answer, or simply does not return, the run stops at step 6 **within the first minute**: it does not retry, does not wait out the prompt, and does not carry on with the reads it can still make and hand off as though the write had landed. Record the refusal as observed — the tool, the call, and the message or the explicit absence of one — in the ticket comment, then take the Blocked path. The rule is written against **any** approval-gated or refusing surface, never against one connector, because the mechanism recurs across surfaces and has been re-fixed per surface each time: an unattended repo write hung for eleven hours on an interactive approval (APP-318, 2026-06-26) and the same shape then arrived on the Figma surface (APP-963). **The test is behavioural, because an agent cannot introspect its own gating.** A gated call does not announce itself — from the run's side an approval dialogue is invisible, so "it worked" and "a human happened to be there" are indistinguishable, and a run can detect the gate only by hanging on it. So the check is a **bounded** call with a timeout, and **no response inside the bound is a hard fail**, not a slow success. What this prevents is the signature: a ticket reading **In Progress** with no comment, no hand-off and no Blocked flag, found only when a person notices a stale claim — about 10½ hours on A1-344 and about 28 on A1-346, both on the same gap (APP-963). A loud stop costs one dispatch slot and names the gap; a silent one costs a day and leaves no trace that it happened at all.
   **Where the Objective names physical-world production but the criteria are coordination-shaped, run the criteria and name the human half.** Some fleet tickets describe an outcome no connector can produce — photographs taken, samples printed, mail posted — while their embedded acceptance criteria ask only for coordination: a brief, a shot list, "by whom and by when confirmed". Run against the criteria, as this step already says: produce the brief and a by-whom/by-when proposal, and state in the hand-off comment, as its own line, that the physical production is a human-execution half this run did not do, naming what it needs and who would carry it. Never claim, simulate or stand in for the physical output, and never refuse the ticket for the half no connector can reach. Where the criteria themselves require the physical output, the ticket is mis-routed rather than runnable — that pure case is `skill-fleet-dispatch`'s *re-route to `human`* (APP-991) — so take step 6 naming it. (Worked case: MBN-308, in-use and gift photography for the five ceramic SKUs on `P1-XEL`, whose criteria asked only that the shoot be confirmed by whom and by when; the run produced the brief and the proposal rather than refusing or claiming photography — APP-1551.)

   **Where the run's output is a recommendation, read the ticket's related tickets for a prior Owner ruling on the same question first — a capability run must never hand back advice the Owner has already overruled.** A fleet run that produces *material for a decision* — an analysis, a re-cut of a metric, a review verdict, a proposed threshold — is answering a question the Owner may have already answered on a sibling ticket, often days before the dispatch sweep began. Nothing upstream catches this: `skill-fleet-dispatch` tests eligibility, step 2 resolves a capability, and neither reads for a settled decision. So before composing the recommendation, walk the ticket's `relatedTo` set and its parent, and read any `DECISION:`-shaped or Owner-commented sibling for a ruling on the same question. Where one exists and the run's finding **agrees**, cite it and say so in the hand-off. Where the finding **diverges**, the ruling stands and the run does not restate the question: hand off with the recommendation written **against** the ruling as the premise, and name the divergence in its own line of the hand-off comment as a fact the operator may escalate — never as a recommendation the operator must now reconcile, and never silently resolved in either direction. This is the Cowork-surface twin of `skill-exec` step 3a's claim-side premise check, and it is one gate later than exec's for the same reason: a connector run's hazard is not a stale claim in the ticket body but a **fresh recommendation that contradicts a settled call**, which only exists once the run has something to recommend. It is deliberately **narrow** — the related set and the parent, read for a ruling, not a general licence to widen the run's reading (see *Guardrails*). (Worked case: MBN-320, "re-cut the BARA funnel KPIs against addressable traffic", dispatched to Sonar on 2026-10-03. Aled had ruled the underlying measurement question — raw versus addressable sessions, and at what thresholds — on MBN-321 on 2026-09-29, four days before the sweep. The run found it only because MBN-321 sat in MBN-320's `relatedTo` and was read incidentally; no step asked for it, the guardrail below pointed the other way, and the run agreed with the ruling by luck rather than by check — APP-2005.)

5. **Hand off — follow the invoked skill's own gate.** The agent's own skill decides when the work needs review: where the capability skill defines its own hand-off or reviewer (e.g. a design asset routed to Pixel's own review), follow that. Where it does not state one, default to the exec-shaped hand-off: tick every `## Acceptance criteria` / `## Definition of done` box the run genuinely satisfies, leave any unmet box unticked and named, move the ticket to **In Review**, set label `human`, assign Aled, and comment a short summary of what was done. **Where the ticket carries an** `operator-<venture>` **label, the default hand-off is to the operator's queue, not to the Owner's.** A fleet run whose output is *material for a decision* — a review, an assessment, a draft awaiting a call — hands off **In Review, executor cleared, the** `operator-<venture>` **label kept, unassigned**; the operator then vets the output and puts one distilled ask to the Owner on the ticket that owns the decision (`skill-operating-loop` step 5, *Assessment before escalation*; `convention-comms-owner`, *The sign-off brief*). Landing it on Aled instead does two things the loop is built to avoid: it puts the raw artefact in front of him, and where a separate sign-off ticket exists it splits one decision across two tickets — the shape `convention-comms-owner` *One decision, one surface* exists to prevent. Note that this hand-off keeps a label that is **not** an `agent`-group value, so it does not re-enter the dispatch set: the executor is cleared, which is what evicts it (`skill-fleet-dispatch`, *The* `agent` *label is the guard*). The `human` + Aled default above stands for everything else and for any ticket carrying no operator label. (Worked case: MBN-283, the BARA policy-page review — the hand-off had to be written into the ticket body by hand as an interim, because this runner's default would otherwise have overridden it — APP-1352.) **Where the ticket's `## Acceptance criteria` (or `## Definition of done`) arrived as plain bullets rather than `- [ ]` checkboxes, you may convert them to checkboxes verbatim** — add the box to each existing line and change nothing else, since a plain bullet cannot be ticked on the Done gate (`convention-ticket`). This is the one edit to the criteria the runner may make: convert the wording unchanged, never reword, add, split, or drop a criterion — the wording is the author's. **Never merges, closes, or marks Done** — sign-off is always Aled's, the same boundary `skill-exec` holds for code. Where the capability skill's output is a **written spec rather than a Figma file or a store change** — the branch `skill-product-design` names for a product with no Figma file — the spec lands in the ticket body under its own heading and the hand-off is this exec-shaped default; do not create a Figma file to have something to hand off (A1-373, APP-1062).
6. **Blocked (fail-safe).** On an unresolvable blocker (missing connector access, a credential, a requirement too ambiguous to run safely) — move to Blocked, priority Urgent, label `human`, assign Aled, comment plainly what blocked the run and what would clear it. Never leave a ticket In Progress with no forward path.

## Editing a Linear document

A capability run sometimes lands on a Linear document rather than a ticket body or a design file — a spec doc, a standard, a decision-log bucket. That surface carries a hazard the others do not, and it is worth stating in full because both halves of it fail quietly.

**A document write is full-replace.** Linear documents have no append and no patch (`convention-linear`, *Documents and the decision log*), so adding two lines means re-sending the entire body. Anything absent from the resend is deleted, and the save returns success either way.

**Any connector read can silently return nothing usable — the overflow-and-persist hazard is connector-general, not a Linear-document one.** When a tool result exceeds the limit, the call **still reports success** and the payload is persisted to a **host temp file** on a path such as `/var/folders/…`, which the sandbox cannot reach; only the session's own mounts are readable, so a shell `grep` of that path fails outright and the read looks *empty* rather than *truncated*. The recovery, on every connector, is to open the persisted payload with the **Read** tool, which reaches it where the shell does not — and, before that, to size the read so it does not overflow: a `fields` set on Linear lists, a bounded date range plus a `fullText` prefix filter on Calendar (`convention-hold-windows`), a narrower query on Shopify. It bites hardest on `get_document`: 50KB was enough, and the connector offers no field-selection or section-fetch mode for documents, so any large spec doc trips it — which is why the full-replace discipline below exists. (Worked cases: `spec.meirion-design-system`, APP-702; Calendar `list_events` over a 14-day hold-window range returning ~81,600 characters to a host path on Aide's A1-362 run, 2026-08-18 — APP-1016.) A related structural limit — the sandbox has no browser, so measuring a live built page needs the recipe in `convention-host-access` *Measuring a live page from the sandbox* — is documented there, not here (APP-1044).

Taken together these are a data-loss shape, not an inconvenience: a partial read followed by a write clobbers every section that was never read, and nothing in the response says so. So, for any document edit:

1. **Prefer not to rewrite the body at all.** A comment on the document (`save_comment` with `documentId`) or a new child document carries most additions without touching the existing text. Reach for the full-replace only when the change genuinely belongs inside the document.
2. **Read the whole document first, and prove you have it.** Check the retrieved body for a heading you know sits near the end and for a plausible length, rather than trusting that the call returned — an overflowed read hands back a path, not content.
3. **Never write a document you have not fully read.** If the read overflowed and the persisted payload could not be recovered with the Read tool, stop and take the Blocked path at step 6. A body assembled from a partial read is worse than an unfinished ticket.
4. **Compose the write from the read, not from memory.** Resend the retrieved body verbatim with the edit applied; never retype or paraphrase the parts you are not changing.
5. **Read the write back and compare.** Re-fetch after saving and confirm both that the edit landed and that the untouched sections are still there. A truncated or mis-quoted resend reverts content silently.
6. **Where the document belongs to a bucketed set, respect the bucket.** The decision log is split into weekly child documents for exactly this reason (`convention-decision-log`) — write the current week's child and update the index row, rather than rewriting a large aggregate.

Worked case: APP-702. A docs-only spec edit dispatched during the 2026-07-28 `task-fleet-dispatch` run (A1-247, Pixel / `skill-brand-system`) hit both halves at once — `get_document` on `spec.meirion-design-system` overflowed to an unreachable host path, and a two-line addition to `spec.meirion-work-architecture` required a full-body resend. The run recovered by reading the persisted payload with the Read tool and doing a full read-back after the save; the connector-side facts belong to `convention-linear`, the procedure here.

## Guardrails

- **Never writes a Linear document it has not fully read.** Document writes are full-replace, so a write on a partial read deletes everything unread — prefer a comment or a child document, and always read the write back (see *Editing a Linear document*).
- **Never touches `F0-RGE` work.** Code tickets are Forge's, run by `skill-exec` against a repo — this skill never claims one.
- **Never merges, closes, or marks Done.** Hand-off ends at In Review with `human` + Aled assigned; sign-off is his.

- **Never stalls silently on a surface that will not write.** A refused, gated or unanswered connector call is a Blocked stop with the refusal recorded, taken in the first minute — never a wait, never a retry loop, and never a run that continues on the reads it can still make and hands off as though the write had landed. The stall is the failure, not the refusal: the refusal is information, and an unattended run that hides it costs a day and leaves no trace (APP-318, APP-963).
- **Never guesses the capability skill.** An ambiguous match is a Blocked stop, not a best-effort run — running the wrong capability skill against a ticket is worse than surfacing the gap.
- **One ticket per agent label at a time; different agent labels may run in parallel.** Unlike Forge's one-agent-per-repo constraint, Pixel/Reach/Sonar/Scout/Aide sit on distinct connector surfaces with no shared write path, so tickets for *different* agent labels can be worked concurrently — but never two tickets under the *same* agent label at once (claim-by-state applies per label, mirroring exec's per-repo lock).
- Reads only the ticket, its embedded criteria, the owning agent's profile, and the resolved capability skill — no assumption carried in from another ticket. **The one exception is the prior-ruling read in step 4**, which walks the ticket's related set and parent for an Owner ruling on the question the run is about to answer. That is not an assumption carried in: this guardrail exists to stop a run importing another ticket's *context* as though it were this ticket's brief, and a settled Owner decision is neither context nor brief — it is a constraint the recommendation is written against. Read it, cite it, and carry nothing else across.

## Setup

- Not independently scheduled. Invoked by `skill-fleet-dispatch`, once per eligible ticket, inside an isolated per-ticket context (see `skill-fleet-dispatch` *Behaviour* — the parent sweep never holds every agent's skills loaded at once).
- Connectors: whichever the owning agent's profile declares (Figma, Gmail, Shopify, Calendar) plus Linear for ticket state.

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.
