Разложите задачу на граф до назначения агентов
Агент - исполнитель узла, а не способ декомпозиции. Сначала определите зависимости, артефакты и точки объединения, и только затем решайте, кто выполняет узлы. Фраза "пусть пять агентов работают параллельно" ничего не говорит о возможности параллелизма.
Нарисуйте направленный ациклический граф. Для каждого узла укажите входы, выходы, write set, проверку и зависимости - в companion-проекте это исполняемый контракт.
export type TaskKind = "plan" | "test" | "security" | "docs" | "review";
export type TaskStatus = "pending" | "running" | "completed" | "failed";
export interface WorkItem {
readonly id: string;
readonly title: string;
readonly kind: TaskKind;
readonly dependsOn: readonly string[];
readonly writeSet: readonly string[];
readonly maxAttempts: number;
}
export interface AgentContract {
readonly id: AgentId;
readonly accepts: readonly TaskKind[];
readonly allowedWritePrefixes: readonly string[];
readonly allowedHandoffs: readonly AgentId[];
}
export interface Usage {
readonly calls: number;
readonly tokens: number;
readonly costUnits: number;Проверить такой граф стоит по пяти вопросам к каждому узлу: какой точный input делает работу возможной; какой artifact подтверждает завершение; можно ли проверить output без чтения внутреннего reasoning; с какими узлами пересекается write set; может ли узел завершиться или повториться независимо. Часто после этого оказывается, что три придуманные роли стоят на одной критической цепочке и ускорить задачу не могут.
Граф проверяется кодом до запуска исполнителей - на дубли, неизвестные зависимости, self-dependency и циклы.
export function validateWorkGraph(tasks: readonly WorkItem[]): void {
if (tasks.length === 0) {
throw new Error("Work graph must contain at least one task");
}
const ids = new Set<string>();
for (const task of tasks) {
if (ids.has(task.id)) {
throw new Error(`Duplicate task id: ${task.id}`);
}
if (task.maxAttempts < 1 || !Number.isInteger(task.maxAttempts)) {
throw new Error(`Invalid maxAttempts for ${task.id}`);
}
ids.add(task.id);
}
for (const task of tasks) {
for (const dependency of task.dependsOn) {
if (!ids.has(dependency)) {
throw new Error(`Unknown dependency ${dependency} for ${task.id}`);
}
if (dependency === task.id) {
throw new Error(`Task ${task.id} depends on itself`);
}
}
}
const visiting = new Set<string>();
const visited = new Set<string>();
const byId = new Map(tasks.map((task) => [task.id, task]));
const visit = (id: string): void => {
if (visiting.has(id)) {
throw new Error(`Cycle detected at ${id}`);
}
if (visited.has(id)) {
return;
}
visiting.add(id);
const task = byId.get(id);
if (!task) {
throw new Error(`Missing task ${id}`);
}
for (const dependency of task.dependsOn) {
visit(dependency);
}
visiting.delete(id);
visited.add(id);
};Не путайте роли с узлами. Один агент может выполнить несколько последовательных узлов. Один узел может породить несколько одинаковых workers по map/reduce. Число названий в prompt не равно числу полезных параллельных ветвей.
Граф есть. Теперь честный вопрос: а существует ли в нём полезный параллелизм вообще.