fix(dispatch): Filter planning-category tasks from dispatch pipeline and agent context
Planning tasks (research, discuss, plan, detail, refine) have their own architect flow and should never enter the dispatch pipeline or clutter agent context. Three changes: 1. Phase auto-queue skips planning-category tasks 2. Safety net in getNextDispatchable() skips planning tasks 3. gatherInitiativeContext() filters to execution tasks only
This commit is contained in:
@@ -56,8 +56,9 @@ AccountCredentialsRefreshedEvent { accountId, expiresAt, previousExpiresAt? }
|
|||||||
2. **Dispatch** — `dispatchNext()` finds highest-priority task with all deps complete
|
2. **Dispatch** — `dispatchNext()` finds highest-priority task with all deps complete
|
||||||
3. **Priority order**: high > medium > low, then oldest first (FIFO within priority)
|
3. **Priority order**: high > medium > low, then oldest first (FIFO within priority)
|
||||||
4. **Checkpoint skip** — Tasks with type starting with `checkpoint:` skip auto-dispatch
|
4. **Checkpoint skip** — Tasks with type starting with `checkpoint:` skip auto-dispatch
|
||||||
5. **Approval check** — `completeTask()` checks `requiresApproval` (task-level, then initiative-level)
|
5. **Planning skip** — Planning-category tasks (research, discuss, plan, detail, refine) skip auto-dispatch — they use the architect flow
|
||||||
6. **Approval flow** — If approval required: status → `pending_approval`, emit `task:pending_approval`
|
6. **Approval check** — `completeTask()` checks `requiresApproval` (task-level, then initiative-level)
|
||||||
|
7. **Approval flow** — If approval required: status → `pending_approval`, emit `task:pending_approval`
|
||||||
|
|
||||||
### DispatchManager Methods
|
### DispatchManager Methods
|
||||||
|
|
||||||
@@ -77,7 +78,7 @@ AccountCredentialsRefreshedEvent { accountId, expiresAt, previousExpiresAt? }
|
|||||||
|
|
||||||
1. **Queue** — `queuePhase(phaseId)` validates phase is approved, gets dependencies
|
1. **Queue** — `queuePhase(phaseId)` validates phase is approved, gets dependencies
|
||||||
2. **Dispatch** — `dispatchNextPhase()` finds phase with all deps complete
|
2. **Dispatch** — `dispatchNextPhase()` finds phase with all deps complete
|
||||||
3. **Auto-queue tasks** — When phase starts, all pending tasks are queued
|
3. **Auto-queue tasks** — When phase starts, pending execution tasks are queued (planning-category tasks excluded)
|
||||||
4. **Events** — `phase:queued`, `phase:started`, `phase:completed`, `phase:blocked`
|
4. **Events** — `phase:queued`, `phase:started`, `phase:completed`, `phase:blocked`
|
||||||
|
|
||||||
### PhaseDispatchManager Methods
|
### PhaseDispatchManager Methods
|
||||||
|
|||||||
@@ -117,9 +117,9 @@ Each procedure uses `require*Repository(ctx)` helpers that throw `TRPCError(INTE
|
|||||||
| Procedure | Type | Description |
|
| Procedure | Type | Description |
|
||||||
|-----------|------|-------------|
|
|-----------|------|-------------|
|
||||||
| spawnArchitectDiscuss | mutation | Discussion agent |
|
| spawnArchitectDiscuss | mutation | Discussion agent |
|
||||||
| spawnArchitectPlan | mutation | Plan agent (generates phases). Passes full initiative context (existing phases, tasks, pages) |
|
| spawnArchitectPlan | mutation | Plan agent (generates phases). Passes initiative context (phases, execution tasks only, pages) |
|
||||||
| spawnArchitectRefine | mutation | Refine agent (generates proposals) |
|
| spawnArchitectRefine | mutation | Refine agent (generates proposals) |
|
||||||
| spawnArchitectDetail | mutation | Detail agent (generates tasks). Passes full initiative context (sibling phases, tasks, pages) |
|
| spawnArchitectDetail | mutation | Detail agent (generates tasks). Passes initiative context (phases, execution tasks only, pages) |
|
||||||
|
|
||||||
### Dispatch
|
### Dispatch
|
||||||
| Procedure | Type | Description |
|
| Procedure | Type | Description |
|
||||||
|
|||||||
@@ -135,6 +135,12 @@ export class DefaultDispatchManager implements DispatchManager {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip planning-category tasks (handled by architect flow)
|
||||||
|
if (task && isPlanningCategory(task.category)) {
|
||||||
|
log.debug({ taskId: qt.taskId, category: task.category }, 'skipping planning-category task');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
readyTasks.push(qt);
|
readyTasks.push(qt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import type { InitiativeRepository } from '../db/repositories/initiative-reposit
|
|||||||
import type { ProjectRepository } from '../db/repositories/project-repository.js';
|
import type { ProjectRepository } from '../db/repositories/project-repository.js';
|
||||||
import type { BranchManager } from '../git/branch-manager.js';
|
import type { BranchManager } from '../git/branch-manager.js';
|
||||||
import type { PhaseDispatchManager, DispatchManager, QueuedPhase, PhaseDispatchResult } from './types.js';
|
import type { PhaseDispatchManager, DispatchManager, QueuedPhase, PhaseDispatchResult } from './types.js';
|
||||||
import { phaseBranchName } from '../git/branch-naming.js';
|
import { phaseBranchName, isPlanningCategory } from '../git/branch-naming.js';
|
||||||
import { ensureProjectClone } from '../git/project-clones.js';
|
import { ensureProjectClone } from '../git/project-clones.js';
|
||||||
import { createModuleLogger } from '../logger/index.js';
|
import { createModuleLogger } from '../logger/index.js';
|
||||||
|
|
||||||
@@ -191,10 +191,10 @@ export class DefaultPhaseDispatchManager implements PhaseDispatchManager {
|
|||||||
// Remove from queue (now being worked on)
|
// Remove from queue (now being worked on)
|
||||||
this.phaseQueue.delete(nextPhase.phaseId);
|
this.phaseQueue.delete(nextPhase.phaseId);
|
||||||
|
|
||||||
// Auto-queue pending tasks for this phase
|
// Auto-queue pending execution tasks for this phase (skip planning-category tasks)
|
||||||
const phaseTasks = await this.taskRepository.findByPhaseId(nextPhase.phaseId);
|
const phaseTasks = await this.taskRepository.findByPhaseId(nextPhase.phaseId);
|
||||||
for (const task of phaseTasks) {
|
for (const task of phaseTasks) {
|
||||||
if (task.status === 'pending') {
|
if (task.status === 'pending' && !isPlanningCategory(task.category)) {
|
||||||
await this.dispatchManager.queue(task.id);
|
await this.dispatchManager.queue(task.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
buildRefinePrompt,
|
buildRefinePrompt,
|
||||||
buildDetailPrompt,
|
buildDetailPrompt,
|
||||||
} from '../../agent/prompts/index.js';
|
} from '../../agent/prompts/index.js';
|
||||||
|
import { isPlanningCategory } from '../../git/branch-naming.js';
|
||||||
import type { PhaseRepository } from '../../db/repositories/phase-repository.js';
|
import type { PhaseRepository } from '../../db/repositories/phase-repository.js';
|
||||||
import type { TaskRepository } from '../../db/repositories/task-repository.js';
|
import type { TaskRepository } from '../../db/repositories/task-repository.js';
|
||||||
import type { PageRepository } from '../../db/repositories/page-repository.js';
|
import type { PageRepository } from '../../db/repositories/page-repository.js';
|
||||||
@@ -68,7 +69,10 @@ async function gatherInitiativeContext(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { phases, tasks: allTasks, pages };
|
// Only include implementation tasks in agent context — planning tasks are irrelevant noise
|
||||||
|
const implementationTasks = allTasks.filter(t => !isPlanningCategory(t.category));
|
||||||
|
|
||||||
|
return { phases, tasks: implementationTasks, pages };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function architectProcedures(publicProcedure: ProcedureBuilder) {
|
export function architectProcedures(publicProcedure: ProcedureBuilder) {
|
||||||
|
|||||||
Reference in New Issue
Block a user