docs(05): create phase 5 plan - task dispatch

Phase 05: Task Dispatch
- 5 plans in 3 waves
- 3 parallel (Wave 1), 1 sequential (Wave 2), 1 sequential (Wave 3)
- Ready for execution

Requirements covered:
- AGENT-06: Message queue for agent questions
- TASK-01: Task status visibility
- TASK-04: Dependency-ordered dispatch
- TASK-05: Work queue for agents
This commit is contained in:
Lukas May
2026-01-30 20:22:17 +01:00
parent 20d867d980
commit 7b155ecc28
5 changed files with 751 additions and 0 deletions

View File

@@ -0,0 +1,116 @@
---
phase: 05-task-dispatch
plan: 01
type: execute
wave: 1
depends_on: []
files_modified: [src/db/schema.ts, src/db/repositories/message-repository.ts, src/db/repositories/drizzle/message.ts, src/db/repositories/drizzle/message.test.ts, src/db/repositories/index.ts, src/db/repositories/drizzle/index.ts]
autonomous: true
---
<objective>
Add message schema and repository for persisting agent questions.
Purpose: AGENT-06 requires agents to surface questions to users. When an agent pauses on AskUserQuestion, the question needs to persist in the database so users can query pending messages and respond later.
Output: messages table, MessageRepository port/adapter with full CRUD and tests.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@src/db/schema.ts
@src/db/repositories/task-repository.ts
@src/db/repositories/drizzle/task.ts
@src/db/repositories/drizzle/agent.ts
@src/events/types.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Add messages table to schema</name>
<files>src/db/schema.ts</files>
<action>
Add messages table after agents table. Schema:
- id: text primary key
- agentId: text NOT NULL foreign key to agents.id with SET NULL on delete (message persists even if agent deleted)
- type: text enum ['question', 'info', 'error'] NOT NULL default 'question'
- content: text NOT NULL (the question or message text)
- status: text enum ['pending', 'read', 'responded'] NOT NULL default 'pending'
- response: text nullable (user's response when status is 'responded')
- createdAt: integer timestamp NOT NULL
- updatedAt: integer timestamp NOT NULL
Add relations: message belongs to agent (nullable after delete).
Export Message and NewMessage types.
Follow existing patterns: use sqliteTable, relations, InferSelectModel/InferInsertModel.
</action>
<verify>npm run build passes, no TypeScript errors</verify>
<done>messages table in schema with types exported</done>
</task>
<task type="auto">
<name>Task 2: Create MessageRepository port and adapter</name>
<files>src/db/repositories/message-repository.ts, src/db/repositories/drizzle/message.ts, src/db/repositories/drizzle/message.test.ts, src/db/repositories/index.ts, src/db/repositories/drizzle/index.ts</files>
<action>
Create MessageRepository following TaskRepository pattern exactly:
1. Port interface (message-repository.ts):
- CreateMessageData type (omit id, createdAt, updatedAt)
- UpdateMessageData type (partial of create data)
- MessageRepository interface with:
- create(data: CreateMessageData): Promise<Message>
- findById(id: string): Promise<Message | null>
- findByAgentId(agentId: string): Promise<Message[]>
- findPending(): Promise<Message[]> (status = 'pending')
- update(id: string, data: UpdateMessageData): Promise<Message>
- delete(id: string): Promise<void>
2. Drizzle adapter (drizzle/message.ts):
- DrizzleMessageRepository class implementing port
- Use nanoid for id generation
- Fetch after insert to get schema defaults
- Order by createdAt DESC for lists
3. Tests (drizzle/message.test.ts):
- Use createTestDatabase helper
- Test create, findById, findByAgentId, findPending, update, delete
- Test pending filter returns only pending messages
- Test response field update when marking as responded
4. Re-export from index files (same pattern as AgentRepository)
</action>
<verify>npm test -- src/db/repositories/drizzle/message.test.ts passes</verify>
<done>MessageRepository port/adapter with passing tests</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] npm run build succeeds
- [ ] npm test passes all tests including new message tests
- [ ] messages table accessible via DrizzleMessageRepository
- [ ] findPending returns only pending messages
</verification>
<success_criteria>
- messages table added to schema with proper types
- MessageRepository port interface defined
- DrizzleMessageRepository adapter implemented
- All tests pass
- Exports wired up in index files
</success_criteria>
<output>
After completion, create `.planning/phases/05-task-dispatch/05-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,122 @@
---
phase: 05-task-dispatch
plan: 02
type: execute
wave: 1
depends_on: []
files_modified: [src/trpc/router.ts, src/trpc/context.ts, src/cli/index.ts]
autonomous: true
---
<objective>
Add task visibility via tRPC and CLI.
Purpose: TASK-01 requires users to see status of all tasks at a glance. Expose existing TaskRepository through tRPC endpoints and CLI commands for task listing and status viewing.
Output: tRPC task procedures, CLI `cw task list` and `cw task get` commands.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@src/trpc/router.ts
@src/trpc/context.ts
@src/cli/index.ts
@src/db/repositories/task-repository.ts
@src/db/schema.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Add task tRPC procedures</name>
<files>src/trpc/router.ts, src/trpc/context.ts</files>
<action>
1. Add TaskRepository to TRPCContext (optional, same pattern as agentManager):
- Add taskRepository?: TaskRepository to context type
- Add requireTaskRepository helper function
2. Add task procedures to appRouter:
listTasks: publicProcedure
.input(z.object({ planId: z.string().optional() }))
.query(...)
- If planId provided, return tasks for that plan (ordered by order field)
- If no planId, return all tasks (need to add findAll to TaskRepository first - OR use a simple "list all" query)
- Actually, for simplicity: require planId. Users query by plan.
- Return array of Task objects
getTask: publicProcedure
.input(z.object({ id: z.string() }))
.query(...)
- Return single task or throw NOT_FOUND
updateTaskStatus: publicProcedure
.input(z.object({
id: z.string(),
status: z.enum(['pending', 'in_progress', 'completed', 'blocked'])
}))
.mutation(...)
- Update task status
- Return updated task
Follow existing patterns from agent procedures. Use Zod schemas for input validation.
</action>
<verify>npm run build passes, npm test -- src/trpc/router.test.ts passes</verify>
<done>Task tRPC procedures available: listTasks, getTask, updateTaskStatus</done>
</task>
<task type="auto">
<name>Task 2: Add task CLI commands</name>
<files>src/cli/index.ts</files>
<action>
Add task subcommands to the existing 'task' command group:
1. cw task list --plan <planId>
- Call listTasks tRPC procedure
- Display tasks as table: name, status, type, priority
- Show count of pending/in_progress/completed
2. cw task get <taskId>
- Call getTask tRPC procedure
- Display full task details: id, name, description, type, priority, status, order, timestamps
3. cw task status <taskId> <status>
- Call updateTaskStatus tRPC procedure
- Validate status is one of: pending, in_progress, completed, blocked
- Confirm status update
Replace the placeholder "not implemented" action with real commands.
Follow existing CLI patterns (error handling, client creation, output formatting).
</action>
<verify>cw task list --plan test-id shows error (expected - no server), cw task --help shows commands</verify>
<done>CLI task commands functional: list, get, status</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] npm run build succeeds
- [ ] npm test passes
- [ ] cw task --help shows list, get, status commands
- [ ] Task procedures added to router
</verification>
<success_criteria>
- tRPC task procedures (listTasks, getTask, updateTaskStatus) added
- TaskRepository optional in context (same pattern as AgentManager)
- CLI task commands: list, get, status
- All existing tests still pass
</success_criteria>
<output>
After completion, create `.planning/phases/05-task-dispatch/05-02-SUMMARY.md`
</output>

View File

@@ -0,0 +1,165 @@
---
phase: 05-task-dispatch
plan: 03
type: execute
wave: 1
depends_on: []
files_modified: [src/dispatch/types.ts, src/dispatch/index.ts, src/events/types.ts, src/events/index.ts]
autonomous: true
---
<objective>
Define DispatchManager port interface and dispatch events.
Purpose: TASK-04 and TASK-05 require dependency-ordered task dispatch and work queue. Define the port interface that the adapter will implement, plus domain events for dispatch lifecycle.
Output: DispatchManager port interface, dispatch domain events, exported from dispatch module.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@src/agent/types.ts
@src/git/types.ts
@src/events/types.ts
@src/db/schema.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Create DispatchManager port interface</name>
<files>src/dispatch/types.ts, src/dispatch/index.ts</files>
<action>
Create src/dispatch/ directory with types.ts and index.ts.
In types.ts, define:
1. QueuedTask type:
- taskId: string
- priority: 'low' | 'medium' | 'high'
- queuedAt: Date
- dependsOn: string[] (task IDs that must complete first)
2. DispatchResult type:
- success: boolean
- taskId: string
- agentId?: string (assigned agent if dispatched)
- reason?: string (why dispatch failed if not success)
3. DispatchManager port interface:
/**
* DispatchManager Port Interface
*
* Manages task dispatch queue with dependency ordering.
*
* Covers requirements:
* - TASK-04: Dependency-ordered dispatch
* - TASK-05: Work queue for available agents
*/
interface DispatchManager {
/**
* Queue a task for dispatch.
* Task will be dispatched when all dependencies complete.
*/
queue(taskId: string): Promise<void>;
/**
* Get next dispatchable task.
* Returns task with all dependencies complete, highest priority first.
* Returns null if no tasks ready.
*/
getNextDispatchable(): Promise<QueuedTask | null>;
/**
* Dispatch next available task to an agent.
* Finds available agent, assigns task, spawns agent.
* Returns dispatch result.
*/
dispatchNext(): Promise<DispatchResult>;
/**
* Mark a task as complete.
* Triggers re-evaluation of dependent tasks.
*/
completeTask(taskId: string): Promise<void>;
/**
* Mark a task as blocked.
* Task will not be dispatched until unblocked.
*/
blockTask(taskId: string, reason: string): Promise<void>;
/**
* Get current queue state.
* Returns all queued tasks with their dispatch readiness.
*/
getQueueState(): Promise<{
queued: QueuedTask[];
ready: QueuedTask[];
blocked: Array<{ taskId: string; reason: string }>;
}>;
}
Export all types from index.ts.
</action>
<verify>npm run build passes, types importable from src/dispatch</verify>
<done>DispatchManager port interface defined and exported</done>
</task>
<task type="auto">
<name>Task 2: Add dispatch domain events</name>
<files>src/events/types.ts, src/events/index.ts</files>
<action>
Add dispatch events to events/types.ts, following existing patterns:
1. TaskQueuedEvent:
type: 'task:queued'
payload: { taskId: string; priority: string; dependsOn: string[] }
2. TaskDispatchedEvent:
type: 'task:dispatched'
payload: { taskId: string; agentId: string; agentName: string }
3. TaskCompletedEvent:
type: 'task:completed'
payload: { taskId: string; agentId: string; success: boolean; message: string }
4. TaskBlockedEvent:
type: 'task:blocked'
payload: { taskId: string; reason: string; blockedBy?: string[] }
Add all four to DomainEventMap union type.
No changes needed to index.ts (types auto-exported).
</action>
<verify>npm run build passes, events compile without errors</verify>
<done>Dispatch events added to DomainEventMap</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] npm run build succeeds
- [ ] npm test passes
- [ ] DispatchManager interface importable from src/dispatch
- [ ] Dispatch events in DomainEventMap
</verification>
<success_criteria>
- src/dispatch/types.ts with DispatchManager interface
- QueuedTask, DispatchResult types defined
- Four dispatch events added to events/types.ts
- All types properly exported
</success_criteria>
<output>
After completion, create `.planning/phases/05-task-dispatch/05-03-SUMMARY.md`
</output>

View File

@@ -0,0 +1,174 @@
---
phase: 05-task-dispatch
plan: 04
type: execute
wave: 2
depends_on: ["05-01", "05-03"]
files_modified: [src/dispatch/manager.ts, src/dispatch/manager.test.ts, src/dispatch/index.ts]
autonomous: true
---
<objective>
Implement DispatchManager adapter with dependency checking and queue management.
Purpose: TASK-04 requires dependency-ordered dispatch, TASK-05 requires work queue. Implement the DispatchManager adapter that checks task dependencies before dispatch and manages the work queue.
Output: DispatchManager adapter with dependency algorithm, queue management, and tests.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/05-task-dispatch/05-01-SUMMARY.md
@.planning/phases/05-task-dispatch/05-03-SUMMARY.md
@src/dispatch/types.ts
@src/db/schema.ts
@src/db/repositories/task-repository.ts
@src/agent/types.ts
@src/events/types.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Implement dependency checking algorithm</name>
<files>src/dispatch/manager.ts</files>
<action>
Create DefaultDispatchManager class implementing DispatchManager interface.
Constructor dependencies (inject via constructor):
- taskRepository: TaskRepository
- messageRepository: MessageRepository (from 05-01)
- agentManager: AgentManager
- eventBus: EventBus
Implement core dependency algorithm:
1. queue(taskId):
- Fetch task and its dependencies from task_dependencies table
- Add to internal queue (use Map<string, QueuedTask>)
- Emit TaskQueuedEvent
2. getNextDispatchable():
- Get all queued tasks
- For each, check if ALL dependencies have status='completed'
- Filter to only ready tasks (all deps complete)
- Sort by priority (high > medium > low), then by queuedAt (oldest first)
- Return first or null
3. completeTask(taskId):
- Update task status to 'completed' via taskRepository
- Remove from queue
- Emit TaskCompletedEvent
- Note: dependent tasks automatically become ready on next getNextDispatchable call
4. blockTask(taskId, reason):
- Update task status to 'blocked' via taskRepository
- Emit TaskBlockedEvent
Use simple in-memory queue (Map). Production could use persistent queue but in-memory is fine for v1.
</action>
<verify>npm run build passes</verify>
<done>Dependency checking algorithm implemented</done>
</task>
<task type="auto">
<name>Task 2: Implement dispatchNext and queue state</name>
<files>src/dispatch/manager.ts</files>
<action>
Complete DispatchManager implementation:
1. dispatchNext():
- Call getNextDispatchable() to get next task
- If null, return { success: false, taskId: '', reason: 'No dispatchable tasks' }
- Find available agent (status='idle') via agentManager.list()
- If no available agent, return { success: false, taskId, reason: 'No available agents' }
- Get task details from taskRepository
- Spawn agent with task: agentManager.spawn({ name: generateAgentName(), taskId, prompt: task.description || task.name })
- Update task status to 'in_progress'
- Emit TaskDispatchedEvent
- Return { success: true, taskId, agentId }
2. getQueueState():
- Return { queued: [...allQueuedTasks], ready: [...readyTasks], blocked: [...blockedTasks] }
- Uses internal queue state
Helper: generateAgentName():
- Generate unique name for agent (e.g., 'agent-${nanoid(6)}' or use Vancouver neighborhood names: 'gastown', 'yaletown', 'kitsilano', etc.)
- For v1: simple 'agent-${taskId.slice(0,6)}' is fine
Handle edge cases:
- Task not found: throw error
- Circular dependencies: not checked in v1 (assume valid DAG)
</action>
<verify>npm run build passes</verify>
<done>dispatchNext and getQueueState implemented</done>
</task>
<task type="auto">
<name>Task 3: Add tests and wire up exports</name>
<files>src/dispatch/manager.test.ts, src/dispatch/index.ts</files>
<action>
Create manager.test.ts with tests:
1. Setup:
- Create in-memory database with createTestDatabase helper
- Create mock/real repositories and EventBus
- Create test tasks with dependencies
2. Test cases:
- queue adds task to queue and emits event
- getNextDispatchable returns null when queue empty
- getNextDispatchable returns task when dependencies complete
- getNextDispatchable returns null when dependencies incomplete
- getNextDispatchable respects priority ordering
- completeTask updates status and emits event
- blockTask updates status and emits event
- dispatchNext returns failure when no tasks ready
- dispatchNext returns failure when no agents available
- getQueueState returns correct state
3. Test dependency scenario:
- Task A (no deps)
- Task B (depends on A)
- Task C (depends on A)
- Queue all three
- Only A should be dispatchable
- Complete A
- B and C should become dispatchable
Update index.ts to export DefaultDispatchManager class.
</action>
<verify>npm test -- src/dispatch/manager.test.ts passes</verify>
<done>Tests pass, DispatchManager exported</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] npm run build succeeds
- [ ] npm test passes all tests
- [ ] Dependency ordering works correctly
- [ ] Queue state accurately reflects task readiness
</verification>
<success_criteria>
- DefaultDispatchManager implements DispatchManager interface
- Dependency checking prevents premature dispatch
- Priority ordering respected
- All dispatch events emitted correctly
- Tests cover core scenarios
</success_criteria>
<output>
After completion, create `.planning/phases/05-task-dispatch/05-04-SUMMARY.md`
</output>

View File

@@ -0,0 +1,174 @@
---
phase: 05-task-dispatch
plan: 05
type: execute
wave: 3
depends_on: ["05-02", "05-04"]
files_modified: [src/trpc/router.ts, src/trpc/context.ts, src/cli/index.ts]
autonomous: true
---
<objective>
Add message and dispatch tRPC procedures and CLI commands.
Purpose: Complete AGENT-06 (message visibility) and TASK-04/TASK-05 (dispatch control) with user-facing interfaces. Users can view pending agent questions and control task dispatch.
Output: tRPC message and dispatch procedures, CLI commands for messages and dispatch.
</objective>
<execution_context>
@~/.claude/get-shit-done/workflows/execute-plan.md
@~/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/05-task-dispatch/05-01-SUMMARY.md
@.planning/phases/05-task-dispatch/05-02-SUMMARY.md
@.planning/phases/05-task-dispatch/05-04-SUMMARY.md
@src/trpc/router.ts
@src/trpc/context.ts
@src/cli/index.ts
@src/db/repositories/message-repository.ts
@src/dispatch/types.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Add message and dispatch tRPC procedures</name>
<files>src/trpc/router.ts, src/trpc/context.ts</files>
<action>
1. Add to TRPCContext:
- messageRepository?: MessageRepository
- dispatchManager?: DispatchManager
- Add requireMessageRepository and requireDispatchManager helpers
2. Add message procedures to appRouter:
listMessages: publicProcedure
.input(z.object({ agentId: z.string().optional(), status: z.enum(['pending', 'read', 'responded']).optional() }))
.query(...)
- If agentId, filter by agent
- If status, filter by status
- Return array of Message objects
getMessage: publicProcedure
.input(z.object({ id: z.string() }))
.query(...)
- Return single message or throw NOT_FOUND
respondToMessage: publicProcedure
.input(z.object({ id: z.string(), response: z.string() }))
.mutation(...)
- Update message with response and status='responded'
- Return updated message
3. Add dispatch procedures:
queueTask: publicProcedure
.input(z.object({ taskId: z.string() }))
.mutation(...)
- Call dispatchManager.queue(taskId)
- Return { success: true }
dispatchNext: publicProcedure
.mutation(...)
- Call dispatchManager.dispatchNext()
- Return DispatchResult
getQueueState: publicProcedure
.query(...)
- Call dispatchManager.getQueueState()
- Return queue state object
completeTask: publicProcedure
.input(z.object({ taskId: z.string() }))
.mutation(...)
- Call dispatchManager.completeTask(taskId)
- Return { success: true }
</action>
<verify>npm run build passes</verify>
<done>Message and dispatch tRPC procedures added</done>
</task>
<task type="auto">
<name>Task 2: Add message and dispatch CLI commands</name>
<files>src/cli/index.ts</files>
<action>
1. Add message command group:
const messageCommand = program
.command('message')
.description('View agent messages and questions');
cw message list [--agent <agentId>] [--status <status>]
- Call listMessages tRPC
- Display as table: id (short), agent, type, content (truncated), status, createdAt
- Show "(pending)" count prominently
cw message read <messageId>
- Call getMessage tRPC
- Display full message content
- If pending, show prompt to respond
cw message respond <messageId> <response>
- Call respondToMessage tRPC
- Confirm response recorded
- Suggest resuming agent if appropriate
2. Add dispatch command group:
const dispatchCommand = program
.command('dispatch')
.description('Control task dispatch queue');
cw dispatch queue <taskId>
- Call queueTask tRPC
- Confirm task queued
cw dispatch next
- Call dispatchNext tRPC
- Show result: which task dispatched to which agent, or why it failed
cw dispatch status
- Call getQueueState tRPC
- Show: queued count, ready count, blocked count
- List ready tasks with their priority
cw dispatch complete <taskId>
- Call completeTask tRPC
- Confirm task completed
Follow existing CLI patterns for error handling and output formatting.
</action>
<verify>cw message --help and cw dispatch --help show commands</verify>
<done>Message and dispatch CLI commands functional</done>
</task>
</tasks>
<verification>
Before declaring plan complete:
- [ ] npm run build succeeds
- [ ] npm test passes
- [ ] cw message --help shows list, read, respond
- [ ] cw dispatch --help shows queue, next, status, complete
- [ ] All tRPC procedures added
</verification>
<success_criteria>
- Message tRPC procedures: listMessages, getMessage, respondToMessage
- Dispatch tRPC procedures: queueTask, dispatchNext, getQueueState, completeTask
- CLI message commands: list, read, respond
- CLI dispatch commands: queue, next, status, complete
- All requirements (AGENT-06, TASK-01, TASK-04, TASK-05) have user-facing interfaces
</success_criteria>
<output>
After completion, create `.planning/phases/05-task-dispatch/05-05-SUMMARY.md`
</output>