import test from "node:test"; import assert from "node:assert/strict"; import { mkdtemp, readFile, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { resolve } from "node:path"; import { SchemaDrivenExecutionEngine } from "../src/agents/orchestration.js"; import type { ActorExecutionResult } from "../src/agents/pipeline.js"; function createManifest(): unknown { return { schemaVersion: "1", topologies: ["hierarchical", "retry-unrolled", "sequential"], personas: [ { id: "product", displayName: "Product", systemPromptTemplate: "Product planning for {{repo}}", toolClearance: { allowlist: ["read_file"], banlist: ["delete_file"], }, }, { id: "task", displayName: "Task", systemPromptTemplate: "Task planning for {{repo}}", toolClearance: { allowlist: ["read_file", "write_file"], banlist: ["git_reset"], }, }, { id: "coder", displayName: "Coder", systemPromptTemplate: "Coder implements {{ticket}}", toolClearance: { allowlist: ["read_file", "write_file"], banlist: ["rm"], }, }, { id: "qa", displayName: "QA", systemPromptTemplate: "QA validates {{ticket}}", toolClearance: { allowlist: ["read_file"], banlist: ["write_file"], }, }, ], relationships: [ { parentPersonaId: "product", childPersonaId: "task", constraints: { maxChildren: 2, maxDepth: 2, }, }, { parentPersonaId: "task", childPersonaId: "coder", constraints: { maxChildren: 3, maxDepth: 3, }, }, ], topologyConstraints: { maxDepth: 6, maxRetries: 2, }, pipeline: { entryNodeId: "project-gate", nodes: [ { id: "project-gate", actorId: "project_gate", personaId: "product", }, { id: "task-plan", actorId: "task_plan", personaId: "task", }, { id: "coder-1", actorId: "coder", personaId: "coder", constraints: { maxRetries: 1, }, }, { id: "qa-1", actorId: "qa", personaId: "qa", }, ], edges: [ { from: "project-gate", to: "task-plan", on: "success", when: [ { type: "state_flag", key: "needs_bootstrap", equals: true, }, ], }, { from: "project-gate", to: "coder-1", on: "success", when: [ { type: "state_flag", key: "needs_bootstrap", equals: false, }, ], }, { from: "task-plan", to: "coder-1", on: "success", }, { from: "coder-1", to: "qa-1", on: "success", when: [ { type: "history_has_event", event: "validation_fail", }, ], }, ], }, }; } test("runs DAG pipeline with state-dependent routing and retry behavior", async () => { const workspaceRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-workspace-")); const stateRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-session-state-")); const projectContextPath = resolve(stateRoot, "project-context.json"); await writeFile(resolve(workspaceRoot, "PRD.md"), "# PRD\n", "utf8"); let coderAttempts = 0; const engine = new SchemaDrivenExecutionEngine({ manifest: createManifest(), settings: { workspaceRoot, stateRoot, projectContextPath, runtimeContext: { repo: "ai_ops", ticket: "AIOPS-123", }, maxChildren: 4, maxDepth: 8, maxRetries: 3, }, actorExecutors: { project_gate: async () => ({ status: "success", payload: { phase: "gate", }, stateFlags: { needs_bootstrap: true, }, }), task_plan: async (input) => { assert.match(input.prompt, /ai_ops/); return { status: "success", payload: { plan: "roadmap", }, stateFlags: { roadmap_ready: true, }, }; }, coder: async (input): Promise => { assert.match(input.prompt, /AIOPS-123/); assert.deepEqual(input.toolClearance.allowlist, ["read_file", "write_file"]); coderAttempts += 1; if (coderAttempts === 1) { return { status: "validation_fail", payload: { issue: "missing test", }, }; } return { status: "success", payload: { code: "done", }, }; }, qa: async () => ({ status: "success", payload: { qa: "ok", }, }), }, behaviorHandlers: { coder: { onValidationFail: () => ({ lastValidationFailure: "coder-1", }), }, }, }); const result = await engine.runSession({ sessionId: "session-orchestration-1", initialPayload: { task: "Implement pipeline", }, }); assert.equal(result.status, "success"); assert.deepEqual( result.records.map((record) => `${record.nodeId}:${record.status}:${String(record.attempt)}`), [ "project-gate:success:1", "task-plan:success:1", "coder-1:validation_fail:1", "coder-1:success:2", "qa-1:success:1", ], ); assert.equal(result.finalState.flags.needs_bootstrap, true); assert.equal(result.finalState.flags.roadmap_ready, true); assert.equal(result.finalState.metadata.lastValidationFailure, "coder-1"); assert.deepEqual(engine.planChildPersonas({ parentPersonaId: "task", depth: 1 }), ["coder"]); }); test("runs parallel topology blocks concurrently and routes via domain-event edges", async () => { const workspaceRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-workspace-")); const stateRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-session-state-")); const projectContextPath = resolve(stateRoot, "project-context.json"); const manifest = { schemaVersion: "1", topologies: ["parallel", "retry-unrolled", "sequential"], personas: [ { id: "planner", displayName: "Planner", systemPromptTemplate: "Planner {{repo}}", toolClearance: { allowlist: ["read_file"], banlist: [], }, }, { id: "coder", displayName: "Coder", systemPromptTemplate: "Coder {{repo}}", toolClearance: { allowlist: ["read_file", "write_file"], banlist: [], }, }, { id: "integrator", displayName: "Integrator", systemPromptTemplate: "Integrator {{repo}}", toolClearance: { allowlist: ["read_file"], banlist: [], }, }, ], relationships: [], topologyConstraints: { maxDepth: 5, maxRetries: 2, }, pipeline: { entryNodeId: "plan", nodes: [ { id: "plan", actorId: "plan_actor", personaId: "planner", }, { id: "code-a", actorId: "code_a", personaId: "coder", topology: { kind: "parallel", blockId: "implementation", }, }, { id: "code-b", actorId: "code_b", personaId: "coder", topology: { kind: "parallel", blockId: "implementation", }, }, { id: "integrate", actorId: "integrate_actor", personaId: "integrator", }, ], edges: [ { from: "plan", to: "code-a", on: "success", }, { from: "plan", to: "code-b", on: "success", }, { from: "code-a", to: "integrate", event: "code_committed", }, { from: "code-b", to: "integrate", event: "code_committed", }, ], }, } as const; let activeCoders = 0; let maxConcurrentCoders = 0; let releaseCoders: (() => void) | undefined; const codersReleased = new Promise((resolve) => { releaseCoders = resolve; }); let coderStarts = 0; let notifyBothCodersStarted: (() => void) | undefined; const bothCodersStarted = new Promise((resolve) => { notifyBothCodersStarted = resolve; }); const engine = new SchemaDrivenExecutionEngine({ manifest, settings: { workspaceRoot, stateRoot, projectContextPath, runtimeContext: { repo: "ai_ops", }, maxDepth: 5, maxRetries: 2, maxChildren: 4, }, actorExecutors: { plan_actor: async () => ({ status: "success", payload: { phase: "plan", }, }), code_a: async () => { activeCoders += 1; maxConcurrentCoders = Math.max(maxConcurrentCoders, activeCoders); coderStarts += 1; if (coderStarts === 2) { notifyBothCodersStarted?.(); } await codersReleased; activeCoders = Math.max(activeCoders - 1, 0); return { status: "success", payload: { branch: "feature/a", }, events: [ { type: "code_committed", payload: { summary: "Feature A committed", }, }, ], projectContextPatch: { artifactPointers: { feature_a_commit: "feature/a@abc123", }, }, }; }, code_b: async () => { activeCoders += 1; maxConcurrentCoders = Math.max(maxConcurrentCoders, activeCoders); coderStarts += 1; if (coderStarts === 2) { notifyBothCodersStarted?.(); } await codersReleased; activeCoders = Math.max(activeCoders - 1, 0); return { status: "success", payload: { branch: "feature/b", }, events: [ { type: "code_committed", payload: { summary: "Feature B committed", }, }, ], projectContextPatch: { enqueueTasks: [ { id: "task-integrate", title: "Integrate feature branches", status: "pending", }, ], }, }; }, integrate_actor: async () => ({ status: "success", payload: { merged: true, }, events: [ { type: "branch_merged", payload: { summary: "Branches merged", }, }, ], }), }, }); const runPromise = engine.runSession({ sessionId: "session-parallel-domain-events", initialPayload: { task: "Parallel implementation", }, }); await bothCodersStarted; releaseCoders?.(); const result = await runPromise; assert.equal(maxConcurrentCoders, 2); assert.equal(result.status, "success"); assert.deepEqual( result.records.map((record) => `${record.nodeId}:${record.status}`), ["plan:success", "code-a:success", "code-b:success", "integrate:success"], ); const storedContextRaw = await readFile(projectContextPath, "utf8"); const storedContext = JSON.parse(storedContextRaw) as { artifactPointers: Record; taskQueue: Array<{ id: string }>; }; assert.equal(storedContext.artifactPointers.feature_a_commit, "feature/a@abc123"); assert.equal(storedContext.taskQueue[0]?.id, "task-integrate"); const finalStatePointer = storedContext.artifactPointers["sessions/session-parallel-domain-events/final_state"]; assert.ok(finalStatePointer); assert.match(finalStatePointer, /state\.json$/); }); test("fails fast after two sequential hard failures", async () => { const workspaceRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-workspace-")); const stateRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-session-state-")); const projectContextPath = resolve(stateRoot, "project-context.json"); const manifest = { schemaVersion: "1", topologies: ["sequential"], personas: [ { id: "coder", displayName: "Coder", systemPromptTemplate: "Coder", toolClearance: { allowlist: [], banlist: [], }, }, ], relationships: [], topologyConstraints: { maxDepth: 4, maxRetries: 0, }, pipeline: { entryNodeId: "first", nodes: [ { id: "first", actorId: "first_actor", personaId: "coder", }, { id: "second", actorId: "second_actor", personaId: "coder", }, ], edges: [ { from: "first", to: "second", on: "failure", }, ], }, } as const; const engine = new SchemaDrivenExecutionEngine({ manifest, settings: { workspaceRoot, stateRoot, projectContextPath, maxDepth: 4, maxRetries: 0, maxChildren: 2, runtimeContext: {}, }, actorExecutors: { first_actor: async () => ({ status: "failure", payload: { error: "network timeout while reaching upstream API", }, failureKind: "hard", }), second_actor: async () => ({ status: "failure", payload: { error: "HTTP 403 from provider", }, failureKind: "hard", }), }, }); await assert.rejects( () => engine.runSession({ sessionId: "session-hard-failure", initialPayload: { task: "Trigger hard failures", }, }), /Hard failure threshold reached/, ); }); test("marks aggregate status as failure when a terminal node fails", async () => { const workspaceRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-workspace-")); const stateRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-session-state-")); const projectContextPath = resolve(stateRoot, "project-context.json"); const manifest = { schemaVersion: "1", topologies: ["sequential"], personas: [ { id: "coder", displayName: "Coder", systemPromptTemplate: "Coder", toolClearance: { allowlist: [], banlist: [], }, }, ], relationships: [], topologyConstraints: { maxDepth: 3, maxRetries: 0, }, pipeline: { entryNodeId: "build", nodes: [ { id: "build", actorId: "build_actor", personaId: "coder", }, { id: "verify", actorId: "verify_actor", personaId: "coder", }, ], edges: [ { from: "build", to: "verify", on: "success", }, ], }, } as const; const engine = new SchemaDrivenExecutionEngine({ manifest, settings: { workspaceRoot, stateRoot, projectContextPath, maxDepth: 3, maxRetries: 0, maxChildren: 2, runtimeContext: {}, }, actorExecutors: { build_actor: async () => ({ status: "success", payload: { step: "build", }, }), verify_actor: async () => ({ status: "failure", payload: { error: "verification failed", }, failureKind: "soft", }), }, }); const result = await engine.runSession({ sessionId: "session-terminal-failure", initialPayload: { task: "Aggregate failure status", }, }); assert.equal(result.status, "failure"); assert.deepEqual( result.records.map((record) => `${record.nodeId}:${record.status}`), ["build:success", "verify:failure"], ); }); test("propagates abort signal into actor execution and stops the run", async () => { const workspaceRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-workspace-")); const stateRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-session-state-")); const projectContextPath = resolve(stateRoot, "project-context.json"); const manifest = { schemaVersion: "1", topologies: ["sequential"], personas: [ { id: "coder", displayName: "Coder", systemPromptTemplate: "Coder", toolClearance: { allowlist: [], banlist: [], }, }, ], relationships: [], topologyConstraints: { maxDepth: 2, maxRetries: 0, }, pipeline: { entryNodeId: "long-run", nodes: [ { id: "long-run", actorId: "long_actor", personaId: "coder", }, ], edges: [], }, } as const; let observedAbort = false; const engine = new SchemaDrivenExecutionEngine({ manifest, settings: { workspaceRoot, stateRoot, projectContextPath, maxDepth: 2, maxRetries: 0, maxChildren: 2, runtimeContext: {}, }, actorExecutors: { long_actor: async (input) => { await new Promise((resolve, reject) => { const timeout = setTimeout(resolve, 5000); input.signal.addEventListener( "abort", () => { observedAbort = true; clearTimeout(timeout); reject(input.signal.reason ?? new Error("aborted")); }, { once: true }, ); }); return { status: "success", payload: { unreachable: true, }, }; }, }, }); const controller = new AbortController(); const runPromise = engine.runSession({ sessionId: "session-abort", initialPayload: { task: "Abort test", }, signal: controller.signal, }); setTimeout(() => { controller.abort(new Error("manual-abort")); }, 20); await assert.rejects(() => runPromise, /(AbortError|manual-abort|aborted)/i); assert.equal(observedAbort, true); });