clawdie-ai/src/task-group.ts
Clawdie AI 86024da1d4 feat(phase6): coordinator + dynamic delegation
Adds task_decompose tool to the pi extension for heuristic domain-based
decomposition of complex requests. Adds src/task-group.ts for tracking
and synthesizing subtask groups. Adds parent_task_id column migration to
controlplane_db. Updates controlplane-runner to map specialist agent IDs
to .agent/identities/ files and pre-load each specialist's skills from
library.yaml into their system prompt at session start.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---
Build: pass | Tests: FAIL — Tests  45 failed | 761 passed (806)
2026-04-14 04:56:22 +00:00

106 lines
3.5 KiB
TypeScript

/**
* src/task-group.ts — Task group management for coordinator delegation.
*
* When the coordinator decomposes a request into subtasks, each subtask
* is created with parent_task_id pointing to the originating task. These
* functions track and synthesize the group.
*/
import type { Pool } from 'pg';
import { getSubtasks, getTaskById, updateTaskOutput } from './controlplane-db.js';
import type { Task } from './controlplane-db.js';
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface TaskGroup {
parentTaskId: string;
subtasks: Task[];
total: number;
completed: number;
failed: number;
pending: number;
inProgress: number;
allDone: boolean;
anyFailed: boolean;
}
// ---------------------------------------------------------------------------
// Queries
// ---------------------------------------------------------------------------
export async function getTaskGroup(pool: Pool, parentTaskId: string): Promise<TaskGroup> {
const subtasks = await getSubtasks(pool, parentTaskId);
const completed = subtasks.filter((t) => t.status === 'done' || t.status === 'completed').length;
const failed = subtasks.filter((t) => t.status === 'failed' || t.status === 'error').length;
const inProgress = subtasks.filter((t) => t.status === 'in_progress' || t.status === 'running').length;
const pending = subtasks.filter((t) => t.status === 'pending').length;
return {
parentTaskId,
subtasks,
total: subtasks.length,
completed,
failed,
pending,
inProgress,
allDone: subtasks.length > 0 && completed === subtasks.length,
anyFailed: failed > 0,
};
}
export async function isTaskGroupComplete(pool: Pool, parentTaskId: string): Promise<boolean> {
const group = await getTaskGroup(pool, parentTaskId);
return group.allDone;
}
// ---------------------------------------------------------------------------
// Synthesis
// ---------------------------------------------------------------------------
export async function synthesizeGroupResults(pool: Pool, parentTaskId: string): Promise<string> {
const parent = await getTaskById(pool, parentTaskId);
const group = await getTaskGroup(pool, parentTaskId);
if (group.subtasks.length === 0) {
return `Task ${parentTaskId} has no subtasks.`;
}
const lines: string[] = [
`Task: ${parent?.title ?? parentTaskId}`,
`Subtasks: ${group.total} total — ${group.completed} completed, ${group.failed} failed`,
'',
];
for (const t of group.subtasks) {
const statusEmoji = (t.status === 'done' || t.status === 'completed') ? '✓'
: (t.status === 'failed' || t.status === 'error') ? '✗'
: '…';
lines.push(`${statusEmoji} [${t.assigned_to ?? '?'}] ${t.title}`);
const output = (t.context as Record<string, unknown> | null)?.output;
if (typeof output === 'string' && output.trim()) {
lines.push(`${output.trim().slice(0, 200)}`);
}
}
if (group.failed > 0) {
lines.push('');
lines.push(`${group.failed} subtask(s) failed. Review above for details.`);
}
return lines.join('\n');
}
// ---------------------------------------------------------------------------
// Output recording (called by specialist agents when they finish)
// ---------------------------------------------------------------------------
export async function recordSubtaskOutput(
pool: Pool,
taskId: string,
output: string,
): Promise<void> {
await updateTaskOutput(pool, taskId, output);
}