Renderizado do repositório de origem, preservando títulos, exemplos, código, tabelas, links e imagens.
Dispatch
Bridge between the product context (control plane) and the feature lifecycle (execution plane). Syncs workspace state, scores stories, picks what to build next, assembles context, and routes into /aep-design or /aep-launch.
Where this fits:
/aep-envision → /aep-map → /aep-model (UI-facing) → /aep-scaffold
→ [ /aep-dispatch → /aep-design → /aep-launch → /aep-build → /aep-wrap ]
▲ you are here
→ /aep-reflect → loopSession: Main, interactive Input: Product definition from product/index.yaml (split mode) or product-context.yaml (v1 mode); operational state from product-context.yaml Output: OpenSpec change with pre-assembled context, story status updated, handoff to /aep-design or /aep-launch
For autonomous orchestration: Use/aep-autopilotinstead. Autopilot runs the full dispatch-launch-monitor-review-wrap-dispatch cycle as a tick-based state machine via/loop. Dispatch remains a single-pass interactive tool. For hands-free batch under Claude Code: dispatch a wave "with workflow" to build the whole wave as one dynamic workflow (Step 5 → Dynamic Workflow).
Before Starting
Probe which mode the product context is in:
ls product/index.yaml 2>/dev/null && echo "SPLIT MODE" || echo "V1 MODE"
cat product-context.yamlMode semantics are canonical in references/file-resolution.md. Dispatch reads the product definition from product/index.yaml (split) or product-context.yaml (v1); it reads/writes operational state (stories, topology, architecture, cost, changelog) always in product-context.yaml.
If product-context.yaml doesn't exist, run /aep-envision then /aep-map first. If the stories section is empty, run /aep-map to decompose the product.
The Dispatch Protocol
Every dispatch run follows the same 7-step protocol. Each step is idempotent — running /aep-dispatch twice with no state changes produces the same result. Story states (pending / ready / blocked / inprogress / inreview / completed / failed / deferred) follow the product-context schema.
① SYNC signals → bring YAML up to date with workspace reality
② CASCADE states → compute all pending→ready, pending→blocked transitions
③ SCORE stories → rank the ready queue by dispatch_score
④ PRESENT queue → show user the scored dispatch queue
⑤ DISPATCH → lock stories (status→in_progress), create OpenSpec changes
⑥ MONITOR → agents work, main session watches signals
⑦ COMPLETE → /aep-wrap updates YAML, atomic cascade, re-invoke dispatchStep 1: Signal Sync
Sync workspace signals into the YAML before computing anything — stale YAML shows in_progress for stories already done and keeps downstream stories pending when they are actually ready.
For each story with status in_progress or in_review, read .feature-workspaces/<story.assigned_to>/.dev-workflow/signals/status.json and copy the record into the story: signal.story_status → story.status, plus completed_at / pr_url / cost_usd on completed, pr_url on in_review, and failure_log appended to story.failure_logs on failed. A story whose signal file is absent stays as it is. The signal shape is canonical in /aep-launch → references/signals-spec.md.
Postcondition: every in_progress/in_review story's YAML status matches its signal file.
Step 2: Cascade State Transitions
After syncing all signals, compute all state transitions in one pass:
For each story with status: pending:
If any story in dependencies[] has status: failed:
Transition → blocked
Elif any story in dependencies[] has status: deferred:
Transition → blocked (dependency deferred)
Elif all stories in dependencies[] have status: completed:
Transition → ready
For each story with status: blocked:
If the blocking dependency is now completed:
Transition → pending (will be re-evaluated in this same pass)This is the dependency gate: a story only becomes ready when every dependency is completed, so no story with unmet dependencies is ever dispatchable.
Recovery transitions (user-initiated, handle if requested):
failed → pending— user resets after fixing the specdeferred → pending— user un-defers a story
Validate the YAML after all updates:
npx js-yaml product-context.yaml > /dev/null && echo "YAML OK"If this fails, fix the YAML before proceeding (see references/yaml-guardrails.md for the common fixes).
Commit the synced + cascaded state to YAML before computing scores, and increment dispatch_epoch.
Postcondition: npx js-yaml exits 0 and the cascaded state is committed.
Step 3: Score Stories
Determine Active Layer
For each layer (0, 1, 2, ...):
If any story in this layer has status not in [completed, deferred]:
This is the active layer. Stop.Layer gate check: If active layer > 0, verify layer_gates[active_layer - 1].status == passed. If not, block and suggest running the layer gate. A gate at scripted_passed (Tier-1 machinery green, but the journey dogfood / coverage half isn't done) does not unblock the next layer — report it as "machinery green, dogfood pending" so the human knows the remaining half. Only /aep-wrap's two-phase flip advances a gate to passed.
Filter Ready Queue
Dispatch only ready stories in the active layer that have no file-level conflict with in-progress work:
For each ready story:
For each in_progress story:
If files_affected intersection is non-empty:
Mark as conflicted — cannot dispatch until the in_progress story completesCompute Scores
For each remaining ready story, compute readiness_score and dispatch_score per references/scoring.md, and write both into the story YAML:
- `readiness_score` (0–1) = spec completeness; drives Step 7 routing.
- `dispatch_score` = (business value + unblock potential + critical-path urgency + reuse leverage) / (complexity cost + ambiguity penalty + interface risk); ranks the queue.
Grouped changes (compile_mode: grouped_change sharing a change_group) score and dispatch as one unit — see references/scoring.md → Grouped Change Dispatch.
Postcondition: every ready story has readiness_score and dispatch_score written to its YAML.
Step 4: Present Dispatch Queue
Show the sorted queue with context. Highlight the top story and explain why (highest score = critical path + high value + unblocks the most). One row per story, plus the conflicted / blocked / in-progress sections:
Dispatch Queue (Layer 0 — 4 ready, 2 in_progress, WIP 3/5)
1. ★ PROJ-003 "Setup auth middleware" score: 14.5
[high] S | Module: auth | Wave 1 | Critical path | Shared enabler
Unblocks: PROJ-005, PROJ-007, PROJ-008
→ Readiness: 0.9 — skip to /aep-launch
Conflicted (waiting): • PROJ-006 — files overlap with in_progress PROJ-002
Blocked (deps unmet): • PROJ-008 — waiting on PROJ-003 (auth middleware)
In progress: • PROJ-001 (tab: feat-api-scaffold) — Phase 4, 60%Step 5: Dispatch
Dispatch Modes
- Interactive (default) — user picks stories one at a time. Best for early layers or learning the system.
- Wave Batch (`--batch wave`) — dispatch all ready stories in the current wave at once (up to the WIP limit), creating N workspaces via
/aep-launch. - Dynamic Workflow (`--batch wave` + "…with workflow") — when the host is Claude Code with the Workflow tool and the user asked for a workflow, route the batch through workflow mode (one dynamic workflow, one agent per locked story) instead of N
/aep-launchworkers. Readreferences/workflow-mode.md: it carries the machine-assembled STEP-0 brief requirement (the stale-base hazard) and the mode announcement dispatch must make when it bypasses/aep-launch.
WIP Limits
max_wip = topology.routing.concurrency_limit (default: 5)
current_wip = count of stories with status: in_progress
available_slots = max_wip - current_wipDispatch at most available_slots stories — this cap holds in every mode, including Dynamic Workflow. The integration/merge bottleneck (usually human PR review, not agent speed) is what available_slots protects: by Little's Law, Lead Time = WIP / Throughput, so dispatching beyond capacity creates traffic jams, not speed.
Gate: dispatch while available_slots > 0; at zero the WIP limit is the answer.
The Dispatch Lock
For each selected story, dispatch atomically:
1. Re-read story.status from YAML (not from cache)
2. If status != ready → SKIP (already dispatched by another run)
3. Write to YAML:
status: in_progress
assigned_to: <workspace-name>
openspec_change: <story-id>
started_at: <ISO 8601 now>
dispatched_at_epoch: <current dispatch_epoch>
4. Commit YAML immediately (this IS the lock)
5. THEN create OpenSpec change and workspaceThe commit happens before the workspace is created, which is what makes a second run safe: run 1 writes in_progress and commits, run 2 reads in_progress and skips.
Mechanical ordering invariant [lock-before-workspace]. A project scaling dispatch puts this ordering behind a typed gate — a verb that refuses[story-not-ready]/[dependencies-completed]by name. Seeaep-autopilotreferences/deterministic-orchestration.md.
Step 6: Create OpenSpec Change with Context Package
For each dispatched story, create the OpenSpec change openspec/changes/<story-id>/ — proposal.md, design.md, specs/<module>.md, tasks.md, and a .context/ package (stable-prefix.md, dependencies.md, retrieval.md). Always create the OpenSpec change, even for a well-specified story — the .context/ directory is what the agent reads.
Assemble the handoff package per references/context-assembly.md, pruned to the role's token budget from topology. The package carries the story's provisional verification tier (derivation in context-assembly.md); the binding re-derivation is build-owned — /aep-build runs it at Phase 5 entry from the actual diff, and the tier only ratchets up.
Dispatch-blocking gates in assembly: calibrated stories (calibration_type set, or .5 alignment layers — concept canonical in /aep-map references/alignment-layers.md) require calibration/<type>.yaml to exist — else route to /aep-calibrate. UI-facing stories (object_model_refs, calibration_type in {visual-design, ux-flow}, or a ui-kind module) require an approved Object Map that covers the story — else route to /aep-model. Both gates are detailed in references/context-assembly.md; if either fails, do not dispatch.
Postcondition: openspec/changes/<story-id>/.context/ exists for each dispatched story.
Commit and Push Before Handoff
Push every dispatch artifact (YAML updates, OpenSpec changes, changelog) to the remote before handing off to /aep-launch: a dispatch commit that stays local is lost when workspace PRs merge to the integration branch and you rebase.
Append to the changelog section:
- date: <today>
type: dispatch
author: human
summary: "Dispatched PROJ-003 (auth middleware), PROJ-004 (user model) — Layer 0, Wave 1"
sections_changed: [stories]Then commit and push per /aep-git-ref "Control-Plane Commits": git add product-context.yaml openspec/changes/, commit (feat: dispatch <ids> — Layer N Wave M), and push to $BASE (resolve $BASE per /aep-git-ref "Resolving $BASE").
Postcondition: the dispatch commit is present on origin/$BASE — on a push failure (remote conflict), resolve it before launching workspaces.
Step 7: Hand Off
Launch mode is normally resolved at `/aep-launch`, not here. For the default path dispatch stays executor-agnostic — it hands a well-specified change to/aep-launch, which detects the host and selects a mode viaaep-executor. The one exception is the _Dynamic Workflow_ opt-in (Step 5): that path runs the workflow mode from dispatch, bypassing/aep-launch, so dispatch itself owns mode selection and the announcement for that case.
Route on the readiness_score band computed in Step 3. Band definitions are canonical in references/scoring.md § Routing Thresholds; this section owns only the interactive actions:
- dispatch-ready → skip to
/aep-launch - borderline → present the
/aep-launchversus/aep-designdecision to the user - under-specified → route to
/aep-design
Full-auto / auto-design routing (medium/low readiness)
Routing of a borderline or under-specified story depends on two topology.routing flags — the full_auto master switch (default false) and the finer-grained auto_design (default false). full_auto sits above auto_design: full_auto: true implies auto_design: true.
- `full_auto: true` OR `auto_design: true` → resolve the story with a
non-interactive gen/eval design resolver (a design agent that refines the spec without a human, then routes to /aep-launch) instead of escalating to interactive /aep-design. No strategic pause.
- `full_auto: false` AND `auto_design: false` (default) → route to interactive
/aep-design for human design refinement before launch. The strategic "what to build" gate stays with the human.
Example single-story handoff:
Story PROJ-003 dispatched (score: 14.5, critical path, shared enabler).
OpenSpec change: openspec/changes/PROJ-003/ Context: .../.context/
Recommendation: Readiness 0.9 — well-specified → /aep-launch
/aep-launch ← start building immediately
/aep-design ← refine the spec firstBatch Handoff
For batch dispatch, create all workspaces via /aep-launch:
Batch dispatched: PROJ-003 (score 23.0), PROJ-004 (score 12.0)
/aep-launch PROJ-003 → tab: auth-middleware
/aep-launch PROJ-004 → tab: user-modelEdge Cases
- No stories ready: All pending stories have unmet dependencies. Check if any
in_progressstories are stuck (high attemptcount, old startedat). Suggest checking workspace progress or running/aep-reflect. - All stories completed in active layer: Trigger the layer gate test. If passed, advance to the next layer and re-run dispatch.
- All stories completed in all layers: Product is done. Suggest
/aep-reflectfor final review. - Layer gate failed: create fix stories from the failure, add them to the current layer, re-dispatch — the layer stays open until its gate passes.
- WIP limit reached: No available slots. Show what's in progress and suggest waiting or reviewing PRs to unblock slots.

