Agent dispatch — workflow templates and eligibility precheck¶
Operational guide for briefing and dispatching Claude Code agents against fork-local backlog items. Implements ADR-0355; see Research-0091 for the design rationale (which Symphony shapes were adopted, which were deliberately dropped).
This page covers three things, in dispatch order:
- Pick a workflow template under
.claude/workflows/. - Run the eligibility precheck to confirm the work isn't already done or already in flight.
- Brief the agent by filling the template's
{{...}}slots.
1. Workflow templates¶
Every recurring agent task class has a template under .claude/workflows/:
| File | Use it when |
|---|---|
_template.md | Generic skeleton. Clone for any new task class. |
codeql-alert-sweep.md | Bulk-fix N CodeQL alerts in a single category. |
simd-port.md | Port or audit a SIMD path (AVX-512 widening, NEON sister, AVX2 audit). |
feature-extractor-port.md | Port a feature extractor to a GPU backend (CUDA / SYCL / HIP; the Vulkan backend was removed, ADR-0726). |
Each template starts with typed YAML front matter (Symphony §4.1.2 / §4.1.3 shape) that captures the dispatch contract:
name: codeql-alert-sweep
description: Bulk-fix CodeQL alerts in one category
agent_type: general-purpose
isolation: worktree
worktree_drift_check: true
required_deliverables: [changelog, rebase_note]
verification:
netflix_golden: true
cross_backend_places: 4
meson_test: true
forbidden:
- modify_netflix_golden_assertions
- lower_test_thresholds
master_status_check: true
backlog_id: T-RC1-MASTER-GREEN
The body below the front matter is the prompt template. {{...}} markers are placeholder slots filled at dispatch time (e.g. {{ALERT_LIST}}, {{BACKLOG_ID}}). The body is plain Markdown — no DSL, no execution engine — so it can be re-edited by hand without touching tooling.
Adding a new template¶
- Copy
_template.mdto.claude/workflows/<task-class>.md. - Edit the front matter — at minimum, set
nameto the file's basename, write a one-linedescription, and decide whichrequired_deliverablesandforbiddenfields apply. - Replace the body's
{{TASK_BODY}}and{{DELIVERABLES_LIST}}placeholders with task-specific guidance. - Land the new template in the same PR as the first dispatch that uses it — never ship a template without a worked example.
2. Eligibility precheck — agent-eligibility-precheck.py¶
Must run before every dispatch
The script lives at scripts/ci/agent-eligibility-precheck.py.
It runs three checks:
- BACKLOG row not closed. Parses
.workingdir/BACKLOG.mdviascripts/lib/backlog_tracker.py. If the row's status is DONE, CLOSED, REMOVED, BLOCKED or DEFERRED, exit 1. - No merged PR mentions this scope. Calls
gh pr list --search "<id> in:title,body" --state merged. Any hit means the work has likely already shipped: exit 1 and list the matching PRs. - No in-flight agent on the same scope. Scans
/tmp/claude-<uid>/*/tasks/*.output(the harness's per-task metadata) plus the open-PR head-branch list for any active task that mentions the scope. If found, exit 1.
The gate fails closed. A missing backlog row, unreadable harness task, failed GitHub query, missing gh, or unavailable authentication produces a blocking verdict. Use --task-tag for deliberately untracked work. The two --skip-* flags are explicit offline overrides; without one, an incomplete check never becomes an eligible dispatch.
The backlog is private
.workingdir/BACKLOG.md is private and gitignored, so a fresh clone has no backlog. Without one, --backlog-id blocks dispatch; use --task-tag for the work instead.
Backlog schema¶
The local backlog uses an explicit checklist schema. Every checkbox must carry a stable backtick ID immediately after it:
2. [ ] `T-RC1-MASTER-GREEN` **Master green and the queue drained**
7. [ ] `T-RC2-BENCH-TUNE` **[BLOCKED]** **Bench and tune**
1. [x] `T-RC1-RELEASE-PIPELINE` **Release pipeline correct and idle**
[x] means DONE; unchecked rows default to OPEN and may start their title with **[BLOCKED]**, **[DEFERRED]** or **[IN_FLIGHT]**. Wrapped, indented lines remain part of the item.
These are schema errors that block dispatch:
- a checkbox without a stable ID, or a duplicate ID;
- an unknown explicit marker, or a marker that contradicts the checkbox;
- an existing backlog with no tracked items.
The parser never invents an ID from list order, title text or a GitHub issue number. Retired pipe-table rows remain readable during migration. See ADR-1303.
Reading the verdict¶
Verdicts go to stderr in GitHub Actions ::error format so a wrapping CI script can parse them. A closed item fails:
$ python3 scripts/ci/agent-eligibility-precheck.py --backlog-id T-RC1-RELEASE-PIPELINE --skip-gh-search --skip-active-scan
agent-eligibility-precheck: scope=T-RC1-RELEASE-PIPELINE
::error title=agent-eligibility: T-RC1-RELEASE-PIPELINE is DONE::BACKLOG.md row already closed (PRs: none recorded). Title: Release pipeline correct and idle
agent-eligibility-precheck: VERDICT=FAIL — do not dispatch.
$ echo "exit=$?"
exit=1
An open item passes:
$ python3 scripts/ci/agent-eligibility-precheck.py --backlog-id T-RC1-MASTER-GREEN --skip-gh-search --skip-active-scan
agent-eligibility-precheck: scope=T-RC1-MASTER-GREEN
backlog: T-RC1-MASTER-GREEN status=OPEN priority=None — OK
agent-eligibility-precheck: VERDICT=PASS — dispatch eligible.
$ echo "exit=$?"
exit=0
Flags¶
| Flag | Meaning |
|---|---|
--backlog-id ID | Run all three checks against the given BACKLOG row. |
--task-tag TAG | Free-form scope tag for runs without a backlog row (e.g. codeql-cpp-overflow). Skips checks 1 and 2. |
--skip-gh-search | Skip check 2 (merged-PR search). Useful offline. |
--skip-active-scan | Skip check 3 (in-flight scan). |
--repo OWNER/REPO | GitHub repository (default: VMAFx/vmafx). |
--harness-tasks-glob GLOB | Override the harness task-files glob (default: /tmp/claude-<uid>/*/tasks/*.output). |
--backlog-path PATH | Override the BACKLOG.md path (default: autodetect from the worktree's main repo). |
Wiring it in¶
The Claude Code harness does not currently expose a pre-Agent dispatch hook in settings.json. Until it does, calling the precheck is manual — it sits at the top of every workflow template's body. When the harness gains the hook, register the precheck via .claude/settings.json and remove the manual call.
3. Tracker abstraction — scripts/lib/backlog_tracker.py¶
The precheck and any future state-audit script consume BACKLOG.md through a typed module rather than re-grepping the file:
from lib.backlog_tracker import BacklogTracker, GitHubTracker, BacklogItem
bk = BacklogTracker() # autodetects .workingdir/BACKLOG.md
bk.list_open() # -> list[BacklogItem]
bk.list_in_flight() # -> list[BacklogItem]
bk.list_closed() # -> list[BacklogItem]
bk.get("T-RC1-MASTER-GREEN") # -> BacklogItem | None
gh = GitHubTracker(repo="VMAFx/vmafx")
gh.merged_prs_since(some_datetime) # -> list[dict]
gh.search_prs("T-RC1-MASTER-GREEN in:title,body") # -> list[dict]
gh.open_agent_branches() # -> list[str]
BacklogItem exposes:
@dataclass
class BacklogItem:
id: str # "T-RC1-MASTER-GREEN"
title: str # markdown-stripped first cell
status: str # OPEN | IN_FLIGHT | DONE | CLOSED | REMOVED | BLOCKED | DEFERRED
priority: int | None # tier number (T0=0, T7=7, TA-* = None)
pr_refs: list[int] # PR numbers parsed from "PR #N" markers
raw_row: str # original markdown row, for diagnostics
The module is read-only — never writes to BACKLOG.md. Edits remain a manual editorial task per the global "Read AND update local state files" rule. If .workingdir/ ever migrates off Markdown (JSON, SQLite, Linear), this module is the only file that needs to change.
See also¶
- ADR-0355 — decision rationale.
- Research-0091 — what we adopted from Symphony and what we dropped.
- ADR-0108 — the six-deliverable rule encoded in
required_deliverables. scripts/ci/AGENTS.md— invariants for files inscripts/ci/.scripts/lib/AGENTS.md— invariants for the tracker abstraction.