Execution agents were spawning blind — no input files, no knowledge of what predecessor tasks accomplished. This adds three capabilities: 1. summary column on tasks table — completeTask() reads the finishing agent's result.message and stores it on the task record 2. dispatchNext() gathers full initiative context (initiative, phase, sibling tasks, pages) and passes it as inputContext so agents get .cw/input/task.md, initiative.md, phase.md, and context directories 3. context/tasks/*.md files now include the summary field in frontmatter so dependent agents can see what prior agents accomplished
5.1 KiB
5.1 KiB
Dispatch & Events
apps/server/dispatch/ — Task and phase dispatch queues. apps/server/events/ — Typed event bus.
Event Bus
apps/server/events/ — Typed pub/sub system for inter-module communication.
Architecture
- Port:
EventBusinterface withemit(event)andon(type, handler) - Adapter:
TypedEventBususing Node.jsEventEmitter - All events implement
BaseEvent { type, timestamp, payload }
Event Types (52)
| Category | Events | Count |
|---|---|---|
| Agent | agent:spawned, agent:stopped, agent:crashed, agent:resumed, agent:account_switched, agent:deleted, agent:waiting, agent:output |
8 |
| Task | task:queued, task:dispatched, task:completed, task:blocked, task:pending_approval |
5 |
| Phase | phase:queued, phase:started, phase:completed, phase:blocked |
4 |
| Merge | merge:queued, merge:started, merge:completed, merge:conflicted |
4 |
| Page | page:created, page:updated, page:deleted |
3 |
| Process | process:spawned, process:stopped, process:crashed |
3 |
| Server | server:started, server:stopped |
2 |
| Worktree | worktree:created, worktree:removed, worktree:merged, worktree:conflict |
4 |
| Account | account:credentials_refreshed, account:credentials_expired, account:credentials_validated |
3 |
| Preview | preview:building, preview:ready, preview:stopped, preview:failed |
4 |
| Conversation | conversation:created, conversation:answered |
2 |
| Log | log:entry |
1 |
Key Event Payloads
AgentSpawnedEvent { agentId, name, taskId, worktreeId, provider }
AgentStoppedEvent { agentId, name, taskId, reason }
// reason: 'user_requested'|'task_complete'|'error'|'waiting_for_input'|
// 'context_complete'|'plan_complete'|'detail_complete'|'refine_complete'
AgentWaitingEvent { agentId, name, taskId, sessionId, questions[] }
AgentOutputEvent { agentId, stream, data }
TaskCompletedEvent { taskId, agentId, success, message }
TaskQueuedEvent { taskId, priority, dependsOn[] }
PhaseStartedEvent { phaseId, initiativeId }
MergeConflictedEvent { taskId, agentId, worktreeId, targetBranch, conflictingFiles[] }
AccountCredentialsRefreshedEvent { accountId, expiresAt, previousExpiresAt? }
Task Dispatch
apps/server/dispatch/ — In-memory queue with dependency-ordered dispatch.
Architecture
- Port:
DispatchManagerinterface - Adapter:
DefaultDispatchManager
How Task Dispatch Works
- Queue —
queue(taskId)fetches task dependencies, adds to internal Map - Dispatch —
dispatchNext()finds highest-priority task with all deps complete - Context gathering — Before spawn,
dispatchNext()gathers initiative context (initiative, phase, tasks, pages) and passes asinputContextto the agent. Agents receive.cw/input/task.md,initiative.md,phase.md,context/tasks/,context/phases/, andpages/. - Priority order: high > medium > low, then oldest first (FIFO within priority)
- Checkpoint skip — Tasks with type starting with
checkpoint:skip auto-dispatch - Planning skip — Planning-category tasks (research, discuss, plan, detail, refine) skip auto-dispatch — they use the architect flow
- Summary propagation —
completeTask()reads the completing agent'sresult.messageand stores it on the task'ssummarycolumn. Dependent tasks see this summary incontext/tasks/<id>.mdfrontmatter. - Approval check —
completeTask()checksrequiresApproval(task-level, then initiative-level) - Approval flow — If approval required: status →
pending_approval, emittask:pending_approval
DispatchManager Methods
| Method | Purpose |
|---|---|
queue(taskId) |
Add task to dispatch queue |
dispatchNext() |
Find and dispatch next ready task |
getNextDispatchable() |
Get next task without dispatching |
completeTask(taskId, agentId?) |
Complete with approval check |
approveTask(taskId) |
Approve pending task |
blockTask(taskId, reason) |
Block task with reason |
getQueueState() |
Return queued, ready, blocked tasks |
Phase Dispatch
DefaultPhaseDispatchManager — Same pattern for phases:
- Queue —
queuePhase(phaseId)validates phase is approved, gets dependencies - Dispatch —
dispatchNextPhase()finds phase with all deps complete - Auto-queue tasks — When phase starts, pending execution tasks are queued (planning-category tasks excluded)
- Events —
phase:queued,phase:started,phase:completed,phase:blocked
PhaseDispatchManager Methods
| Method | Purpose |
|---|---|
queuePhase(phaseId) |
Queue approved phase |
dispatchNextPhase() |
Start next ready phase, auto-queue its tasks |
getNextDispatchablePhase() |
Get next phase without dispatching |
completePhase(phaseId) |
Mark phase complete |
blockPhase(phaseId, reason) |
Block phase |
getPhaseQueueState() |
Return queued, ready, blocked phases |