Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.
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.

