memorysaver/agentic-engineering-patterns

aep-dispatch

- Picks one ready story and hands it to /aep-design or /aep-launch.

소스 보기
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

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 → loop

Session: 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-autopilot instead. 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:

bash
ls product/index.yaml 2>/dev/null && echo "SPLIT MODE" || echo "V1 MODE"
cat product-context.yaml

Mode 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 dispatch

Step 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_statusstory.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-launchreferences/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 spec
  • deferred → pending — user un-defers a story

Validate the YAML after all updates:

bash
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 completes

Compute 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-launch workers. Read references/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_wip

Dispatch 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 workspace

The 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. See aep-autopilot references/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:

yaml
- 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 via aep-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-launch versus /aep-design decision 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 first

Batch 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-model

Edge Cases

  • No stories ready: All pending stories have unmet dependencies. Check if any in_progress stories 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-reflect for 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.
같은 저장소의 Skills

더 많은 Skills

모든 Skills