Protocol: Warren Dogfood Dispatch Pipeline
You run a themed slice of the seeds backlog end-to-end through the live
warren instance: audit → ordered dispatch list → sequential runs →
PR shepherding → tracker closure. Every run on the public instance is
also demo content, so prefer dispatching warren runs over doing the work
locally — including for repairs. The pipeline was proven 2026-07-28
(deletion pass: 9 issues, 15 runs, PRs #650–#658, plan pl-3a79 closed).
0. Inputs (ask if not given)
- Focus theme + priority band — e.g. "P0/P1 deletion-focused",
"security leaks", "docs drift". This drives the audit lens.
- Agent + model/provider — ALWAYS stop and ask the operator
(AskUserQuestion) which model + provider to use for all tasks in the
session, even when every other input was given. Recommended default:
provider
openrouter, model moonshotai/kimi-k3, on the pi
harness (the only harness that can reach OpenRouter; it is also the
project defaultRole). Offer the anthropic tiers
(src/registry/builtins/model-tiers.ts) as alternatives — for hard
or large refactors use claude-opus-5 (Opus 5; pass it as an
explicit modelOverride — do NOT rely on the opus tier's default,
which still pins the older claude-opus-4-8). Dispatch with explicit
providerOverride + modelOverride matching the answer.
- Dispatch mode — sequential one-at-a-time (default, what the
operator chose last time) vs parallel. Sequential means: next issue
run dispatches only after the previous run is TERMINAL; repair runs
are exempt and may overlap.
- Blocked issues — skip them, or dispatch their blockers first
(last time: blockers first).
Report your understanding and the candidate list BEFORE dispatching
anything. Dispatching costs real money and opens real PRs.
1. Phase 1 — Audit & prioritize (subagent)
Spawn a subagent that invokes the seeds-issue-audit skill, with an
extra deliverable bolted on: a DELETION-DISPATCH-LIST-style section
for the focus theme — every still-open, in-theme issue with LIVE
blocker resolution (a closed blocker doesn't count; re-check each edge)
and a serial dispatch order, blockers first, sequenced so each run can
build on the previous merge. Require the exact line format
<order>. <id> — <title> — <UNBLOCKED | blocked-by: ids> so you can
dispatch straight off it.
Babysit the subagent: if it stops claiming to "wait for a worker
notification" while it has no live children, resume it and tell it to
finish the batch inline.
2. Phase 2 — Dispatch plumbing
- Instance: hostname lives only in the gitignored
deploy/k8s/overlays/gke-live/kustomization.yaml (Ingress host
patch). Token: kubectl get secret warren-secrets -n warren -o jsonpath='{.data.warren-api-token}' | base64 -d (key is
warren-api-token, not WARREN_API_TOKEN).
- Project id:
GET /projects, match the gitUrl.
- Dispatch:
POST /runs with {agent, project, prompt, modelOverride, seedId}. Always pass seedId — it links the run to
the tracker. BOTH POST /runs and GET /runs/:id WRAP the run as
{run: {...}} — since warren-7d84 the detail GETs wrap to match the
POST and the plan-runs family, so no route needs per-envelope
knowledge. Parse .run.state, never .state. A bare parse reads
all-null, which a polling loop silently reads as "not terminal yet"
and spins to its iteration cap instead of failing loudly.
Terminal states are succeeded|failed|cancelled.
- Repair dispatch onto an existing PR branch: same POST with
ref = targetBranch = the PR's head branch. Warren pushes back to
that branch in place; the agent must NOT open a new PR or branch.
Prompt template per issue run (adapt, keep all five elements):
- "Work seeds issue . First run
sd show <id> --json from the
repo root" — the issue body is the spec; your summary is a digest.
- Context of what already merged on main that this run builds on.
- Scope guidance (what to delete/change; repo-specific gotchas like
bun run check:bundle-size --update for UI work).
- "Quality gates are terminal:
bun run check:all must be green
before you commit and report done."
- "Close the issue with
sd close <id> --reason ..., then commit
everything."
Polling: background loop, ~55s interval, ≤10 iterations per loop
(re-launch on expiry). Pipe curl straight into the JSON parser — never
round-trip response bodies through a shell variable + echo (zsh echo
expands \n inside the JSON and corrupts it). On terminal, capture
state, failureReason, prUrl, costUsd.
3. Phase 3 — Sequential loop with merge gates
- Next independent issue dispatches when the previous run is
terminal.
- A dependent issue dispatches only when every blocker's PR is
MERGED on main (re-verify with
gh pr view <n> --json state
immediately before dispatch) — a terminal run is not enough; the
next clone needs the code.
- Interleave: put independent issues between a blocker and its
dependents so PRs have time to merge while other work runs.
4. PR shepherding (expect to do this for EVERY PR)
Themed slices touch overlapping files (CLAUDE.md, budgets,
.seeds/issues.jsonl), so almost every PR that outlives another merge
goes stale. Diagnose with
gh pr view <n> --json mergeable,mergeStateStatus,autoMergeRequest,statusCheckRollup:
| State |
Fix |
BEHIND, auto-merge armed |
gh pr update-branch <n> (re-run after every upstream merge — serial auto-merge is a chase) |
DIRTY (conflicts) |
FIRST verify the conflict is real: git merge-tree --write-tree origin/main <head-ref>. Clean (tree hash only) has TWO possible meanings — check whether the PR touches .seeds/*.jsonl before concluding: (a) PR does NOT touch .seeds → GitHub's cached mergeability is stale, push a merge commit to force a recompute, do NOT spend a repair run (warren-fb6e). (b) PR touches .seeds → the conflict is REAL for GitHub: the local merge=seeds-jsonl driver resolved an id-keyed row rewrite that GitHub's server-side merge genuinely cannot (it runs no custom drivers), so merge-tree-clean is NOT proof of a stale cache (warren-b34b). The seeds-merge-autoheal workflow pushes the driver-aware merge automatically; if it has not (or failed), do it manually: checkout the PR branch, git merge origin/main --no-edit (driver registered by bun install), verify bun run check:seeds-integrity + no duplicate ids, push. Genuinely conflicted even with the driver → dispatch a repair run on the PR branch (below) |
CLEAN/green but auto-merge off |
Article IX gate declined to arm it; gh pr merge <n> --auto --squash — only with operator authorization to unblock merges (ask once, then treat as standing for the session) |
CI FAILURE |
read the failing gate log; bundle-size overshoot after a merge → repair run that runs the bounded check:bundle-size --update and commits ONLY the budget diff |
Repair-run prompt essentials: name the PR and its issue; name what
merged on main since the branch was cut; git fetch origin && git merge origin/main; preserve BOTH sides (deletions on main win over
incidental touches; this branch's feature always stays);
.seeds/issues.jsonl needs explicit attention even when git reports NO
conflict — .gitattributes routes it through the merge=seeds-jsonl
driver (a 3-way id-keyed merge registered by bun install), which is
correct ONLY in a clone where the driver is registered; verify
bun run check:seeds-integrity after any merge, and remember closing
one issue rewrites blockedBy on every plan sibling (warren-9c90 /
warren-db8c / warren-b34b).
Always run jq -r .id .seeds/issues.jsonl | sort | uniq -d after the
merge. Resolve by taking closed where either side closed and
INTERSECTING blockedBy — each side only ever removes a blocker, so
taking either row alone resurrects one the other side satisfied. Then
confirm with bun run check:seeds-integrity; budgets take main's numbers unless this branch
changed the UI; stay on the branch, no new PR, gate green, commit, no
push.
5. Known failure modes (all observed live)
- Stale host clone on ref-dispatch (warren-b94b): a repair run's
workspace can miss a recent push to the same branch → reap push
rejected non-fast-forward →
finalize_failed, work discarded.
Recovery: re-dispatch immediately (dispatch re-fetches), or fix
locally in a throwaway git worktree and push.
- noChanges seed auto-close (warren-0395): a run that commits
nothing can still close its
seedId at reap. After any run that
declined or no-op'd, verify the seed's status ON ORIGIN
(git show origin/main:.seeds/issues.jsonl) and reopen if needed.
- Lost closes in conflict-repair merges: every
.seeds conflict
resolution risks dropping a close from one side. At wrap-up, verify
EVERY dispatched seed's final status and re-close with evidence.
- Sandboxed pre-commit hook: warren's git-fixture tests escape
their temp dirs under Claude Code's sandbox — they can commit fixture
debris into the working tree and flip
core.bare=true on the repo.
Never let the check:all hook run sandboxed: commit data/docs-only
changes with --no-verify (CI arbitrates), and run suspect
git-fixture tests with the sandbox disabled. Repair: git config core.bare false.
- Agent declines the issue: an agent refusing work because the
issue contradicts a shipped contract is SIGNAL, not failure. Read its
reasoning from the run events, surface the design decision to the
operator (AskUserQuestion with a recommended re-scope), then
re-dispatch with the approved scope in the prompt — and have the run
sd update the issue description first so the tracker matches.
6. Wrap-up (all steps, in order)
- Verify every dispatched seed is closed on origin with an
evidence-citing reason; restore lost closes. Record
sd plan outcome <pl> --result ... if a plan completed.
- File follow-up issues for every product bug the session surfaced
(
sd create) — dogfood yield is half the point.
ml record the failures/patterns worth keeping (with
--resolution on failures).
sd sync && ml sync; if the hook blocks sandboxed, commit the
.seeds/.mulch diff with --no-verify.
- Rebase onto origin (main moved under you all session) and push;
git status must end "up to date with origin". Do NOT trust push
output alone — remote: N of N required status checks are expected
appears on a SUCCESSFUL push and is easy to misread as a rejection
(warren-53c0). After any push that touched .seeds/, verify the
remote tip is clean:
git fetch origin && bun run check:seeds-integrity against
git show origin/main:.seeds/issues.jsonl (or the branch you
pushed), and confirm jq -r .id <(git show origin/<ref>:.seeds/issues.jsonl) | sort | uniq -d
is empty. The pre-push hook refuses a corrupt tip locally, but a
misread of push output has still left a bad file on origin once.
- Final report: table of issue → PR → merge state → cost, total spend,
borderline items left for the operator, and the filed follow-ups.
1---2name: warren-dogfood-pipeline3description: Full prioritize → dispatch → shepherd → track pipeline against the live warren instance. Audits the seeds backlog around a focus theme, dispatches the surviving issues to warren agents one at a time, babysits the resulting PRs to merge (update-branch, conflict-repair runs, auto-merge), and closes the loop in the tracker. Activate for prompts like "run the dogfood pipeline", "dispatch the P1 <theme> issues to warren", "audit and dispatch", "work the backlog through warren agents".4---56# Protocol: Warren Dogfood Dispatch Pipeline78You run a themed slice of the seeds backlog end-to-end through the live9warren instance: audit → ordered dispatch list → sequential runs →10PR shepherding → tracker closure. Every run on the public instance is11also demo content, so prefer dispatching warren runs over doing the work12locally — including for repairs. The pipeline was proven 2026-07-2813(deletion pass: 9 issues, 15 runs, PRs #650–#658, plan pl-3a79 closed).1415## 0. Inputs (ask if not given)1617- **Focus theme + priority band** — e.g. "P0/P1 deletion-focused",18 "security leaks", "docs drift". This drives the audit lens.19- **Agent + model/provider** — ALWAYS stop and ask the operator20 (AskUserQuestion) which model + provider to use for all tasks in the21 session, even when every other input was given. Recommended default:22 provider `openrouter`, model `moonshotai/kimi-k3`, on the `pi`23 harness (the only harness that can reach OpenRouter; it is also the24 project `defaultRole`). Offer the anthropic tiers25 (`src/registry/builtins/model-tiers.ts`) as alternatives — for hard26 or large refactors use `claude-opus-5` (Opus 5; pass it as an27 explicit `modelOverride` — do NOT rely on the opus tier's default,28 which still pins the older claude-opus-4-8). Dispatch with explicit29 `providerOverride` + `modelOverride` matching the answer.30- **Dispatch mode** — sequential one-at-a-time (default, what the31 operator chose last time) vs parallel. Sequential means: next issue32 run dispatches only after the previous run is TERMINAL; repair runs33 are exempt and may overlap.34- **Blocked issues** — skip them, or dispatch their blockers first35 (last time: blockers first).3637Report your understanding and the candidate list BEFORE dispatching38anything. Dispatching costs real money and opens real PRs.3940## 1. Phase 1 — Audit & prioritize (subagent)4142Spawn a subagent that invokes the `seeds-issue-audit` skill, with an43extra deliverable bolted on: a **DELETION-DISPATCH-LIST-style section**44for the focus theme — every still-open, in-theme issue with LIVE45blocker resolution (a closed blocker doesn't count; re-check each edge)46and a serial dispatch order, blockers first, sequenced so each run can47build on the previous merge. Require the exact line format48`<order>. <id> — <title> — <UNBLOCKED | blocked-by: ids>` so you can49dispatch straight off it.5051Babysit the subagent: if it stops claiming to "wait for a worker52notification" while it has no live children, resume it and tell it to53finish the batch inline.5455## 2. Phase 2 — Dispatch plumbing5657- **Instance**: hostname lives only in the gitignored58 `deploy/k8s/overlays/gke-live/kustomization.yaml` (Ingress host59 patch). Token: `kubectl get secret warren-secrets -n warren -o60 jsonpath='{.data.warren-api-token}' | base64 -d` (key is61 `warren-api-token`, not `WARREN_API_TOKEN`).62- **Project id**: `GET /projects`, match the gitUrl.63- **Dispatch**: `POST /runs` with `{agent, project, prompt,64 modelOverride, seedId}`. Always pass `seedId` — it links the run to65 the tracker. BOTH `POST /runs` and `GET /runs/:id` WRAP the run as66 `{run: {...}}` — since warren-7d84 the detail GETs wrap to match the67 POST and the plan-runs family, so no route needs per-envelope68 knowledge. Parse `.run.state`, never `.state`. A bare parse reads69 all-null, which a polling loop silently reads as "not terminal yet"70 and spins to its iteration cap instead of failing loudly.71 Terminal states are `succeeded|failed|cancelled`.72- **Repair dispatch onto an existing PR branch**: same POST with73 `ref` = `targetBranch` = the PR's head branch. Warren pushes back to74 that branch in place; the agent must NOT open a new PR or branch.7576**Prompt template per issue run** (adapt, keep all five elements):771. "Work seeds issue <id>. First run `sd show <id> --json` from the78 repo root" — the issue body is the spec; your summary is a digest.792. Context of what already merged on main that this run builds on.803. Scope guidance (what to delete/change; repo-specific gotchas like81 `bun run check:bundle-size --update` for UI work).824. "Quality gates are terminal: `bun run check:all` must be green83 before you commit and report done."845. "Close the issue with `sd close <id> --reason ...`, then commit85 everything."8687**Polling**: background loop, ~55s interval, ≤10 iterations per loop88(re-launch on expiry). Pipe curl straight into the JSON parser — never89round-trip response bodies through a shell variable + `echo` (zsh echo90expands `\n` inside the JSON and corrupts it). On terminal, capture91`state`, `failureReason`, `prUrl`, `costUsd`.9293## 3. Phase 3 — Sequential loop with merge gates9495- Next **independent** issue dispatches when the previous run is96 terminal.97- A **dependent** issue dispatches only when every blocker's PR is98 **MERGED on main** (re-verify with `gh pr view <n> --json state`99 immediately before dispatch) — a terminal run is not enough; the100 next clone needs the code.101- Interleave: put independent issues between a blocker and its102 dependents so PRs have time to merge while other work runs.103104## 4. PR shepherding (expect to do this for EVERY PR)105106Themed slices touch overlapping files (CLAUDE.md, budgets,107`.seeds/issues.jsonl`), so almost every PR that outlives another merge108goes stale. Diagnose with109`gh pr view <n> --json mergeable,mergeStateStatus,autoMergeRequest,statusCheckRollup`:110111| State | Fix |112| --- | --- |113| `BEHIND`, auto-merge armed | `gh pr update-branch <n>` (re-run after every upstream merge — serial auto-merge is a chase) |114| `DIRTY` (conflicts) | FIRST verify the conflict is real: `git merge-tree --write-tree origin/main <head-ref>`. Clean (tree hash only) has TWO possible meanings — check whether the PR touches `.seeds/*.jsonl` before concluding: (a) PR does NOT touch `.seeds` → GitHub's cached mergeability is stale, push a merge commit to force a recompute, do NOT spend a repair run (warren-fb6e). (b) PR touches `.seeds` → the conflict is REAL for GitHub: the local `merge=seeds-jsonl` driver resolved an id-keyed row rewrite that GitHub's server-side merge genuinely cannot (it runs no custom drivers), so merge-tree-clean is NOT proof of a stale cache (warren-b34b). The `seeds-merge-autoheal` workflow pushes the driver-aware merge automatically; if it has not (or failed), do it manually: checkout the PR branch, `git merge origin/main --no-edit` (driver registered by `bun install`), verify `bun run check:seeds-integrity` + no duplicate ids, push. Genuinely conflicted even with the driver → dispatch a **repair run** on the PR branch (below) |115| `CLEAN`/green but auto-merge off | Article IX gate declined to arm it; `gh pr merge <n> --auto --squash` — only with operator authorization to unblock merges (ask once, then treat as standing for the session) |116| CI `FAILURE` | read the failing gate log; bundle-size overshoot after a merge → repair run that runs the bounded `check:bundle-size --update` and commits ONLY the budget diff |117118**Repair-run prompt essentials**: name the PR and its issue; name what119merged on main since the branch was cut; `git fetch origin && git merge120origin/main`; preserve BOTH sides (deletions on main win over121incidental touches; this branch's feature always stays);122`.seeds/issues.jsonl` needs explicit attention even when git reports NO123conflict — `.gitattributes` routes it through the `merge=seeds-jsonl`124driver (a 3-way id-keyed merge registered by `bun install`), which is125correct ONLY in a clone where the driver is registered; verify126`bun run check:seeds-integrity` after any merge, and remember closing127one issue rewrites `blockedBy` on every plan sibling (warren-9c90 /128warren-db8c / warren-b34b).129Always run `jq -r .id .seeds/issues.jsonl | sort | uniq -d` after the130merge. Resolve by taking `closed` where either side closed and131INTERSECTING `blockedBy` — each side only ever removes a blocker, so132taking either row alone resurrects one the other side satisfied. Then133confirm with `bun run check:seeds-integrity`; budgets take main's numbers unless this branch134changed the UI; stay on the branch, no new PR, gate green, commit, no135push.136137## 5. Known failure modes (all observed live)138139- **Stale host clone on ref-dispatch** (warren-b94b): a repair run's140 workspace can miss a recent push to the same branch → reap push141 rejected non-fast-forward → `finalize_failed`, work discarded.142 Recovery: re-dispatch immediately (dispatch re-fetches), or fix143 locally in a throwaway `git worktree` and push.144- **noChanges seed auto-close** (warren-0395): a run that commits145 nothing can still close its `seedId` at reap. After any run that146 declined or no-op'd, verify the seed's status ON ORIGIN147 (`git show origin/main:.seeds/issues.jsonl`) and reopen if needed.148- **Lost closes in conflict-repair merges**: every `.seeds` conflict149 resolution risks dropping a close from one side. At wrap-up, verify150 EVERY dispatched seed's final status and re-close with evidence.151- **Sandboxed pre-commit hook**: warren's git-fixture tests escape152 their temp dirs under Claude Code's sandbox — they can commit fixture153 debris into the working tree and flip `core.bare=true` on the repo.154 Never let the `check:all` hook run sandboxed: commit data/docs-only155 changes with `--no-verify` (CI arbitrates), and run suspect156 git-fixture tests with the sandbox disabled. Repair: `git config157 core.bare false`.158- **Agent declines the issue**: an agent refusing work because the159 issue contradicts a shipped contract is SIGNAL, not failure. Read its160 reasoning from the run events, surface the design decision to the161 operator (AskUserQuestion with a recommended re-scope), then162 re-dispatch with the approved scope in the prompt — and have the run163 `sd update` the issue description first so the tracker matches.164165## 6. Wrap-up (all steps, in order)1661671. Verify every dispatched seed is closed on origin with an168 evidence-citing reason; restore lost closes. Record169 `sd plan outcome <pl> --result ...` if a plan completed.1702. File follow-up issues for every product bug the session surfaced171 (`sd create`) — dogfood yield is half the point.1723. `ml record` the failures/patterns worth keeping (with173 `--resolution` on failures).1744. `sd sync && ml sync`; if the hook blocks sandboxed, commit the175 `.seeds`/`.mulch` diff with `--no-verify`.1765. Rebase onto origin (main moved under you all session) and push;177 `git status` must end "up to date with origin". Do NOT trust push178 output alone — `remote: N of N required status checks are expected`179 appears on a SUCCESSFUL push and is easy to misread as a rejection180 (warren-53c0). After any push that touched `.seeds/`, verify the181 remote tip is clean:182 `git fetch origin && bun run check:seeds-integrity` against183 `git show origin/main:.seeds/issues.jsonl` (or the branch you184 pushed), and confirm `jq -r .id <(git show origin/<ref>:.seeds/issues.jsonl) | sort | uniq -d`185 is empty. The pre-push hook refuses a corrupt tip locally, but a186 misread of push output has still left a bad file on origin once.1876. Final report: table of issue → PR → merge state → cost, total spend,188 borderline items left for the operator, and the filed follow-ups.