Batch Work
Run Ready tasks through /workflow:work, one isolated subagent at a time, in dependency-then-priority order. For unattended execution of already-groomed work, not for making product or design decisions.
Main context is the orchestrator
The main context plans and dispatches; it never does task work itself. Keep it token-thin — a long batch must not fill the orchestrator's context with specs, diffs, and code.
- Does: cheap shell that changes or reports process/repo state (
git status, the validator,./workflow/status) — never a shell read of a file (cat,sed -n,grepa path it would then reason about); the rare file the orchestrator legitimately needs goes throughRead/Grepso it deduplicates instead of re-loading every turn. Computes the batch order and dependency plan from that output, dispatches subagents, records each subagent's condensed return, writes the report. - Never: opens task files, doc leaves, source, or diffs; reads a spec to work out an implementation. Every token-heavy step happens inside a subagent and comes back as a short summary. If you catch the orchestrator reading a
.mdtask body or a source file, that work belongs in a subagent.
Role decomposition already happens one level down, inside each worker's /work loop: it runs validation, spawns the independent acceptance-verifier subagent for the acceptance gate, and delegates bounded find/summarize/test-generation to the repo agents the contract names — all in the worker's isolated context, invisible to the orchestrator. Keep it there. Do not add orchestrator-level role agents (a separate validator, reviewer, etc.); that work belongs in the worker, and pulling it up here refills the context the isolation exists to protect.
Runtime adapter
Batch work requires context-isolated workers that share the current repository worktree. Select exactly one dispatch adapter by callable capability:
- If
spawn_agentandwait_agentare available, readreferences/codex-instructions.md. - Otherwise, if the
Agenttool is available, readreferences/claude-instructions.md. - Otherwise stop before execution and report that this runtime cannot provide isolated workers.
Never infer the runtime from .claude/ or .codex/ files because both can coexist. Load only the selected adapter. The adapter owns dispatch and waiting; this file owns every workflow decision and worker request. Dispatch one fresh worker per unit, wait for its final response, run the shared completion gates, then dispatch the next unit.
Workers inherit the host's current permissions. Never request a permission override, reuse a worker for another unit, create a separate worktree, or run a unit inline.
Unattended by default. batch-work never pauses for confirmation. The queue advances on each worker's structured return and stops only on failed/partial (Execution step 3) or an empty/blocked plan. Workers apply /work's fix-and-continue rule — resolve a routine blocker in place when the root cause is proven and the repair is local, reversible, and in scope (a lockfile-present dependency, a stale locator/snapshot/fixture, an unused port offset) — and return failed/partial/needs_regroom rather than asking when the cause is uncertain or multiple outcomes are valid.
Permissions must be pre-granted by the host. Workers inherit the host session's permissions and the skill never requests an override. An unattended batch assumes the session already allows what the work needs — file edits, the contract's build/test/verify commands, and any Chrome-MCP driving for UI acceptance. If those aren't granted up front, workers block on prompts and the batch stalls; grant them at launch, not per unit.
Usage
/workflow:batch-work # all Ready tasks, priority order
/workflow:batch-work auto # all runnable Ready tasks, auto-ordered by depends: (deps before dependents)
/workflow:batch-work 3 # first three Ready tasks
/workflow:batch-work 184,185 # named Ready tasks, in the given order
Tasks in workflow/ready/ are the batch. A Ready task with an unresolved question in its file is ineligible — report it and stop before execution instead of guessing.
Dependency handling
A depends: edge inside the batch is not a blocker — it's an order. Run the dependency first and the dependent is never actually stopped; working the whole set resolves the chain. /work enforces depends: at pickup (a dependency must be in done/), so the batch just has to feed tasks in the right order. Handle each unsatisfied depends: A on a selected task B by the first rung that applies:
- A already in
done/→ satisfied, nothing to do. - A is a batched or Ready task → run A fully, ordered before B, pulling A into the batch if it wasn't selected. A completes atomically through its own
/workloop, then B runs. Default and preferred path. - A is anything else (in
draft//in-progress/and not pullable whole, orblocked/on an externalgate:) → do not touch A. Drop B and report it. An unattended batch never partially implements or auto-slices an ungroomed dependency — that is how these runs corrupt state. B waits for a real/workor re-groom pass on A.
auto selection — build the batch from the depends: graph instead of a hand-picked list:
- Candidates = every task in
workflow/ready/. - Resolve each candidate's
depends:through the ladder above: satisfied/in-batch → keep; Ready-but-unselected dep → pull it in; anything else (draft/in-progress not pullable whole, or blocked/external) → drop the candidate and report it. - Order with Kahn's algorithm: repeatedly emit the runnable candidate whose deps are all done or already-emitted, breaking ties by lowest
priority:then lowest id. Adepends:cycle leaves tasks unemittable — drop them (the validator flags cycles before you get here). - List in the plan: the ordered batch and every dropped task with its reason.
autowith nothing runnable stops with that report instead of running an empty batch.
Everything downstream (isolated subagent per unit, clean-tree gate between units, stop-on-failure, report) is identical across modes; only auto computes the order and dependency plan for you.
Preconditions
- Resume check — glob
workflow/reports/batch-*.md(date-agnostic: an overnight batch can span midnight, so don't scope to today). If any carriesStatus: in-progress, this is a resumed batch — take the most recent one: adopt its frozen plan verbatim, skip selection and the rest of these preconditions, and go to Execution at itsnext=unit. Do not recompute selection — board state may have shifted mid-batch and the frozen order is the decision of record. Otherwise continue below to start a fresh batch. git status --porcelain— if it prints anything, stop and report the dirty paths. Do not stash, reset, clean, or commit the existing worktree.- Run the bundled validator:
python3 <plugin>/skills/framework-doctor/scripts/validate_workflow.py(resolve<plugin>via${CLAUDE_PLUGIN_ROOT}, or relative to this skill file). Stop if the framework is inconsistent. - Read
workflow/AGENTS.md(contract) and the Ready queue (./workflow/status) — the orchestrator's only reads. Collect the per-task model tiers with one cheap grep over the queue (grep -H '^model:' workflow/ready/*.md, empty output is normal) — a metadata scrape, not a task-file read, so it stays inside the orchestrator's budget. Selection by mode: no arg → priority order (lowestpriority:first, ties by id);auto→ the runnable, dependency-ordered set fromautoselection above; explicit IDs → the given order, all must be inready/. Apply Dependency handling to any selected task with an unmetdepends:, in every mode. - Resolve the report path:
workflow/reports/batch-<YYYY-MM-DD>.md, appending-2,-3, … when a completed or stopped report already occupies the path (never overwrite a finished run's record). Write the report file now with the full plan andStatus: in-progress | next=<first unit>(see Report), commit it (batch-work: <date> plan) so the tree is clean before the first unit, then print the same plan: the ordered units (task id + title), any dropped tasks and why, report path.
Execution
Work the plan's units in order, one isolated subagent at a time. The orchestrator only dispatches and records the condensed return — it does not read what the subagent read.
The orchestrator holds no batch state it can't rebuild from the report file. A long batch will be compacted; treat the on-disk report, not this context, as the source of truth. Every iteration re-derives its position instead of trusting memory:
- Read the report file → frozen plan, completed units, and
next=. - Reconcile with reality:
git logconfirms the last recorded unit's commit landed;git status --porcelainis empty. A mismatch (recorded done but no commit, or a dirty tree) means the previous unit did not finish cleanly — treat it as a stop, not a resume past it. - Dispatch
next=through the runtime adapter. Note the wall-clock time at dispatch. - On return, append the unit's condensed return to the report, advance the
Status:marker (next=<following unit>, orcompleteafter the last), and commit the report (per-unit handling below). The committed checkpoint is the durable record.
A compaction between any two of these steps is harmless: step 1 reloads the truth on the next turn. The report is committed at every checkpoint, so the tree is clean whenever a worker starts.
A dispatch that comes back without the structured return is a dispatch failure, not a worker failure. The worker request already demands the return, so the first suspect is the dispatch itself: a spawn mode with no return channel back to the orchestrator. Re-read the adapter, fix the dispatch, and run the unit once more — the adapter says whether its runtime resumes the same worker or re-dispatches, and never open a fresh worker where it doesn't. A second empty return fails the unit (step 3 below); record the raw dispatch result in the report so the cause stays visible. Never reconstruct a unit's outcome from its commits and record that as the return: a commit proves code landed, not that the acceptance gate passed.
Dispatch each unit on the model tier its plan entry records: a task that carried a model: line runs its worker on that tier, everything else inherits the host's model. The tier is frozen into the plan at precondition 4 because the grep can't be repeated later — the task file moves out of ready/ the moment its unit starts. A resumed batch reads the tier from the report like every other plan decision; never re-derive it, and never downgrade a unit the plan didn't mark.
Resolve the absolute path to the sibling ../work/SKILL.md, then send this self-contained request for the unit through the selected runtime adapter. It carries only what /work can't know on its own — which task, that the run is unattended, and the return schema. Don't re-list /work's steps here: a partial restatement reads as the authoritative loop and the worker follows it over the skill.
In <absolute repo path>, read and follow <absolute work SKILL.md path> for task
<id>, every step, in order. You run unattended in a batch: do not pause for
confirmation — apply /work's fix-and-continue rule for routine blockers and
return failed/partial/needs_regroom instead of asking.
Return only: final_status
(done | failed | partial | needs_regroom), acceptance_verdict, verification_depth
(which gates actually ran — typecheck/build/unit/e2e/Chrome-MCP — and one clause
on why any were skipped, e.g. non-UI task), key_decisions, files_changed excluding
task bookkeeping, sub_agents_spawned (count), tests_added (count), validation,
issues, needs_regroom (required when final_status is needs_regroom; else omit:
ownership_map, failed_assumption, why_not_one_loop, candidate_slices,
dependency_order).
Then, per unit:
- Success means
final_status: donewith an emptygit status --porcelain— the worker's atomic completion commit landed and left the tree clean. A dirty tree or any non-donestatus is not success → step 3. - Record the outcome: append only the returned summary to the report (never pull the subagent's full output into main context, never hold results only in context), plus the unit's wall-clock duration (dispatch→return, from step 3 above) and the proxies the worker returned (
verification_depth,sub_agents_spawned,tests_added) — these are the batch's only visibility into per-unit cost and rigor. Advance theStatus:marker to the next unit, and commit the report (batch-work: checkpoint <id>). This commit is the durable checkpoint and restores a clean tree for the next unit's dispatch. - On
failed,partial, orneeds_regroom, stop the queue immediately. Preserve the worktree exactly as the subagent left it — nevergit checkout,git reset,git clean, or automatic rollback. SetStatus: stopped | at=<id>, record which unit stopped it and why (forneeds_regroom, record the returned ownership map and candidate slices so the follow-up groom has them), mark every remaining unit not run, and commit the report checkpoint.
Sequential execution is required: later units may depend on earlier commits and all workers share one working directory.
Report
The report is created at plan time and updated in place as the batch runs — it is both the live checkpoint and the final artifact. It is committed incrementally: a batch-work: <date> plan commit at creation, then a batch-work: checkpoint <id> commit after each unit. That keeps the tree clean between units (so the clean-tree gate and each worker start hold) and makes every checkpoint survive a session restart. The Status: line is what the resume check reads: in-progress | next=<id> while running, stopped | at=<id> on failure, complete when the queue finishes. When the queue ends, its final state is already committed — just print its content and end with its path.
# Batch Run: YYYY-MM-DD
Status: in-progress | next=185
Tasks: 184, 185 (model: sonnet), 186 # tier only where the task pinned one
Dropped: 187 (dep 191 blocked/ on external gate) # omit if none
## Per task
### 184 — Title
- **Status**: done | failed | partial | not run
- **Duration**: NN min (dispatch→return) · **Verification depth**: gates that ran (or why skipped) · **Sub-agents**: N · **Tests added**: N
- **Acceptance verdict** / **Key decisions** / **Files changed** / **Validation** / **Issues** / **Needs re-groom** (when returned)
## Overall
| Metric | Value |
|--------|-------|
| Completed | N/total |
| Stopped (failed/partial/re-groom) | N |
| Not run | N |
| Total wall time | sum of per-unit durations |
### Files touched by multiple tasks
### Needs manual review
List every needs re-groom handoff with its ownership map, failed assumption, one-loop constraint, candidate slices, and dependency order. Write `None` only when every task completed cleanly.