Files
harness-tools/extensions/pi-workflow/engine.ts
T

4711 lines
157 KiB
TypeScript

import { createHash, randomUUID } from "node:crypto";
import * as path from "node:path";
import type {
AgentProfile,
AssignmentGitState,
AssignmentSelection,
CommandContinuation,
CommandReceipt,
FeatureScriptInsertionPoint,
GateDecision,
ProfileRole,
StageEligibilityEvidence,
UiImpact,
VerificationCommand,
VerificationEvidence,
WorkflowAssignment,
WorkflowConfig,
WorkflowFacts,
WorkflowGate,
WorkflowRun,
WorkflowStage,
} from "./domain.ts";
import { eligibleProfiles, selectInitialDiscoveryProfile } from "./profiles.ts";
import { enforceUiImpact } from "./ui-policy.ts";
import {
readVerifiedPlanArtifact,
type StartRecord,
type WorkflowStore,
} from "./store.ts";
import {
WorkspaceSafetyError,
type PrepareWorkspaceOptions,
type WorkspacePlan,
} from "./workspace.ts";
const FEATURE_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const MAX_FEATURE_NAME_LENGTH = 80;
const DEFAULT_LEASE_STALE_MS = 30_000;
const DEFAULT_ASSIGNMENT_HEARTBEAT_MS = 10_000;
const DEFAULT_ASSIGNMENT_ABORT_TIMEOUT_MS = 15_000;
const ACTIVE_ASSIGNMENT_TOKENS = new Set<string>();
interface ActiveAssignmentExecution {
runId: string;
assignmentId: string;
controller: AbortController;
settled: Promise<void>;
resolveSettled: () => void;
resumeAfterAbort: Promise<void>;
resolveResumeAfterAbort: () => void;
metadata?: AssignmentExecutionMetadata;
transcriptArtifact?: WorkflowAssignment["transcriptArtifact"];
}
const ACTIVE_ASSIGNMENT_EXECUTIONS = new Map<string, ActiveAssignmentExecution>();
const ACTIVE_SCRIPT_EXECUTIONS = new Map<
string,
{ controller: AbortController; settled: Promise<void>; resolve: () => void }
>();
const ACTIVE_STARTS = new Map<
string,
{
fingerprint: string;
operation: Promise<WorkflowView>;
published: Promise<WorkflowView>;
}
>();
export interface FeatureScriptInput {
source: string;
args?: unknown;
insertionPoint: FeatureScriptInsertionPoint;
activeToolNames: string[];
}
export interface StartWorkflowCommand {
action: "start";
request: string;
featureName: string;
idempotencyKey: string;
concurrency?: number;
background?: boolean;
script?: FeatureScriptInput;
}
export interface StartAnalysisWorkflowCommand {
action: "start-analysis";
request: string;
source: string;
args?: unknown;
activeToolNames: string[];
idempotencyKey: string;
}
export interface DecideWorkflowCommand {
action: "decide";
runId: string;
gateId: string;
expectedRevision: number;
decision: GateDecision;
idempotencyKey: string;
activeToolNames?: string[];
}
export interface InspectWorkflowCommand {
action: "inspect";
runId: string;
}
export interface AbortWorkflowCommand {
action: "abort";
runId: string;
expectedRevision: number;
idempotencyKey: string;
}
interface ContextCommandBase {
runId: string;
gateId: string;
preparationId: string;
planRevision: number;
contextRevision: number;
expectedRevision: number;
}
export interface ContinueContextCommand extends ContextCommandBase {
action: "continue-context";
sourceSessionFile?: string;
}
export interface ClaimContextHandoffCommand extends ContextCommandBase {
action: "claim-context-handoff";
sourceSessionFile: string;
targetSessionFile: string;
reason: "new";
}
export interface ConsumeContextHandoffCommand extends ContextCommandBase {
action: "consume-context-handoff";
targetSessionFile: string;
previousSessionFile: string;
}
export interface CompleteNormalCompactionCommand extends ContextCommandBase {
action: "complete-normal-compaction";
outcome: "succeeded" | "no-op" | "failed";
error?: string;
}
export interface RecoverContextIntentCommand extends ContextCommandBase {
action: "recover-context-intent";
reason:
| "startup"
| "reload"
| "resume"
| "fork"
| "expired"
| "cancelled";
}
export type WorkflowCommand =
| StartWorkflowCommand
| StartAnalysisWorkflowCommand
| DecideWorkflowCommand
| InspectWorkflowCommand
| AbortWorkflowCommand
| ContinueContextCommand
| ClaimContextHandoffCommand
| ConsumeContextHandoffCommand
| CompleteNormalCompactionCommand
| RecoverContextIntentCommand;
export interface WorkflowView {
runId: string;
revision: number;
status: WorkflowRun["status"];
background: boolean;
workspace: WorkflowRun["workspace"];
openGate?: WorkflowGate;
uiImpact?: UiImpact;
uiImpactEvidence: string[];
affectedUiSurfaces: string[];
planArtifact?: WorkflowRun["planArtifact"];
reportArtifact?: WorkflowRun["reportArtifact"];
implementationContext?: WorkflowRun["implementationContext"];
stages: WorkflowStage[];
assignments: WorkflowAssignment[];
mutatorProfileNames: string[];
reviewCycles: number;
verificationCycles: number;
verificationEvidence?: WorkflowRun["verificationEvidence"];
reviewVerdict?: WorkflowRun["reviewVerdict"];
error?: WorkflowRun["error"];
}
export interface AssignmentExecutionMetadata {
childSessionId?: string;
provider?: string;
model?: string;
effort?: string;
contextCapacity?: number;
transcript: unknown[];
gitBefore?: AssignmentGitState;
gitAfter?: AssignmentGitState;
}
export interface AssignmentExecutionResult {
kind: "pi-workflow-assignment-result";
output: unknown;
metadata: AssignmentExecutionMetadata;
}
export interface AssignmentExecutionError extends Error {
assignmentMetadata: AssignmentExecutionMetadata;
}
export interface AssignmentRequest {
runId: string;
role: ProfileRole | "remediate";
request: string;
profile: AgentProfile;
facts: WorkflowFacts;
decisions: WorkflowRun["decisions"];
plannerCandidates?: StageEligibilityEvidence[];
cwd: string;
signal: AbortSignal;
mutates?: boolean;
ownedPaths?: string[];
captureGitState?: boolean;
allowedReadTools?: string[];
runConcurrency?: number;
approvedPlan?: string;
verificationEvidence?: VerificationEvidence;
reviewDiff?: string;
onReserved?: () => Promise<void>;
}
export interface AssignmentExecutor {
readonly reservesCapacity?: boolean;
run(request: AssignmentRequest): Promise<unknown>;
}
export interface DiscoveryOutput {
questions: Array<{ id: string; prompt: string }>;
candidatePaths: string[];
languages: string[];
tags: string[];
riskTags: string[];
}
export interface PlannedAssignmentOutput {
role: "prototype" | "implement" | "verify" | "review";
profileName: string;
rationale: string;
mutates: boolean;
ownedPaths: string[];
}
export interface PlannedVerificationCommand {
executable: string;
args: string[];
ownedPaths: string[];
kind: "test" | "typecheck" | "lint" | "build" | "browser" | "generation";
}
export interface DesignOutput {
plan: string;
uiImpact: UiImpact;
uiImpactEvidence: string[];
affectedUiSurfaces: string[];
assignments: PlannedAssignmentOutput[];
acceptanceCriteria: string[];
verificationCommands: PlannedVerificationCommand[];
}
export interface PrototypeOutput {
path: string;
route: string;
structuralRationale: string;
responsiveChecks: string[];
browserEvidence: string[];
launchInstructions: string;
}
export interface ImplementationOutput {
summary: string;
changedPaths: string[];
focusedTests: string[];
}
export interface VerificationOutput {
succeeded: boolean;
focusedBehavioralTests: boolean;
summary: string;
}
export interface ReviewOutput {
specification: {
verdict: "approved" | "changes_requested";
findings: Array<{ severity: string; message: string; path?: string }>;
};
standards: {
verdict: "approved" | "changes_requested";
findings: Array<{ severity: string; message: string; path?: string }>;
};
}
export interface WorkspaceManager {
prepare(options: PrepareWorkspaceOptions): Promise<WorkspacePlan>;
create(plan: WorkspacePlan): Promise<WorkflowRun["workspace"]>;
reconcile(
workspace: WorkflowRun["workspace"],
expectedWorktreeIdentity?: string,
): Promise<WorkflowRun["workspace"]>;
}
export interface ReviewDiffProvider {
read(worktreeRoot: string, baseHead?: string): Promise<string>;
}
export interface HostCommandExecutor {
run(options: {
worktreeRoot: string;
ownedPaths: string[];
executable: string;
args: string[];
}): Promise<{
result: { code: number; stdout: string; stderr: string };
}>;
}
export interface PrototypePolicyValidator {
validateCandidate(options: {
worktreeRoot: string;
assignment: WorkflowAssignment;
output: PrototypeOutput;
}): Promise<{ artifactFiles: string[]; visualDigest: string }>;
validateCleanup(options: {
worktreeRoot: string;
candidates: WorkflowRun["prototypeCandidates"];
}): Promise<void>;
}
export interface ScriptRuntime {
run(options: {
source: string;
args: unknown;
cwd: string;
signal: AbortSignal;
onAssign: (
role: string,
prompt: string,
signal: AbortSignal,
requestId: number,
) => Promise<{ ok: boolean; output: string; structured?: unknown; error?: string }>;
onStage: (title: string) => void;
}): Promise<unknown>;
}
export interface WorkflowEngineOptions {
store: WorkflowStore;
workspace: WorkspaceManager;
executor: AssignmentExecutor;
hostCommands?: HostCommandExecutor;
reviewDiff?: ReviewDiffProvider;
scriptRuntime?: ScriptRuntime;
prototypeValidator?: PrototypePolicyValidator;
sourceRoot: string;
parentSessionId: string;
effectiveConfig: WorkflowConfig;
profiles: AgentProfile[];
now?: () => number;
createId?: () => string;
processId?: number;
leaseStaleMs?: number;
isProcessAlive?: (processId: number) => boolean;
assignmentHeartbeatMs?: number;
assignmentAbortTimeoutMs?: number;
afterAssignmentPersist?: (
assignment: WorkflowAssignment,
run: WorkflowRun,
) => void | Promise<void>;
afterRunPersist?: (run: WorkflowRun) => void | Promise<void>;
}
function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function isAssignmentExecutionResult(
value: unknown,
): value is AssignmentExecutionResult {
return (
isRecord(value) &&
value.kind === "pi-workflow-assignment-result" &&
isRecord(value.metadata) &&
Array.isArray(value.metadata.transcript)
);
}
function isAssignmentExecutionError(
value: unknown,
): value is AssignmentExecutionError {
return (
value instanceof Error &&
"assignmentMetadata" in value &&
isRecord(value.assignmentMetadata) &&
Array.isArray(value.assignmentMetadata.transcript)
);
}
async function waitForAssignmentStops(
executions: ActiveAssignmentExecution[],
timeoutMs = DEFAULT_ASSIGNMENT_ABORT_TIMEOUT_MS,
) {
if (executions.length === 0) return;
let timer: ReturnType<typeof setTimeout> | undefined;
try {
await Promise.race([
Promise.allSettled(executions.map((execution) => execution.settled)),
new Promise<never>((_resolve, reject) => {
timer = setTimeout(
() =>
reject(
new Error(
"Timed out stopping active Assignments; Workflow Run was not aborted",
),
),
timeoutMs,
);
}),
]);
} finally {
if (timer) clearTimeout(timer);
}
}
function applyExecutionMetadata(
assignment: WorkflowAssignment,
metadata: AssignmentExecutionMetadata | undefined,
transcriptArtifact: WorkflowAssignment["transcriptArtifact"],
) {
if (!metadata) return;
assignment.childSessionId = metadata.childSessionId;
assignment.effectiveProvider = metadata.provider;
assignment.effectiveModel = metadata.model;
assignment.effectiveEffort = metadata.effort;
assignment.contextCapacity = metadata.contextCapacity;
assignment.transcriptArtifact = transcriptArtifact;
assignment.gitBefore = metadata.gitBefore;
assignment.gitAfter = metadata.gitAfter;
}
function assertKeys(
value: Record<string, unknown>,
required: string[],
optional: string[] = [],
) {
const allowed = new Set([...required, ...optional]);
for (const key of Object.keys(value)) {
if (!allowed.has(key)) throw new Error(`Unexpected field ${key}`);
}
for (const key of required) {
if (!(key in value)) throw new Error(`Missing required field ${key}`);
}
}
function nonEmptyString(value: unknown, label: string) {
if (typeof value !== "string" || value.trim().length === 0) {
throw new Error(`${label} must be a non-empty string`);
}
}
function nonNegativeInteger(value: unknown, label: string) {
if (!Number.isInteger(value) || (value as number) < 0) {
throw new Error(`${label} must be a non-negative integer`);
}
}
function validateDecisionShape(value: unknown): asserts value is GateDecision {
if (!isRecord(value) || typeof value.kind !== "string") {
throw new Error("decision must be an object with a kind");
}
if (value.kind === "answer-questions") {
assertKeys(value, ["kind", "answers"]);
if (!isRecord(value.answers)) throw new Error("answers must be an object");
for (const [id, answer] of Object.entries(value.answers)) {
nonEmptyString(id, "question ID");
nonEmptyString(answer, `answer for ${id}`);
}
return;
}
if (value.kind === "approve-plan") {
assertKeys(value, ["kind", "approved", "acceptedUiImpact"], ["comment"]);
if (value.approved !== true)
throw new Error("approve-plan requires approved=true");
if (
value.acceptedUiImpact !== "none" &&
value.acceptedUiImpact !== "minor" &&
value.acceptedUiImpact !== "non-trivial"
) {
throw new Error("acceptedUiImpact is invalid");
}
if (value.comment !== undefined && typeof value.comment !== "string") {
throw new Error("comment must be a string");
}
return;
}
if (value.kind === "revise-plan" || value.kind === "refine-prototypes") {
assertKeys(value, ["kind", "feedback"]);
nonEmptyString(value.feedback, "feedback");
return;
}
if (value.kind === "accept-prototype") {
assertKeys(
value,
["kind", "candidateId", "candidateRevision", "browserReviewed"],
["feedback"],
);
nonEmptyString(value.candidateId, "candidateId");
nonNegativeInteger(value.candidateRevision, "candidateRevision");
if (value.browserReviewed !== true) {
throw new Error("accept-prototype requires browserReviewed=true");
}
if (value.feedback !== undefined && typeof value.feedback !== "string") {
throw new Error("feedback must be a string");
}
return;
}
if (value.kind === "select-implementation-context") {
assertKeys(value, ["kind", "strategy"]);
if (
value.strategy !== "fresh-session" &&
value.strategy !== "smart-compact" &&
value.strategy !== "current-session"
) {
throw new Error("implementation context strategy is invalid");
}
return;
}
if (value.kind === "report-context-preparation") {
assertKeys(
value,
["kind", "preparationId", "planRevision", "method", "outcome"],
["error"],
);
nonEmptyString(value.preparationId, "preparationId");
nonNegativeInteger(value.planRevision, "planRevision");
if (value.method !== "smart_compact") throw new Error("method is invalid");
if (
value.outcome !== "unavailable" &&
value.outcome !== "succeeded" &&
value.outcome !== "no-op" &&
value.outcome !== "failed"
) {
throw new Error("context preparation outcome is invalid");
}
if (value.outcome === "failed") {
nonEmptyString(value.error, "context preparation error");
} else if (value.error !== undefined) {
throw new Error("context preparation error is only valid for failure");
}
return;
}
if (value.kind === "recover") {
assertKeys(value, ["kind", "resolution"]);
if (
value.resolution !== "retry" &&
value.resolution !== "inspect" &&
value.resolution !== "abort"
) {
throw new Error("recovery resolution is invalid");
}
return;
}
throw new Error(`Unknown decision kind ${value.kind}`);
}
function validateActiveToolNames(value: unknown) {
if (
!Array.isArray(value) ||
value.some((name) => typeof name !== "string" || name.trim().length === 0)
) {
throw new Error("activeToolNames must be an array of non-empty strings");
}
}
function validateFeatureScript(value: unknown) {
if (!isRecord(value)) throw new Error("script must be an object");
assertKeys(
value,
["source", "insertionPoint", "activeToolNames"],
["args"],
);
nonEmptyString(value.source, "script source");
if (
value.insertionPoint !== "before-design" &&
value.insertionPoint !== "after-design" &&
value.insertionPoint !== "before-review"
) {
throw new Error("script insertionPoint is invalid");
}
validateActiveToolNames(value.activeToolNames);
}
function validateWorkflowCommand(
value: unknown,
): asserts value is WorkflowCommand {
if (!isRecord(value) || typeof value.action !== "string") {
throw new Error("workflow command must be an object with an action");
}
if (value.action === "start") {
assertKeys(
value,
["action", "request", "featureName", "idempotencyKey"],
["background", "concurrency", "script"],
);
nonEmptyString(value.request, "request");
nonEmptyString(value.featureName, "featureName");
nonEmptyString(value.idempotencyKey, "idempotencyKey");
if (
value.concurrency !== undefined &&
(typeof value.concurrency !== "number" ||
!Number.isInteger(value.concurrency) ||
value.concurrency < 1)
) {
throw new Error("concurrency must be a positive integer");
}
if (
value.background !== undefined &&
typeof value.background !== "boolean"
) {
throw new Error("background must be a boolean");
}
if (value.script !== undefined) validateFeatureScript(value.script);
return;
}
if (value.action === "start-analysis") {
assertKeys(
value,
["action", "request", "source", "activeToolNames", "idempotencyKey"],
["args"],
);
nonEmptyString(value.request, "request");
nonEmptyString(value.source, "source");
nonEmptyString(value.idempotencyKey, "idempotencyKey");
validateActiveToolNames(value.activeToolNames);
return;
}
if (value.action === "inspect") {
assertKeys(value, ["action", "runId"]);
nonEmptyString(value.runId, "runId");
return;
}
if (value.action === "abort") {
assertKeys(value, [
"action",
"runId",
"expectedRevision",
"idempotencyKey",
]);
nonEmptyString(value.runId, "runId");
nonNegativeInteger(value.expectedRevision, "expectedRevision");
nonEmptyString(value.idempotencyKey, "idempotencyKey");
return;
}
if (
value.action === "continue-context" ||
value.action === "claim-context-handoff" ||
value.action === "consume-context-handoff" ||
value.action === "complete-normal-compaction" ||
value.action === "recover-context-intent"
) {
const actionFields: Record<string, { required: string[]; optional?: string[] }> = {
"continue-context": { required: [], optional: ["sourceSessionFile"] },
"claim-context-handoff": {
required: ["sourceSessionFile", "targetSessionFile", "reason"],
},
"consume-context-handoff": {
required: ["targetSessionFile", "previousSessionFile"],
},
"complete-normal-compaction": {
required: ["outcome"],
optional: ["error"],
},
"recover-context-intent": { required: ["reason"] },
};
const fields = actionFields[value.action];
if (!fields) throw new Error("Unknown context command");
assertKeys(
value,
[
"action",
"runId",
"gateId",
"preparationId",
"planRevision",
"contextRevision",
"expectedRevision",
...fields.required,
],
fields.optional,
);
nonEmptyString(value.runId, "runId");
nonEmptyString(value.gateId, "gateId");
nonEmptyString(value.preparationId, "preparationId");
nonNegativeInteger(value.planRevision, "planRevision");
nonNegativeInteger(value.contextRevision, "contextRevision");
nonNegativeInteger(value.expectedRevision, "expectedRevision");
if ("sourceSessionFile" in value && value.sourceSessionFile !== undefined) {
nonEmptyString(value.sourceSessionFile, "sourceSessionFile");
}
if ("targetSessionFile" in value) {
nonEmptyString(value.targetSessionFile, "targetSessionFile");
}
if ("previousSessionFile" in value) {
nonEmptyString(value.previousSessionFile, "previousSessionFile");
}
if (value.action === "claim-context-handoff" && value.reason !== "new") {
throw new Error("handoff claim reason must be new");
}
if (value.action === "recover-context-intent") {
if (
value.reason !== "startup" &&
value.reason !== "reload" &&
value.reason !== "resume" &&
value.reason !== "fork" &&
value.reason !== "expired" &&
value.reason !== "cancelled"
) {
throw new Error("context recovery reason is invalid");
}
}
if (value.action === "complete-normal-compaction") {
if (
value.outcome !== "succeeded" &&
value.outcome !== "no-op" &&
value.outcome !== "failed"
) {
throw new Error("normal compaction outcome is invalid");
}
if (value.outcome === "failed") {
nonEmptyString(value.error, "normal compaction error");
} else if (value.error !== undefined) {
throw new Error("normal compaction error is only valid for failure");
}
}
return;
}
if (value.action === "decide") {
assertKeys(value, [
"action",
"runId",
"gateId",
"expectedRevision",
"decision",
"idempotencyKey",
], ["activeToolNames"]);
nonEmptyString(value.runId, "runId");
nonEmptyString(value.gateId, "gateId");
nonNegativeInteger(value.expectedRevision, "expectedRevision");
nonEmptyString(value.idempotencyKey, "idempotencyKey");
if (
value.activeToolNames !== undefined &&
(!Array.isArray(value.activeToolNames) ||
value.activeToolNames.some((name) => typeof name !== "string"))
) {
throw new Error("activeToolNames must be an array of strings");
}
validateDecisionShape(value.decision);
return;
}
throw new Error(`Unknown workflow action ${value.action}`);
}
function stringList(value: unknown, label: string): string[] {
if (
!Array.isArray(value) ||
value.some((entry) => typeof entry !== "string")
) {
throw new Error(`${label} must be an array of strings`);
}
return value.map((entry) => entry.trim()).filter(Boolean);
}
function discoveryOutput(value: unknown): DiscoveryOutput {
if (!isRecord(value) || !Array.isArray(value.questions)) {
throw new Error("Discovery Assignment returned an invalid result");
}
if (value.questions.length > 20) {
throw new Error("Discovery may return at most 20 questions");
}
const questions = value.questions.map((question, index) => {
if (
!isRecord(question) ||
typeof question.id !== "string" ||
question.id.trim().length === 0 ||
question.id.length > 160 ||
typeof question.prompt !== "string" ||
question.prompt.trim().length === 0 ||
question.prompt.length > 2_000
) {
throw new Error(`Discovery question ${index} is invalid`);
}
return { id: question.id.trim(), prompt: question.prompt.trim() };
});
if (
new Set(questions.map((question) => question.id)).size !== questions.length
) {
throw new Error("Discovery question IDs must be unique");
}
return {
questions,
candidatePaths: stringList(value.candidatePaths ?? [], "candidatePaths"),
languages: stringList(value.languages ?? [], "languages"),
tags: stringList(value.tags ?? [], "tags").map((entry) =>
entry.toLowerCase(),
),
riskTags: stringList(value.riskTags ?? [], "riskTags").map((entry) =>
entry.toLowerCase(),
),
};
}
function designOutput(value: unknown): DesignOutput {
if (
!isRecord(value) ||
typeof value.plan !== "string" ||
value.plan.trim().length === 0 ||
(value.uiImpact !== "none" &&
value.uiImpact !== "minor" &&
value.uiImpact !== "non-trivial")
) {
throw new Error("Design Assignment returned an invalid result");
}
const rawAssignments = value.assignments ?? [];
if (!Array.isArray(rawAssignments)) {
throw new Error("Design assignments must be an array");
}
const assignments = rawAssignments.map((assignment, index) => {
if (
!isRecord(assignment) ||
(assignment.role !== "prototype" &&
assignment.role !== "implement" &&
assignment.role !== "verify" &&
assignment.role !== "review") ||
typeof assignment.profileName !== "string" ||
assignment.profileName.trim().length === 0 ||
typeof assignment.rationale !== "string" ||
assignment.rationale.trim().length === 0 ||
typeof assignment.mutates !== "boolean"
) {
throw new Error(`Design assignment ${index} is invalid`);
}
const role = assignment.role as PlannedAssignmentOutput["role"];
return {
role,
profileName: assignment.profileName.trim(),
rationale: assignment.rationale.trim(),
mutates: assignment.mutates,
ownedPaths: stringList(
assignment.ownedPaths,
`Design assignment ${index} ownedPaths`,
),
};
});
const acceptanceCriteria = stringList(
value.acceptanceCriteria ?? [],
"acceptanceCriteria",
);
const rawCommands = value.verificationCommands ?? [];
if (!Array.isArray(rawCommands)) {
throw new Error("verificationCommands must be an array");
}
const verificationCommands = rawCommands.map((command, index) => {
if (
!isRecord(command) ||
typeof command.executable !== "string" ||
command.executable.trim().length === 0 ||
(command.kind !== "test" &&
command.kind !== "typecheck" &&
command.kind !== "lint" &&
command.kind !== "build" &&
command.kind !== "browser" &&
command.kind !== "generation")
) {
throw new Error(`Verification command ${index} is invalid`);
}
return {
executable: command.executable.trim(),
args: stringList(command.args, `Verification command ${index} args`),
ownedPaths: stringList(
command.ownedPaths,
`Verification command ${index} ownedPaths`,
),
kind: command.kind as PlannedVerificationCommand["kind"],
};
});
if (acceptanceCriteria.length === 0) {
throw new Error("Design requires at least one acceptance criterion");
}
if (!verificationCommands.some((command) => command.kind === "test")) {
throw new Error("Design requires at least one focused behavioral test command");
}
return {
plan: value.plan.trim(),
uiImpact: value.uiImpact,
uiImpactEvidence: stringList(
value.uiImpactEvidence ?? [],
"uiImpactEvidence",
),
affectedUiSurfaces: stringList(
value.affectedUiSurfaces ?? [],
"affectedUiSurfaces",
),
assignments,
acceptanceCriteria,
verificationCommands,
};
}
function prototypeOutput(value: unknown): PrototypeOutput {
if (
!isRecord(value) ||
typeof value.path !== "string" ||
value.path.trim().length === 0 ||
typeof value.route !== "string" ||
value.route.trim().length === 0 ||
typeof value.structuralRationale !== "string" ||
value.structuralRationale.trim().length === 0 ||
typeof value.launchInstructions !== "string" ||
value.launchInstructions.trim().length === 0
) {
throw new Error("Prototype Assignment returned an invalid result");
}
const responsiveChecks = stringList(
value.responsiveChecks,
"prototype responsiveChecks",
);
const browserEvidence = stringList(
value.browserEvidence,
"prototype browserEvidence",
);
if (responsiveChecks.length === 0 || browserEvidence.length === 0) {
throw new Error("Prototype requires responsive checks and browser evidence");
}
return {
path: value.path.trim(),
route: value.route.trim(),
structuralRationale: value.structuralRationale.trim(),
responsiveChecks,
browserEvidence,
launchInstructions: value.launchInstructions.trim(),
};
}
function implementationOutput(value: unknown): ImplementationOutput {
if (
!isRecord(value) ||
typeof value.summary !== "string" ||
value.summary.trim().length === 0
) {
throw new Error("Implementation Assignment returned an invalid result");
}
return {
summary: value.summary.trim(),
changedPaths: stringList(value.changedPaths ?? [], "changedPaths"),
focusedTests: stringList(value.focusedTests ?? [], "focusedTests"),
};
}
function verificationOutput(value: unknown): VerificationOutput {
if (
!isRecord(value) ||
typeof value.succeeded !== "boolean" ||
typeof value.focusedBehavioralTests !== "boolean" ||
typeof value.summary !== "string" ||
value.summary.trim().length === 0
) {
throw new Error("Verification Assignment returned an invalid result");
}
return {
succeeded: value.succeeded,
focusedBehavioralTests: value.focusedBehavioralTests,
summary: value.summary.trim(),
};
}
function reviewAxis(value: unknown, label: string): ReviewOutput["specification"] {
if (
!isRecord(value) ||
(value.verdict !== "approved" && value.verdict !== "changes_requested") ||
!Array.isArray(value.findings)
) {
throw new Error(`${label} review axis is invalid`);
}
return {
verdict: value.verdict,
findings: value.findings.map((finding, index) => {
if (
!isRecord(finding) ||
typeof finding.severity !== "string" ||
typeof finding.message !== "string" ||
finding.message.trim().length === 0 ||
(finding.path !== undefined && typeof finding.path !== "string")
) {
throw new Error(`${label} review finding ${index} is invalid`);
}
return {
severity: finding.severity,
message: finding.message.trim(),
...(finding.path ? { path: finding.path } : {}),
};
}),
};
}
function reviewOutput(value: unknown): ReviewOutput {
if (!isRecord(value)) throw new Error("Review Assignment returned an invalid result");
return {
specification: reviewAxis(value.specification, "Specification"),
standards: reviewAxis(value.standards, "Standards"),
};
}
function stableJson(value: unknown): string {
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
if (isRecord(value)) {
return `{${Object.keys(value)
.sort()
.map((key) => `${JSON.stringify(key)}:${stableJson(value[key])}`)
.join(",")}}`;
}
return JSON.stringify(value);
}
function fingerprint(value: unknown) {
return createHash("sha256").update(stableJson(value)).digest("hex");
}
function requestTerms(request: string) {
return [
...new Set(
request
.toLowerCase()
.split(/[^a-z0-9-]+/)
.map((term) => term.trim())
.filter(Boolean),
),
];
}
function activeGate(run: WorkflowRun) {
for (let index = run.gates.length - 1; index >= 0; index--) {
const gate = run.gates[index];
if (gate?.status === "open") return gate;
}
return undefined;
}
function isTerminalStatus(status: WorkflowRun["status"]) {
return status === "completed" || status === "failed" || status === "aborted";
}
function openGate(run: WorkflowRun, gate: WorkflowGate) {
run.gates.push(gate);
run.deliveries.push({
key: `gate:${run.runId}:${gate.id}:${gate.revision}`,
state: "pending",
createdAt: gate.openedAt,
});
}
function view(run: WorkflowRun): WorkflowView {
return {
runId: run.runId,
revision: run.revision,
status: run.status,
background: run.background,
workspace: structuredClone(run.workspace),
...(activeGate(run) ? { openGate: structuredClone(activeGate(run)) } : {}),
...(run.uiImpact ? { uiImpact: run.uiImpact } : {}),
uiImpactEvidence: [...run.uiImpactEvidence],
affectedUiSurfaces: [...run.affectedUiSurfaces],
...(run.planArtifact ? { planArtifact: { ...run.planArtifact } } : {}),
...(run.reportArtifact ? { reportArtifact: { ...run.reportArtifact } } : {}),
...(run.implementationContext
? { implementationContext: structuredClone(run.implementationContext) }
: {}),
stages: structuredClone(run.stages),
assignments: structuredClone(run.assignments),
mutatorProfileNames: [...run.mutatorProfileNames],
reviewCycles: run.reviewCycles,
verificationCycles: run.verificationCycles,
...(run.verificationEvidence
? { verificationEvidence: structuredClone(run.verificationEvidence) }
: {}),
...(run.reviewVerdict
? { reviewVerdict: structuredClone(run.reviewVerdict) }
: {}),
...(run.error ? { error: { ...run.error } } : {}),
};
}
function selectionEvidence(
profiles: AgentProfile[],
facts: WorkflowFacts,
role: ProfileRole,
): { profile: AgentProfile; selection: AssignmentSelection } {
const evaluations = eligibleProfiles(profiles, facts, role);
const selected = evaluations.find((evaluation) => evaluation.eligible);
if (!selected)
throw new Error(`No eligible ${role} Agent Profile is available`);
return {
profile: selected.profile,
selection: {
eligibleProfiles: evaluations.map((evaluation) => ({
profileName: evaluation.profile.name,
profileDigest: evaluation.profile.digest,
eligible: evaluation.eligible,
reasons: [...evaluation.reasons],
specificity: evaluation.specificity,
})),
selectedProfileName: selected.profile.name,
rationale: `Selected the highest-specificity eligible ${role} profile with a code-point name tie-breaker.`,
},
};
}
function stage(run: WorkflowRun, kind: WorkflowStage["kind"]) {
const found = run.stages.find((candidate) => candidate.kind === kind);
if (!found) throw new Error(`Workflow stage ${kind} is missing`);
return found;
}
export function decisionIsLegal(
gateKind: WorkflowGate["kind"],
decisionKind: GateDecision["kind"],
) {
return (
(gateKind === "questions" && decisionKind === "answer-questions") ||
(gateKind === "plan-approval" &&
(decisionKind === "approve-plan" || decisionKind === "revise-plan")) ||
(gateKind === "prototype-acceptance" &&
(decisionKind === "accept-prototype" ||
decisionKind === "refine-prototypes")) ||
(gateKind === "implementation-context" &&
(decisionKind === "select-implementation-context" ||
decisionKind === "report-context-preparation")) ||
(gateKind === "recovery" && decisionKind === "recover")
);
}
function decisionContinuation(
gateKind: WorkflowGate["kind"],
decision: GateDecision,
): CommandContinuation {
if (gateKind === "questions") return "design";
if (gateKind === "plan-approval" && decision.kind === "revise-plan") {
return "design";
}
if (gateKind === "plan-approval" && decision.kind === "approve-plan") {
return "prototype";
}
if (gateKind === "prototype-acceptance") return "prototype";
if (
gateKind === "implementation-context" &&
((decision.kind === "select-implementation-context" &&
decision.strategy === "current-session") ||
(decision.kind === "report-context-preparation" &&
(decision.outcome === "succeeded" || decision.outcome === "no-op")))
) {
return "implementation";
}
if (gateKind === "recovery") return "recovery";
return "none";
}
function legalDecision(gate: WorkflowGate, decision: GateDecision) {
if (!decisionIsLegal(gate.kind, decision.kind)) {
throw new Error(`Decision ${decision.kind} is not legal for ${gate.kind}`);
}
}
function validateDecisionPayload(gate: WorkflowGate, decision: GateDecision) {
if (decision.kind === "answer-questions") {
if (!isRecord(gate.payload) || !Array.isArray(gate.payload.questions)) {
throw new Error("Questions Gate payload is invalid");
}
for (const question of gate.payload.questions) {
if (!isRecord(question) || typeof question.id !== "string") {
throw new Error("Questions Gate payload is invalid");
}
const answer = decision.answers[question.id];
if (typeof answer !== "string" || answer.trim().length === 0) {
throw new Error(`Question ${question.id} requires an answer`);
}
}
}
if (
(decision.kind === "revise-plan" ||
decision.kind === "refine-prototypes") &&
decision.feedback.trim().length === 0
) {
throw new Error(`${decision.kind} feedback is required`);
}
if (decision.kind === "accept-prototype") {
if (!isRecord(gate.payload) || !Array.isArray(gate.payload.candidates)) {
throw new Error("Prototype Acceptance Gate payload is invalid");
}
const candidate = gate.payload.candidates.find(
(entry) =>
isRecord(entry) &&
entry.id === decision.candidateId &&
entry.revision === decision.candidateRevision,
);
if (!candidate) {
throw new Error("Accepted prototype candidate or revision is stale");
}
}
if (
decision.kind === "report-context-preparation" &&
decision.outcome === "failed" &&
decision.error.trim().length === 0
) {
throw new Error("Failed context preparation requires an actionable error");
}
}
function processAlive(processId: number) {
try {
process.kill(processId, 0);
return true;
} catch {
return false;
}
}
export class WorkflowEngine {
private readonly options: WorkflowEngineOptions;
private readonly now: () => number;
private readonly createId: () => string;
private readonly processId: number;
private readonly leaseStaleMs: number;
private readonly isProcessAlive: (processId: number) => boolean;
private readonly assignmentHeartbeatMs: number;
private readonly assignmentAbortTimeoutMs: number;
constructor(options: WorkflowEngineOptions) {
this.options = options;
this.now = options.now ?? Date.now;
this.createId = options.createId ?? randomUUID;
this.processId = options.processId ?? process.pid;
this.leaseStaleMs = options.leaseStaleMs ?? DEFAULT_LEASE_STALE_MS;
this.isProcessAlive = options.isProcessAlive ?? processAlive;
this.assignmentHeartbeatMs =
options.assignmentHeartbeatMs ?? DEFAULT_ASSIGNMENT_HEARTBEAT_MS;
this.assignmentAbortTimeoutMs =
options.assignmentAbortTimeoutMs ?? DEFAULT_ASSIGNMENT_ABORT_TIMEOUT_MS;
}
async execute(command: WorkflowCommand): Promise<WorkflowView> {
validateWorkflowCommand(command);
if (command.action === "start") return this.start(command);
if (command.action === "start-analysis") return this.startAnalysis(command);
let run = await this.options.store.loadRun(command.runId);
if (command.action === "inspect") return view(run);
if (command.action === "consume-context-handoff") {
return this.consumeContextHandoff(run, command);
}
if (command.action === "recover-context-intent") {
return this.recoverContextIntent(run, command);
}
run = this.reclaimStaleRecoveryLease(run, command);
this.assertActiveParent(run);
if (command.action === "abort") return this.abort(run, command);
if (command.action === "decide") return this.decide(run, command);
if (command.action === "continue-context") {
return this.continueContext(run, command);
}
if (command.action === "claim-context-handoff") {
return this.claimContextHandoff(run, command);
}
return this.completeNormalCompaction(run, command);
}
async recover() {
const recovered: WorkflowView[] = [];
for (const current of await this.options.store.listRuns()) {
if (
current.status === "completed" ||
current.status === "failed" ||
current.status === "aborted"
) {
continue;
}
let run = current;
if (
run.workspace.state === "creating" &&
run.lease &&
this.isProcessAlive(run.lease.processId)
) {
continue;
}
if (run.workspace.state === "creating") {
const workspace = await this.options.workspace.reconcile(
run.workspace,
run.lease?.worktreeIdentity,
);
run = await this.persistWorkspaceOutcome(run, workspace);
if (run.workspace.state !== "ready") {
recovered.push(view(run));
continue;
}
}
if (
run.implementationContext?.status === "normal-compact-running" &&
this.leaseIsStale(run)
) {
run = await this.transition(run, (next) => {
const context = next.implementationContext;
if (!context) throw new Error("Implementation Context state is missing");
context.status = "failed";
context.outcome = "failed";
context.error =
"Normal compaction was interrupted before its callback completed";
context.runRevision = next.revision + 1;
next.status = "waiting";
});
recovered.push(view(run));
continue;
}
const running = run.assignments.filter(
(assignment) =>
assignment.status === "running" ||
(assignment.status === "queued" && Boolean(assignment.executionToken)),
);
const alreadyWaiting = activeGate(run)?.kind === "recovery";
if (running.length === 0 && alreadyWaiting) continue;
if (await this.hasActiveAssignmentLease(run.runId, running)) continue;
if (!this.leaseIsStale(run)) continue;
run = await this.transition(run, (next) => {
next.lease = {
processId: this.processId,
activeParentSessionId: this.options.parentSessionId,
heartbeatAt: this.now(),
worktreeIdentity: next.workspace.root,
};
next.parentSessionIds = [
...new Set([...next.parentSessionIds, this.options.parentSessionId]),
];
});
if (running.length === 0) {
const claimedRevision = run.revision;
const resumed = await this.resumePersistedContinuation(run);
if (resumed.revision !== claimedRevision) {
recovered.push(view(resumed));
continue;
}
}
for (const assignment of running) {
if (assignment.executionToken) {
await this.options.store.releaseAssignmentLease(
run.runId,
assignment.id,
assignment.executionToken,
);
}
}
run = await this.transition(run, (next) => {
const interruptedIds = new Set(running.map((assignment) => assignment.id));
for (const assignment of next.assignments) {
if (interruptedIds.has(assignment.id)) assignment.status = "interrupted";
}
next.lease = {
processId: this.processId,
activeParentSessionId: this.options.parentSessionId,
heartbeatAt: this.now(),
worktreeIdentity: next.workspace.root,
};
next.parentSessionIds = [
...new Set([...next.parentSessionIds, this.options.parentSessionId]),
];
next.status = "waiting";
if (!activeGate(next)) {
const gate: WorkflowGate = {
id: this.createId(),
kind: "recovery",
status: "open",
revision: next.revision + 1,
openedAt: this.now(),
payload: {
interruptedAssignmentIds: running.map(
(assignment) => assignment.id,
),
potentiallyMutating: running.some(
(assignment) => assignment.mutates,
),
},
};
openGate(next, gate);
}
});
recovered.push(view(run));
}
return recovered;
}
private start(command: StartWorkflowCommand) {
const commandFingerprint = fingerprint(command);
const activeKey = `${this.options.store.root}\0${command.idempotencyKey}`;
const active = ACTIVE_STARTS.get(activeKey);
if (active) {
if (active.fingerprint !== commandFingerprint) {
return Promise.reject(
new Error("idempotency key is active for another start command"),
);
}
return command.background
? this.latestBackgroundView(active.published)
: active.operation;
}
let resolvePublished = (_view: WorkflowView) => {};
let rejectPublished = (_error: unknown) => {};
const published = new Promise<WorkflowView>((resolve, reject) => {
resolvePublished = resolve;
rejectPublished = reject;
});
const operation = this.startOwned(command, resolvePublished);
void operation.catch(rejectPublished);
void published.catch(() => {});
ACTIVE_STARTS.set(activeKey, {
fingerprint: commandFingerprint,
operation,
published,
});
void operation
.finally(() => {
if (ACTIVE_STARTS.get(activeKey)?.operation === operation) {
ACTIVE_STARTS.delete(activeKey);
}
})
.catch(() => {});
return command.background ? published : operation;
}
private async latestBackgroundView(published: Promise<WorkflowView>) {
const initial = await published;
try {
return view(await this.options.store.loadRun(initial.runId));
} catch {
return initial;
}
}
private startAnalysis(command: StartAnalysisWorkflowCommand) {
const commandFingerprint = fingerprint(command);
const activeKey = `${this.options.store.root}\0${command.idempotencyKey}`;
const active = ACTIVE_STARTS.get(activeKey);
if (active) {
if (active.fingerprint !== commandFingerprint) {
return Promise.reject(
new Error("idempotency key is active for another start command"),
);
}
return active.operation;
}
const operation = this.startAnalysisOwned(command);
ACTIVE_STARTS.set(activeKey, {
fingerprint: commandFingerprint,
operation,
published: operation,
});
void operation
.finally(() => {
if (ACTIVE_STARTS.get(activeKey)?.operation === operation) {
ACTIVE_STARTS.delete(activeKey);
}
})
.catch(() => {});
return operation;
}
private async startAnalysisOwned(command: StartAnalysisWorkflowCommand) {
if (!this.options.scriptRuntime) {
throw new Error("Analysis scripts require the restricted script runtime");
}
const commandFingerprint = fingerprint(command);
const existing = await this.options.store.getStart(command.idempotencyKey);
if (existing) {
this.validateExistingStart(existing, commandFingerprint);
if (existing.status === "completed") {
return existing.result as unknown as WorkflowView;
}
}
const runId = existing?.runId ?? this.createId();
const reservation = await this.options.store.reserveStart(
command.idempotencyKey,
commandFingerprint,
runId,
);
this.validateExistingStart(reservation.record, commandFingerprint);
let run: WorkflowRun;
try {
run = await this.options.store.loadRun(reservation.record.runId);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
run = this.initialAnalysisRun(reservation.record.runId, command);
run = (await this.options.store.createRun(run)).run;
}
if (run.status !== "completed" && run.status !== "failed") {
run = await this.runWorkflowScript(run);
}
const result = view(run);
await this.options.store.completeStart(command.idempotencyKey, result);
return result;
}
private async startOwned(
command: StartWorkflowCommand,
onPublished?: (view: WorkflowView) => void,
) {
this.validateStart(command);
const commandFingerprint = fingerprint(command);
const existing = await this.options.store.getStart(command.idempotencyKey);
if (existing) {
this.validateExistingStart(existing, commandFingerprint);
if (existing.status === "completed") {
// SAFETY: completeStart only persists WorkflowView results produced by this engine.
const result = existing.result as unknown as WorkflowView;
onPublished?.(result);
return result;
}
}
const initialFacts: WorkflowFacts = {
stage: "discover",
taskKind: "feature",
requestTerms: requestTerms(command.request),
candidatePaths: [],
languages: [],
tags: [],
riskTags: [],
};
const preflightEligibility = this.preflightEligibility(initialFacts);
selectInitialDiscoveryProfile(this.options.profiles, initialFacts);
let persistedRun: WorkflowRun | undefined;
if (existing?.status === "pending") {
try {
persistedRun = await this.options.store.loadRun(existing.runId);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
}
}
let runId = existing?.runId ?? this.createId();
let workspacePlan = persistedRun
? this.workspacePlanForRun(persistedRun)
: await this.options.workspace.prepare({
sourceRoot: this.options.sourceRoot,
configuredRoot: this.options.effectiveConfig.worktrees.root,
featureName: command.featureName,
runId,
});
if (workspacePlan.branchExists) {
const owner = await this.options.store.findRunByBranch(
workspacePlan.branch,
);
const resumable =
existing?.status === "pending" && owner?.runId === runId;
const uncertainPending = existing?.status === "pending" && !owner;
if (!resumable && !uncertainPending) {
if (owner) {
throw new Error(
`Branch ${workspacePlan.branch} already belongs to Workflow Run ${owner.runId}; continue it with inspect or decide`,
);
}
throw new Error(
`Branch ${workspacePlan.branch} already exists; choose another descriptive feature name`,
);
}
}
const reservation = await this.options.store.reserveStart(
command.idempotencyKey,
commandFingerprint,
runId,
);
this.validateExistingStart(reservation.record, commandFingerprint);
if (reservation.record.runId !== runId) {
runId = reservation.record.runId;
try {
persistedRun = await this.options.store.loadRun(runId);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
persistedRun = undefined;
}
workspacePlan = persistedRun
? this.workspacePlanForRun(persistedRun)
: await this.options.workspace.prepare({
sourceRoot: this.options.sourceRoot,
configuredRoot: this.options.effectiveConfig.worktrees.root,
featureName: command.featureName,
runId,
});
}
{
let run: WorkflowRun;
let persistedBeforeThisAttempt = true;
try {
run = persistedRun ?? (await this.options.store.loadRun(runId));
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
run = this.initialRun(
runId,
command,
workspacePlan,
preflightEligibility,
);
const publication = await this.options.store.createRun(run);
persistedBeforeThisAttempt = !publication.created;
run = publication.run;
}
onPublished?.(view(run));
if (persistedBeforeThisAttempt) {
const claimed = await this.claimPersistedStartRun(run);
if (!claimed) return view(run);
run = claimed;
}
if (run.workspace.state === "creating" && persistedBeforeThisAttempt) {
const reconciled = await this.options.workspace.reconcile(
run.workspace,
run.lease?.worktreeIdentity,
);
if (reconciled.state !== "creating") {
run = await this.persistWorkspaceOutcome(run, reconciled);
}
}
if (run.workspace.state === "creating") {
let workspace: WorkflowRun["workspace"];
try {
workspace = await this.options.workspace.create(workspacePlan);
} catch (error) {
workspace = {
...run.workspace,
state: "creating",
error: error instanceof Error ? error.message : String(error),
};
}
run = await this.persistWorkspaceOutcome(run, workspace);
}
if (run.workspace.state === "ready" && run.status !== "failed") {
const discoverStatus = stage(run, "discover").status;
const designStatus = stage(run, "design").status;
if (discoverStatus === "pending" || discoverStatus === "running") {
run = await this.runDiscovery(run);
} else if (
discoverStatus === "completed" &&
(designStatus === "pending" || designStatus === "running")
) {
run = await this.runDesign(run);
}
}
const result = view(run);
await this.options.store.completeStart(command.idempotencyKey, result);
return result;
}
}
private async claimPersistedStartRun(run: WorkflowRun) {
if (
run.lease?.processId === this.processId &&
run.lease.activeParentSessionId === this.options.parentSessionId
) {
return run;
}
if (
run.workspace.state === "creating" &&
run.lease &&
run.lease.processId !== this.processId &&
this.isProcessAlive(run.lease.processId)
) {
return undefined;
}
if (!this.leaseIsStale(run)) return undefined;
try {
return await this.transition(run, (next) => {
next.lease = {
processId: this.processId,
activeParentSessionId: this.options.parentSessionId,
heartbeatAt: this.now(),
worktreeIdentity: next.workspace.root,
};
next.parentSessionIds = [
...new Set([...next.parentSessionIds, this.options.parentSessionId]),
];
});
} catch (error) {
if (
error instanceof Error &&
/Stale workflow revision/.test(error.message)
) {
return undefined;
}
throw error;
}
}
private async persistWorkspaceOutcome(
run: WorkflowRun,
workspace: WorkflowRun["workspace"],
) {
return this.transition(run, (next) => {
next.workspace = workspace;
const initialize = stage(next, "initialize");
if (workspace.state === "ready") {
initialize.status = "completed";
initialize.finishedAt = this.now();
next.status = "running";
next.error = undefined;
return;
}
initialize.status = workspace.state === "failed" ? "failed" : "waiting";
next.status = "waiting";
next.error = {
code: "workspace-recovery-required",
message:
workspace.error ?? "Workspace creation requires reconciliation",
stageId: initialize.id,
};
if (!activeGate(next)) {
openGate(next, {
id: this.createId(),
kind: "recovery",
status: "open",
revision: next.revision + 1,
openedAt: this.now(),
payload: {
workspaceState: workspace.state,
workspaceError: workspace.error,
},
});
}
});
}
private validateExistingStart(
record: StartRecord,
commandFingerprint: string,
) {
if (record.fingerprint !== commandFingerprint) {
throw new Error(
"idempotency key was already used with another start command",
);
}
if (record.status === "completed" && !record.result) {
throw new Error("Completed start record is missing its result");
}
}
private initialAnalysisRun(
runId: string,
command: StartAnalysisWorkflowCommand,
): WorkflowRun {
const now = this.now();
const run = this.initialRun(
runId,
{
action: "start",
request: command.request,
featureName: "analysis-script",
idempotencyKey: command.idempotencyKey,
},
{
sourceRoot: this.options.sourceRoot,
worktreeRoot: this.options.sourceRoot,
worktreePath: this.options.sourceRoot,
branch: "",
baseHead: "",
branchExists: false,
},
[],
);
run.mode = "analysis";
run.featureName = undefined;
run.workspace = {
state: "not-required",
sourceRoot: this.options.sourceRoot,
root: this.options.sourceRoot,
};
run.script = {
source: command.source,
...(command.args !== undefined
? { args: structuredClone(command.args) }
: {}),
activeToolNames: [...command.activeToolNames],
status: "pending",
stages: [],
executed: false,
};
run.facts = {
...run.facts,
taskKind: "analysis",
stage: "discover",
};
for (const workflowStage of run.stages) {
workflowStage.status = workflowStage.kind === "discover" ? "running" : "pending";
if (workflowStage.kind === "discover") workflowStage.startedAt = now;
else delete workflowStage.startedAt;
delete workflowStage.finishedAt;
}
return run;
}
private initialRun(
runId: string,
command: StartWorkflowCommand,
workspacePlan: WorkspacePlan,
preflightEligibility: StageEligibilityEvidence[],
): WorkflowRun {
const now = this.now();
const stages: WorkflowStage[] = [
"initialize",
"discover",
"design",
"prototype",
"implement",
"remediate",
"verify",
"review",
"complete",
].map((kind) => ({
id: `${runId}:${kind}`,
kind: kind as WorkflowStage["kind"],
status: kind === "initialize" ? "running" : "pending",
...(kind === "initialize" ? { startedAt: now } : {}),
}));
return {
runId,
revision: 0,
mode: "feature",
...(command.script
? {
script: {
source: command.script.source,
...(command.script.args !== undefined
? { args: structuredClone(command.script.args) }
: {}),
insertionPoint: command.script.insertionPoint,
activeToolNames: [...command.script.activeToolNames],
status: "pending" as const,
stages: [],
executed: false,
},
}
: {}),
status: "running",
background: command.background ?? false,
request: command.request.trim(),
featureName: command.featureName,
parentSessionIds: [this.options.parentSessionId],
lease: {
processId: this.processId,
activeParentSessionId: this.options.parentSessionId,
heartbeatAt: now,
worktreeIdentity: workspacePlan.worktreePath,
},
workspace: {
state: "creating",
sourceRoot: workspacePlan.sourceRoot,
root: workspacePlan.worktreePath,
branch: workspacePlan.branch,
baseHead: workspacePlan.baseHead,
},
effectiveConfig: {
...structuredClone(this.options.effectiveConfig),
concurrency: {
...structuredClone(this.options.effectiveConfig.concurrency),
default:
command.concurrency ??
this.options.effectiveConfig.concurrency.default,
},
},
pinnedProfiles: structuredClone(this.options.profiles),
preflightEligibility: structuredClone(preflightEligibility),
facts: {
stage: "discover",
taskKind: "feature",
requestTerms: requestTerms(command.request),
candidatePaths: [],
languages: [],
tags: [],
riskTags: [],
},
stages,
gates: [],
decisions: [],
assignments: [],
prototypeCandidates: [],
prototypeCleanupCompleted: false,
deliveries: [],
commandReceipts: [],
error: undefined,
planArtifact: undefined,
implementationContext: undefined,
uiImpact: undefined,
uiImpactEvidence: [],
affectedUiSurfaces: [],
mutatorProfileNames: [],
reviewCycles: 0,
verificationCycles: 0,
verificationEvidence: undefined,
reviewVerdict: undefined,
};
}
private async saveApprovedPlan(
run: WorkflowRun,
decision: Extract<GateDecision, { kind: "approve-plan" }>,
) {
if (!run.planArtifact) throw new Error("Plan Approval requires a plan artifact");
const currentPlan = await readVerifiedPlanArtifact(run.planArtifact);
const acceptedDecisions = [
...run.decisions.map((entry) => entry.value),
decision,
];
const contents = [
currentPlan.trimEnd(),
"## Accepted Decisions",
"```json",
JSON.stringify(acceptedDecisions, null, 2),
"```",
"## Worktree",
`- Source: ${run.workspace.sourceRoot}`,
`- Path: ${run.workspace.root ?? "not ready"}`,
`- Branch: ${run.workspace.branch ?? "not ready"}`,
`- Base HEAD: ${run.workspace.baseHead ?? "unknown"}`,
"## UI Impact Evidence",
`- Accepted classification: ${decision.acceptedUiImpact}`,
`- Affected surfaces: ${run.affectedUiSurfaces.join(", ") || "none"}`,
...run.uiImpactEvidence.map((evidence) => `- ${evidence}`),
`- Candidate paths: ${run.facts.candidatePaths.join(", ") || "none"}`,
`- Tags: ${run.facts.tags.join(", ") || "none"}`,
`- Risk tags: ${run.facts.riskTags.join(", ") || "none"}`,
].join("\n\n");
return this.options.store.savePlan(
run.runId,
run.planArtifact.revision + 1,
contents,
);
}
private async saveAcceptedPrototypePlan(
run: WorkflowRun,
decision: Extract<GateDecision, { kind: "accept-prototype" }>,
) {
if (!run.planArtifact) throw new Error("Prototype Acceptance requires a plan");
const candidate = run.prototypeCandidates.find(
(entry) =>
entry.id === decision.candidateId &&
entry.revision === decision.candidateRevision,
);
if (!candidate) throw new Error("Accepted prototype candidate is missing");
const plan = await readVerifiedPlanArtifact(run.planArtifact);
const contents = [
plan.trimEnd(),
"## Accepted Prototype",
`- Candidate: ${candidate.id} revision ${candidate.revision}`,
`- Route: ${candidate.route}`,
`- Structural direction: ${candidate.structuralRationale}`,
`- Evidence artifact: ${candidate.artifactPath}`,
`- Feedback: ${decision.feedback?.trim() || "none"}`,
].join("\n\n");
return this.options.store.savePlan(
run.runId,
run.planArtifact.revision + 1,
contents,
);
}
private prototypeCleanupAssignment(run: WorkflowRun): WorkflowAssignment {
const implementer = run.assignments.find(
(assignment) => assignment.role === "implement",
);
if (!implementer) throw new Error("Prototype cleanup requires an implementer");
const ownedPaths = [
...new Set(
run.assignments
.filter((assignment) => assignment.role === "prototype")
.flatMap((assignment) => assignment.ownedPaths),
),
];
return {
id: this.createId(),
stageId: stage(run, "remediate").id,
role: "remediate",
profileName: implementer.profileName,
profileDigest: implementer.profileDigest,
selection: {
...structuredClone(implementer.selection),
rationale:
"Remove prototype-only routes, variants, and switchers while preserving persisted prototype evidence.",
},
status: "queued",
mutates: true,
ownedPaths,
};
}
private refinementAssignments(run: WorkflowRun) {
const prototype = run.assignments.find(
(assignment) => assignment.role === "prototype",
);
if (!prototype) throw new Error("Prototype refinement requires a prototype profile");
const roots = [
...new Set(
run.assignments
.filter((assignment) => assignment.role === "prototype")
.flatMap((assignment) => assignment.ownedPaths)
.map((ownedPath) => ownedPath.replace(/[/\\]candidate-\d+$/, "")),
),
];
return Array.from({ length: 3 }, (_, index): WorkflowAssignment => ({
...structuredClone(prototype),
id: this.createId(),
status: "queued",
executionToken: undefined,
result: undefined,
error: undefined,
selection: {
...structuredClone(prototype.selection),
rationale: `Refined candidate ${index + 1} must address accepted feedback and remain structurally distinct.`,
},
ownedPaths: roots.map((root) =>
path.join(root, `refinement-${run.revision + 1}-candidate-${index + 1}`),
),
}));
}
private openImplementationContext(run: WorkflowRun) {
if (!run.planArtifact) {
throw new Error("Implementation Context requires a persisted plan");
}
if (run.implementationContext?.status === "waiting") return;
const gate: WorkflowGate = {
id: this.createId(),
kind: "implementation-context",
status: "open",
revision: run.revision + 1,
openedAt: this.now(),
payload: {
planRevision: run.planArtifact.revision,
planDigest: run.planArtifact.digest,
strategies: ["fresh-session", "smart-compact", "current-session"],
},
};
run.implementationContext = {
gateId: gate.id,
runRevision: run.revision + 1,
planRevision: run.planArtifact.revision,
preparationId: this.createId(),
activeParentSessionId: this.options.parentSessionId,
sourceParentSessionId: this.options.parentSessionId,
status: "waiting",
};
openGate(run, gate);
run.status = "waiting";
}
private async runWorkflowScript(
initialRun: WorkflowRun,
insertionPoint?: FeatureScriptInsertionPoint,
) {
let run = initialRun;
const script = run.script;
if (
!script ||
script.executed ||
(run.mode === "feature" && script.insertionPoint !== insertionPoint)
) {
return run;
}
if (!this.options.scriptRuntime) {
return this.failRun(
run,
"script-runtime-unavailable",
new Error("Custom scripts require the restricted script runtime"),
);
}
let stateQueue: Promise<void> = Promise.resolve();
const update = (mutate: (next: WorkflowRun) => void) => {
const operation = stateQueue.then(async () => {
run = await this.transition(run, mutate);
});
stateQueue = operation.catch(() => {});
return operation;
};
const allowedReadTools = run.effectiveConfig.analysisTools.filter((tool) =>
script.activeToolNames.includes(tool),
);
const stageKind: WorkflowStage["kind"] =
run.mode === "analysis"
? "discover"
: insertionPoint === "before-review"
? "review"
: "design";
const controller = new AbortController();
let resolveSettled = () => {};
const activeScript = {
controller,
settled: new Promise<void>((resolve) => {
resolveSettled = resolve;
}),
resolve: () => resolveSettled(),
};
ACTIVE_SCRIPT_EXECUTIONS.set(run.runId, activeScript);
const activeScriptAssignments = new Set<Promise<void>>();
try {
await update((next) => {
if (!next.script) throw new Error("Script state is missing");
next.script.status = "running";
});
const stageUpdates: Promise<void>[] = [];
const result = await this.options.scriptRuntime.run({
source: script.source,
args: script.args,
cwd: run.workspace.root ?? run.workspace.sourceRoot,
signal: controller.signal,
onStage: (title) => {
stageUpdates.push(
update((next) => {
if (!next.script) throw new Error("Script state is missing");
next.script.stages.push(title);
}),
);
},
onAssign: (requestedRole, prompt, signal, requestId) => {
const operation = (async () => {
if (
requestedRole !== "discover" &&
requestedRole !== "design" &&
requestedRole !== "verify" &&
requestedRole !== "review"
) {
throw new Error(
`Script Assignment role ${requestedRole} is not read-only or supported`,
);
}
const role = requestedRole as ProfileRole;
const requestFingerprint = fingerprint({ role, prompt });
await stateQueue;
const reusable = run.assignments.find(
(assignment) =>
assignment.scriptRequestId === requestId &&
assignment.scriptRequestFingerprint === requestFingerprint &&
assignment.status === "succeeded",
);
if (reusable) {
return {
ok: true,
output:
typeof reusable.result === "string"
? reusable.result
: JSON.stringify(reusable.result),
structured: structuredClone(reusable.result),
};
}
const selected = selectionEvidence(
run.pinnedProfiles,
{ ...run.facts, stage: role },
role,
);
const assignmentId = this.createId();
await update((next) => {
if (
next.assignments.length >= next.effectiveConfig.maxAssignmentsPerRun
) {
throw new Error("Workflow Run exceeded its Assignment limit");
}
next.assignments.push({
id: assignmentId,
stageId: stage(next, stageKind).id,
role,
profileName: selected.profile.name,
profileDigest: selected.profile.digest,
selection: {
...structuredClone(selected.selection),
rationale: `Custom read-only script Assignment: ${prompt.slice(0, 160)}`,
},
status: "queued",
mutates: false,
ownedPaths: [],
scriptRequestId: requestId,
scriptRequestFingerprint: requestFingerprint,
});
});
const markRunning = () =>
update((next) => {
const assignment = next.assignments.find(
(candidate) => candidate.id === assignmentId,
);
if (!assignment || assignment.status !== "queued") {
throw new Error(`Script Assignment ${assignmentId} is not queued`);
}
assignment.status = "running";
});
if (!this.options.executor.reservesCapacity) await markRunning();
try {
const executionResult = await this.options.executor.run({
runId: run.runId,
role,
request: prompt,
profile: selected.profile,
facts: { ...structuredClone(run.facts), stage: role },
decisions: structuredClone(run.decisions),
cwd: run.workspace.root ?? run.workspace.sourceRoot,
signal,
mutates: false,
ownedPaths: [],
captureGitState: true,
allowedReadTools,
runConcurrency: run.effectiveConfig.concurrency.default,
...(this.options.executor.reservesCapacity
? { onReserved: markRunning }
: {}),
});
const metadata = isAssignmentExecutionResult(executionResult)
? executionResult.metadata
: undefined;
const output = isAssignmentExecutionResult(executionResult)
? executionResult.output
: executionResult;
const transcriptArtifact = metadata
? await this.options.store.saveAssignmentTranscript(
run.runId,
assignmentId,
metadata.transcript,
)
: undefined;
await update((next) => {
const assignment = next.assignments.find(
(candidate) => candidate.id === assignmentId,
);
if (!assignment || assignment.status !== "running") {
throw new Error(`Script Assignment ${assignmentId} lost its fence`);
}
assignment.status = "succeeded";
assignment.result = structuredClone(output);
if (metadata) {
assignment.childSessionId = metadata.childSessionId;
assignment.effectiveProvider = metadata.provider;
assignment.effectiveModel = metadata.model;
assignment.effectiveEffort = metadata.effort;
assignment.contextCapacity = metadata.contextCapacity;
assignment.gitBefore = metadata.gitBefore;
assignment.gitAfter = metadata.gitAfter;
}
if (transcriptArtifact) {
assignment.transcriptArtifact = transcriptArtifact;
}
});
return {
ok: true,
output:
typeof output === "string" ? output : JSON.stringify(output),
structured: output,
};
} catch (error) {
await update((next) => {
const assignment = next.assignments.find(
(candidate) => candidate.id === assignmentId,
);
if (assignment) {
assignment.status = "failed";
assignment.error =
error instanceof Error ? error.message : String(error);
}
});
throw error;
}
})();
activeScriptAssignments.add(
operation.then(
() => {},
() => {},
),
);
return operation;
},
});
await Promise.allSettled([...activeScriptAssignments]);
await Promise.all(stageUpdates);
await update((next) => {
if (!next.script) throw new Error("Script state is missing");
next.script.status = "completed";
next.script.executed = true;
next.script.result = structuredClone(result);
if (next.mode === "analysis") {
next.status = "completed";
const discover = stage(next, "discover");
discover.status = "completed";
discover.finishedAt = this.now();
const complete = stage(next, "complete");
complete.status = "completed";
complete.startedAt ??= this.now();
complete.finishedAt = this.now();
}
});
return run;
} catch (error) {
await Promise.allSettled([...activeScriptAssignments]);
await update((next) => {
if (next.script) {
next.script.status = "failed";
next.script.error = error instanceof Error ? error.message : String(error);
}
next.status = "failed";
next.error = {
code: "script-execution-failed",
message: error instanceof Error ? error.message : String(error),
};
}).catch(() => {});
return run;
} finally {
if (ACTIVE_SCRIPT_EXECUTIONS.get(run.runId) === activeScript) {
ACTIVE_SCRIPT_EXECUTIONS.delete(run.runId);
}
activeScript.resolve();
}
}
private async runDiscovery(run: WorkflowRun) {
const selected = selectionEvidence(
run.pinnedProfiles,
{ ...run.facts, stage: "discover" },
"discover",
);
const execution = await this.executeOrReuseAssignment(
run,
"discover",
selected,
);
run = execution.run;
if (!execution.output) return run;
let output: DiscoveryOutput;
try {
output = discoveryOutput(execution.output);
} catch (error) {
return this.failRun(run, "invalid-discovery-output", error);
}
run = await this.transition(run, (next) => {
next.facts = {
...next.facts,
stage: "design",
candidatePaths: output.candidatePaths,
languages: output.languages,
tags: output.tags,
riskTags: output.riskTags,
};
const discover = stage(next, "discover");
discover.output = output;
if (output.questions.length > 0) {
discover.status = "waiting";
next.status = "waiting";
const gate: WorkflowGate = {
id: this.createId(),
kind: "questions",
status: "open",
revision: next.revision + 1,
openedAt: this.now(),
payload: { questions: output.questions },
};
openGate(next, gate);
} else {
discover.status = "completed";
discover.finishedAt = this.now();
}
});
if (output.questions.length > 0) return run;
run = await this.runWorkflowScript(run, "before-design");
return run.status === "failed" ? run : this.runDesign(run);
}
private plannerCandidates(run: WorkflowRun) {
const roles: ProfileRole[] = ["prototype", "implement", "verify", "review"];
return roles.map((role): StageEligibilityEvidence => {
const candidates = eligibleProfiles(
run.pinnedProfiles,
{ ...run.facts, stage: role },
role,
).map((evaluation) => ({
profileName: evaluation.profile.name,
profileDigest: evaluation.profile.digest,
eligible: evaluation.eligible,
reasons: [...evaluation.reasons],
specificity: evaluation.specificity,
}));
return { role, candidates };
});
}
private buildPlannedAssignments(run: WorkflowRun, output: DesignOutput) {
const candidates = this.plannerCandidates(run);
const required: PlannedAssignmentOutput["role"][] = [
"implement",
"verify",
"review",
...(output.uiImpact === "non-trivial" ? (["prototype"] as const) : []),
];
for (const role of required) {
if (output.assignments.filter((assignment) => assignment.role === role).length !== 1) {
throw new Error(`Design must select exactly one ${role} Assignment`);
}
}
if (new Set(output.assignments.map((assignment) => assignment.role)).size !== output.assignments.length) {
throw new Error("Design Assignment roles must be unique");
}
if (
output.uiImpact !== "non-trivial" &&
output.assignments.some((assignment) => assignment.role === "prototype")
) {
throw new Error("Prototype Assignment requires non-trivial UI impact");
}
const planned = output.assignments.flatMap((selection): WorkflowAssignment[] => {
const evidence = candidates.find((entry) => entry.role === selection.role);
const selected = evidence?.candidates.find(
(candidate) =>
candidate.eligible && candidate.profileName === selection.profileName,
);
if (!evidence || !selected) {
throw new Error(
`Planner selected ineligible ${selection.role} profile ${selection.profileName}`,
);
}
const profile = run.pinnedProfiles.find(
(candidate) =>
candidate.name === selected.profileName &&
candidate.digest === selected.profileDigest,
);
if (!profile) throw new Error(`Pinned profile ${selected.profileName} is missing`);
const shouldMutate =
selection.role === "prototype" || selection.role === "implement";
if (selection.mutates !== shouldMutate) {
throw new Error(`${selection.role} Assignment mutation policy is invalid`);
}
if (selection.mutates && selection.ownedPaths.length === 0) {
throw new Error(`${selection.role} Assignment requires owned paths`);
}
const candidateCount = selection.role === "prototype" ? 3 : 1;
return Array.from({ length: candidateCount }, (_, index) => ({
id: this.createId(),
stageId: stage(run, selection.role).id,
role: selection.role,
profileName: profile.name,
profileDigest: profile.digest,
selection: {
eligibleProfiles: structuredClone(evidence.candidates),
selectedProfileName: profile.name,
rationale:
selection.role === "prototype"
? `${selection.rationale} Candidate ${index + 1} must be structurally distinct, responsive, and browser-validated.`
: selection.rationale,
},
status: "queued" as const,
mutates: selection.mutates,
ownedPaths:
selection.role === "prototype"
? selection.ownedPaths.map((ownedPath) =>
path.join(ownedPath, `candidate-${index + 1}`),
)
: [...selection.ownedPaths],
}));
});
const mutators = new Set(
planned.filter((assignment) => assignment.mutates).map((assignment) => assignment.profileName),
);
const reviewer = planned.find((assignment) => assignment.role === "review");
if (!reviewer || mutators.has(reviewer.profileName)) {
throw new Error("Planner must select an independent review profile");
}
return planned;
}
private async runDesign(run: WorkflowRun, forceNewAssignment = false) {
const selected = selectionEvidence(
run.pinnedProfiles,
{ ...run.facts, stage: "design" },
"design",
);
const execution = await this.executeOrReuseAssignment(
run,
"design",
selected,
forceNewAssignment,
);
run = execution.run;
if (!execution.output) return run;
let output: DesignOutput;
let plannedAssignments: WorkflowAssignment[];
let planArtifact: NonNullable<WorkflowRun["planArtifact"]>;
try {
output = designOutput(execution.output);
const uiPolicy = enforceUiImpact({
plannerImpact: output.uiImpact,
plannerEvidence: output.uiImpactEvidence,
affectedSurfaces: output.affectedUiSurfaces,
facts: run.facts,
});
output = {
...output,
uiImpact: uiPolicy.impact,
uiImpactEvidence: uiPolicy.evidence,
affectedUiSurfaces: uiPolicy.affectedSurfaces,
};
plannedAssignments = this.buildPlannedAssignments(run, output);
const planDocument = [
output.plan,
"## Acceptance Criteria",
...output.acceptanceCriteria.map((criterion) => `- ${criterion}`),
"## UI Impact",
`- Classification: ${output.uiImpact}`,
`- Affected surfaces: ${output.affectedUiSurfaces.join(", ") || "none"}`,
...output.uiImpactEvidence.map((evidence) => `- ${evidence}`),
"## Planned Assignments",
...output.assignments.map(
(assignment) =>
`- ${assignment.role}: ${assignment.profileName} owns ${assignment.ownedPaths.join(", ") || "no mutable paths"}`,
),
"## Verification Commands",
...output.verificationCommands.map(
(command) =>
`- ${command.executable} ${command.args.join(" ")} (${command.kind})`,
),
].join("\n\n");
planArtifact = await this.options.store.savePlan(
run.runId,
run.revision + 1,
planDocument,
);
} catch (error) {
return this.failRun(run, "invalid-design-output", error);
}
run = await this.transition(run, (next) => {
next.planArtifact = planArtifact;
next.uiImpact = output.uiImpact;
next.uiImpactEvidence = [...output.uiImpactEvidence];
next.affectedUiSurfaces = [...output.affectedUiSurfaces];
next.verificationEvidence = {
commands: output.verificationCommands.map((command) => ({
id: this.createId(),
...structuredClone(command),
status: "pending",
})),
focusedBehavioralTests: false,
verifierSucceeded: false,
};
next.status = "waiting";
const design = stage(next, "design");
design.status = "waiting";
design.output = output;
const plannedRoles: ProfileRole[] = [
"prototype",
"implement",
"verify",
"review",
];
next.assignments = next.assignments.filter(
(assignment) =>
assignment.role === "remediate" ||
!plannedRoles.includes(assignment.role) ||
assignment.status !== "queued",
);
next.assignments.push(...structuredClone(plannedAssignments));
const gate: WorkflowGate = {
id: this.createId(),
kind: "plan-approval",
status: "open",
revision: next.revision + 1,
openedAt: this.now(),
payload: {
planArtifact,
uiImpact: output.uiImpact,
uiImpactEvidence: output.uiImpactEvidence,
affectedUiSurfaces: output.affectedUiSurfaces,
},
};
openGate(next, gate);
});
return this.runWorkflowScript(run, "after-design");
}
private async executeOrReuseAssignment(
run: WorkflowRun,
role: "discover" | "design",
selected: ReturnType<typeof selectionEvidence>,
forceNewAssignment = false,
) {
const stageId = stage(run, role).id;
const existing = [...run.assignments]
.reverse()
.find(
(assignment) =>
assignment.stageId === stageId && assignment.role === role,
);
if (existing && !forceNewAssignment) {
return {
run,
output: existing.status === "succeeded" ? existing.result : undefined,
};
}
return this.runAssignment(run, role, selected);
}
private async assignmentEvidence(
run: WorkflowRun,
role: ProfileRole | "remediate",
) {
if (role !== "verify" && role !== "review") return {};
if (!run.planArtifact) throw new Error(`${role} requires an approved plan`);
const approvedPlan = await readVerifiedPlanArtifact(run.planArtifact);
if (role === "verify") {
return {
approvedPlan,
verificationEvidence: structuredClone(run.verificationEvidence),
};
}
if (!this.options.reviewDiff) {
throw new Error("Review requires the read-only Git diff provider");
}
return {
approvedPlan,
verificationEvidence: structuredClone(run.verificationEvidence),
reviewDiff: await this.options.reviewDiff.read(
run.workspace.root ?? run.workspace.sourceRoot,
run.workspace.baseHead,
),
};
}
private async runAssignment(
run: WorkflowRun,
role: ProfileRole | "remediate",
selected: ReturnType<typeof selectionEvidence>,
planned?: WorkflowAssignment,
) {
const assignmentId = planned?.id ?? this.createId();
const executionToken = this.createId();
const abortController = new AbortController();
let resolveSettled = () => {};
let resolveResumeAfterAbort = () => {};
const activeExecution: ActiveAssignmentExecution = {
runId: run.runId,
assignmentId,
controller: abortController,
settled: new Promise<void>((resolve) => {
resolveSettled = resolve;
}),
resolveSettled: () => resolveSettled(),
resumeAfterAbort: new Promise<void>((resolve) => {
resolveResumeAfterAbort = resolve;
}),
resolveResumeAfterAbort: () => resolveResumeAfterAbort(),
};
ACTIVE_ASSIGNMENT_EXECUTIONS.set(executionToken, activeExecution);
try {
await this.options.store.createAssignmentLease(run.runId, assignmentId, {
token: executionToken,
processId: this.processId,
heartbeatAt: this.now(),
});
run = await this.transition(run, (next) => {
next.status = "running";
const targetStage = stage(next, role);
targetStage.status = "running";
targetStage.startedAt ??= this.now();
if (planned) {
const assignment = next.assignments.find(
(candidate) => candidate.id === assignmentId,
);
if (!assignment || assignment.status !== "queued") {
throw new Error(`Planned Assignment ${assignmentId} is not queued`);
}
assignment.executionToken = executionToken;
if (assignment.mutates) {
next.mutatorProfileNames = [
...new Set([...next.mutatorProfileNames, assignment.profileName]),
];
}
} else {
next.assignments.push({
id: assignmentId,
stageId: targetStage.id,
role,
profileName: selected.profile.name,
profileDigest: selected.profile.digest,
selection: selected.selection,
status: "queued",
mutates: false,
ownedPaths: [],
executionToken,
});
}
});
} catch (error) {
await this.options.store
.releaseAssignmentLease(run.runId, assignmentId, executionToken)
.catch(() => {});
ACTIVE_ASSIGNMENT_EXECUTIONS.delete(executionToken);
activeExecution.resolveSettled();
activeExecution.resolveResumeAfterAbort();
throw error;
}
ACTIVE_ASSIGNMENT_TOKENS.add(executionToken);
const stopHeartbeat = this.startAssignmentHeartbeat(
run.runId,
assignmentId,
executionToken,
);
const markRunning = async () => {
run = await this.transition(run, (next) => {
const assignment = next.assignments.find(
(item) => item.id === assignmentId,
);
if (!assignment) {
throw new Error(`Assignment ${assignmentId} is missing`);
}
assignment.status = "running";
});
};
if (!this.options.executor.reservesCapacity) await markRunning();
try {
let output: unknown;
let metadata: AssignmentExecutionMetadata | undefined;
let transcriptArtifact: WorkflowAssignment["transcriptArtifact"];
try {
const evidence = await this.assignmentEvidence(run, role);
const executionResult = await this.options.executor.run({
runId: run.runId,
role,
request: planned
? `${run.request}\n\nAssignment instructions: ${planned.selection.rationale}\nOwned paths: ${planned.ownedPaths.join(", ")}`
: run.request,
profile: selected.profile,
facts: structuredClone(run.facts),
decisions: structuredClone(run.decisions),
...(role === "design"
? { plannerCandidates: this.plannerCandidates(run) }
: {}),
cwd: run.workspace.root ?? run.workspace.sourceRoot,
signal: abortController.signal,
mutates: planned?.mutates ?? false,
ownedPaths: planned?.ownedPaths ?? [],
captureGitState: role === "review",
runConcurrency: run.effectiveConfig.concurrency.default,
...evidence,
...(this.options.executor.reservesCapacity
? { onReserved: markRunning }
: {}),
});
metadata = isAssignmentExecutionResult(executionResult)
? executionResult.metadata
: undefined;
output = isAssignmentExecutionResult(executionResult)
? executionResult.output
: executionResult;
transcriptArtifact = metadata
? await this.options.store.saveAssignmentTranscript(
run.runId,
assignmentId,
metadata.transcript,
)
: undefined;
activeExecution.metadata = metadata;
activeExecution.transcriptArtifact = transcriptArtifact;
} catch (error) {
metadata = isAssignmentExecutionError(error)
? error.assignmentMetadata
: undefined;
transcriptArtifact = metadata
? await this.options.store.saveAssignmentTranscript(
run.runId,
assignmentId,
metadata.transcript,
)
: undefined;
activeExecution.metadata = metadata;
activeExecution.transcriptArtifact = transcriptArtifact;
await stopHeartbeat();
await this.assertAssignmentLease(
run.runId,
assignmentId,
executionToken,
);
if (abortController.signal.aborted) {
activeExecution.resolveSettled();
await activeExecution.resumeAfterAbort;
run = await this.options.store.loadRun(run.runId);
return { run, output: undefined };
}
run = await this.transition(run, (next) => {
const assignment = next.assignments.find(
(item) => item.id === assignmentId,
);
if (assignment) {
assignment.status = "failed";
assignment.error =
error instanceof Error ? error.message : String(error);
applyExecutionMetadata(assignment, metadata, transcriptArtifact);
}
const targetStage = stage(next, role);
targetStage.status = "failed";
targetStage.finishedAt = this.now();
const message = error instanceof Error ? error.message : String(error);
if (planned?.mutates || role === "review") {
next.status = "waiting";
next.error = {
code: "mutation-recovery-required",
message,
stageId: targetStage.id,
assignmentId,
};
if (!activeGate(next)) {
openGate(next, {
id: this.createId(),
kind: "recovery",
status: "open",
revision: next.revision + 1,
openedAt: this.now(),
payload: {
interruptedAssignmentIds: [assignmentId],
potentiallyMutating: true,
},
});
}
} else {
next.status = "failed";
next.error = {
code: "assignment-failed",
message,
stageId: targetStage.id,
assignmentId,
};
}
});
return { run, output: undefined };
}
await stopHeartbeat();
await this.assertAssignmentLease(run.runId, assignmentId, executionToken);
run = await this.transition(run, (next) => {
const assignment = next.assignments.find(
(item) => item.id === assignmentId,
);
if (!assignment)
throw new Error(`Assignment ${assignmentId} is missing`);
assignment.status = "succeeded";
assignment.result = output;
applyExecutionMetadata(assignment, metadata, transcriptArtifact);
});
const assignment = run.assignments.find(
(item) => item.id === assignmentId,
);
if (!assignment) throw new Error(`Assignment ${assignmentId} is missing`);
await this.options.afterAssignmentPersist?.(
structuredClone(assignment),
run,
);
return { run, output };
} finally {
abortController.abort();
await stopHeartbeat().catch(() => {});
ACTIVE_ASSIGNMENT_TOKENS.delete(executionToken);
ACTIVE_ASSIGNMENT_EXECUTIONS.delete(executionToken);
await this.options.store
.releaseAssignmentLease(run.runId, assignmentId, executionToken)
.catch(() => {});
activeExecution.resolveSettled();
}
}
private startAssignmentHeartbeat(
runId: string,
assignmentId: string,
executionToken: string,
) {
let stopped = false;
let heartbeatError: unknown;
let pending = Promise.resolve();
const timer = setInterval(() => {
pending = pending.then(async () => {
const owned = await this.options.store.heartbeatAssignmentLease(
runId,
assignmentId,
executionToken,
this.now(),
);
if (!owned)
throw new Error(`Assignment ${assignmentId} lost its lease`);
});
pending.catch((error) => {
heartbeatError ??= error;
});
}, this.assignmentHeartbeatMs);
timer.unref?.();
return async () => {
if (!stopped) {
stopped = true;
clearInterval(timer);
}
await pending.catch(() => {});
if (heartbeatError) throw heartbeatError;
};
}
private async assertAssignmentLease(
runId: string,
assignmentId: string,
executionToken: string,
) {
const lease = await this.options.store.loadAssignmentLease(
runId,
assignmentId,
);
if (!lease || lease.token !== executionToken) {
throw new Error(`Assignment ${assignmentId} lost its execution lease`);
}
}
private async decide(run: WorkflowRun, command: DecideWorkflowCommand) {
const duplicate = this.commandReceipt(run, command);
if (duplicate) {
if (duplicate.status === "completed" && duplicate.result) {
return duplicate.result as WorkflowView;
}
return this.resumePendingDecision(run, duplicate);
}
if (command.expectedRevision !== run.revision) {
throw new Error(
`Stale workflow revision ${command.expectedRevision}; current revision is ${run.revision}`,
);
}
if (run.status !== "waiting") {
throw new Error(`Workflow Run must be waiting to decide a Gate`);
}
const openGates = run.gates.filter(
(candidate) => candidate.status === "open",
);
if (openGates.length !== 1 || openGates[0]?.id !== command.gateId) {
throw new Error("Decision must target the single active Gate");
}
const gate = openGates[0];
if (gate.revision > run.revision)
throw new Error("Gate revision is invalid");
legalDecision(gate, command.decision);
if (
gate.kind === "questions" &&
stage(run, "discover").status !== "waiting"
) {
throw new Error("Questions Gate requires a waiting Discover stage");
}
if (
gate.kind === "plan-approval" &&
stage(run, "design").status !== "waiting"
) {
throw new Error("Plan Approval Gate requires a waiting Design stage");
}
if (gate.kind === "implementation-context") {
const context = run.implementationContext;
if (!context || context.gateId !== gate.id) {
throw new Error("Implementation Context state does not match the Gate");
}
if (!run.planArtifact || context.planRevision !== run.planArtifact.revision) {
throw new Error("Implementation Context plan revision is stale");
}
if (
run.assignments.some(
(assignment) =>
assignment.status === "running" ||
(assignment.status === "queued" && Boolean(assignment.executionToken)),
)
) {
throw new Error("Implementation Context cannot change while an Assignment is active");
}
}
validateDecisionPayload(gate, command.decision);
if (
command.decision.kind === "recover" &&
command.decision.resolution === "retry" &&
run.workspace.state !== "ready" &&
run.lease &&
run.lease.processId !== this.processId &&
this.isProcessAlive(run.lease.processId)
) {
throw new Error("Workspace creation is still owned by a live process");
}
if (
command.decision.kind === "approve-plan" &&
command.decision.acceptedUiImpact !== run.uiImpact
) {
throw new Error("Approved UI impact does not match the current plan");
}
if (command.decision.kind === "report-context-preparation") {
const context = run.implementationContext;
if (
!context ||
context.gateId !== command.gateId ||
context.preparationId !== command.decision.preparationId ||
context.planRevision !== command.decision.planRevision ||
context.strategy !== "smart-compact" ||
context.status !== "smart-compact-pending"
) {
throw new Error("Context preparation report does not match the pending intent");
}
if (command.decision.outcome === "unavailable") {
if (!command.activeToolNames) {
throw new Error("activeToolNames are required for an unavailable report");
}
if (command.activeToolNames.includes("smart_compact")) {
throw new Error("smart_compact is active and cannot be reported unavailable");
}
}
}
const approvedPlanArtifact =
command.decision.kind === "approve-plan"
? await this.saveApprovedPlan(run, command.decision)
: undefined;
const acceptedPrototypePlan =
command.decision.kind === "accept-prototype"
? await this.saveAcceptedPrototypePlan(run, command.decision)
: undefined;
const cleanupAssignment =
command.decision.kind === "accept-prototype"
? this.prototypeCleanupAssignment(run)
: undefined;
const refinedAssignments =
command.decision.kind === "refine-prototypes"
? this.refinementAssignments(run)
: undefined;
const commandFingerprint = fingerprint(command);
run = await this.transition(run, (next) => {
const nextGate = next.gates.find(
(candidate) => candidate.id === command.gateId,
);
if (!nextGate || nextGate.status !== "open")
throw new Error("Gate is not open");
const keepsGateOpen =
(command.decision.kind === "recover" &&
command.decision.resolution === "inspect") ||
(command.decision.kind === "select-implementation-context" &&
command.decision.strategy !== "current-session") ||
(command.decision.kind === "report-context-preparation" &&
(command.decision.outcome === "unavailable" ||
command.decision.outcome === "failed"));
if (!keepsGateOpen) {
nextGate.status =
command.decision.kind === "revise-plan" ||
command.decision.kind === "refine-prototypes"
? "rejected"
: "satisfied";
}
next.decisions.push({
gateId: command.gateId,
revision: next.revision + 1,
idempotencyKey: command.idempotencyKey,
decidedAt: this.now(),
value: structuredClone(command.decision),
});
next.commandReceipts.push({
key: command.idempotencyKey,
action: "decide",
fingerprint: commandFingerprint,
status: "pending",
acceptedRevision: next.revision + 1,
continuation: decisionContinuation(gate.kind, command.decision),
});
next.status = "running";
if (gate.kind === "questions") {
const discover = stage(next, "discover");
discover.status = "completed";
discover.finishedAt = this.now();
} else if (
gate.kind === "plan-approval" &&
command.decision.kind === "approve-plan"
) {
const design = stage(next, "design");
design.status = "completed";
design.finishedAt = this.now();
if (!approvedPlanArtifact) {
throw new Error("Approved plan artifact is missing");
}
next.planArtifact = approvedPlanArtifact;
if (next.uiImpact === "non-trivial") {
const prototype = stage(next, "prototype");
prototype.status = "pending";
prototype.startedAt = undefined;
prototype.finishedAt = undefined;
next.status = "running";
} else {
this.openImplementationContext(next);
}
} else if (
gate.kind === "prototype-acceptance" &&
command.decision.kind === "accept-prototype"
) {
if (!acceptedPrototypePlan || !cleanupAssignment) {
throw new Error("Accepted prototype continuation is incomplete");
}
next.planArtifact = acceptedPrototypePlan;
next.assignments.push(structuredClone(cleanupAssignment));
const prototype = stage(next, "prototype");
prototype.status = "completed";
prototype.finishedAt = this.now();
this.openImplementationContext(next);
} else if (
gate.kind === "prototype-acceptance" &&
command.decision.kind === "refine-prototypes"
) {
if (!refinedAssignments) {
throw new Error("Prototype refinement Assignments are missing");
}
next.assignments.push(...structuredClone(refinedAssignments));
const prototype = stage(next, "prototype");
prototype.status = "pending";
prototype.startedAt = undefined;
prototype.finishedAt = undefined;
next.status = "running";
} else if (
gate.kind === "implementation-context" &&
command.decision.kind === "select-implementation-context"
) {
const context = next.implementationContext;
if (!context) throw new Error("Implementation Context state is missing");
context.strategy = command.decision.strategy;
context.runRevision = next.revision + 1;
context.activeParentSessionId = this.options.parentSessionId;
context.sourceParentSessionId = this.options.parentSessionId;
context.error = undefined;
context.outcome = undefined;
if (command.decision.strategy === "fresh-session") {
context.status = "handoff-pending";
context.method = "fresh-session";
next.status = "waiting";
} else if (command.decision.strategy === "smart-compact") {
context.status = "smart-compact-pending";
context.method = "smart_compact";
next.status = "waiting";
} else {
context.status = "ready";
context.method = "unchanged";
context.outcome = "no-op";
next.status = "running";
}
} else if (
gate.kind === "implementation-context" &&
command.decision.kind === "report-context-preparation"
) {
const context = next.implementationContext;
if (!context) throw new Error("Implementation Context state is missing");
context.runRevision = next.revision + 1;
context.outcome = command.decision.outcome;
if (command.decision.outcome === "unavailable") {
context.status = "normal-compact-pending";
next.status = "waiting";
} else if (command.decision.outcome === "failed") {
context.status = "failed";
context.error = command.decision.error;
next.status = "waiting";
} else {
context.status = "ready";
context.error = undefined;
next.status = "running";
}
}
});
const receipt = run.commandReceipts.find(
(candidate) => candidate.key === command.idempotencyKey,
);
if (!receipt) throw new Error("Persisted command receipt is missing");
return this.resumePendingDecision(run, receipt);
}
private profileForAssignment(run: WorkflowRun, assignment: WorkflowAssignment) {
const profile = run.pinnedProfiles.find(
(candidate) =>
candidate.name === assignment.profileName &&
candidate.digest === assignment.profileDigest,
);
if (!profile) {
throw new Error(`Pinned profile ${assignment.profileName} is missing`);
}
return { profile, selection: assignment.selection };
}
private queuedAssignment(run: WorkflowRun, role: WorkflowAssignment["role"]) {
return run.assignments.find(
(assignment) => assignment.role === role && assignment.status === "queued",
);
}
private succeededAssignment(
run: WorkflowRun,
role: WorkflowAssignment["role"],
) {
return [...run.assignments]
.reverse()
.find(
(assignment) =>
assignment.role === role &&
assignment.status === "succeeded" &&
assignment.result !== undefined,
);
}
private async runPlannedAssignment(
run: WorkflowRun,
assignment: WorkflowAssignment,
) {
return this.runAssignment(
run,
assignment.role,
this.profileForAssignment(run, assignment),
assignment,
);
}
private async markStageCompleted(
run: WorkflowRun,
kind: WorkflowStage["kind"],
output?: unknown,
) {
return this.transition(run, (next) => {
const target = stage(next, kind);
target.status = "completed";
target.finishedAt = this.now();
if (output !== undefined) target.output = output;
});
}
private async openLifecycleRecovery(
run: WorkflowRun,
message: string,
assignmentIds: string[] = [],
) {
if (activeGate(run)?.kind === "recovery") return run;
return this.transition(run, (next) => {
next.status = "waiting";
next.error = { code: "lifecycle-recovery-required", message };
openGate(next, {
id: this.createId(),
kind: "recovery",
status: "open",
revision: next.revision + 1,
openedAt: this.now(),
payload: {
interruptedAssignmentIds: assignmentIds,
message,
},
});
});
}
private async runVerificationCommands(run: WorkflowRun) {
const evidence = run.verificationEvidence;
if (!evidence) throw new Error("Verification evidence is missing");
if (!this.options.hostCommands && evidence.commands.length > 0) {
return this.failRun(
run,
"host-command-executor-missing",
new Error("Verification commands require the host command executor"),
);
}
for (const current of evidence.commands) {
const command = run.verificationEvidence?.commands.find(
(candidate) => candidate.id === current.id,
);
if (
!command ||
command.status === "succeeded" ||
command.status === "superseded"
) {
continue;
}
if (command.status === "running" || command.status === "interrupted") {
return this.openLifecycleRecovery(
run,
`Verification command ${command.id} was interrupted and will not be replayed automatically`,
);
}
run = await this.transition(run, (next) => {
const pending = next.verificationEvidence?.commands.find(
(candidate) => candidate.id === command.id,
);
if (!pending || (pending.status !== "pending" && pending.status !== "failed")) {
throw new Error(`Verification command ${command.id} is not runnable`);
}
pending.status = "running";
});
let safetyFailure: WorkspaceSafetyError | undefined;
try {
const executed = await this.options.hostCommands!.run({
worktreeRoot: run.workspace.root ?? run.workspace.sourceRoot,
ownedPaths: command.ownedPaths,
executable: command.executable,
args: command.args,
});
run = await this.transition(run, (next) => {
const persisted = next.verificationEvidence?.commands.find(
(candidate) => candidate.id === command.id,
);
if (!persisted) throw new Error(`Verification command ${command.id} is missing`);
persisted.exitCode = executed.result.code;
persisted.stdout = executed.result.stdout.slice(0, 32 * 1024);
persisted.stderr = executed.result.stderr.slice(0, 32 * 1024);
persisted.status = executed.result.code === 0 ? "succeeded" : "failed";
});
} catch (error) {
if (error instanceof WorkspaceSafetyError) safetyFailure = error;
run = await this.transition(run, (next) => {
const persisted = next.verificationEvidence?.commands.find(
(candidate) => candidate.id === command.id,
);
if (!persisted) throw new Error(`Verification command ${command.id} is missing`);
persisted.status = "failed";
persisted.error = error instanceof Error ? error.message : String(error);
});
}
if (safetyFailure) {
return this.openLifecycleRecovery(run, safetyFailure.message);
}
const result = run.verificationEvidence?.commands.find(
(candidate) => candidate.id === command.id,
);
if (result?.status !== "succeeded") return run;
}
return run;
}
private reviewSelection(run: WorkflowRun) {
const evaluations = eligibleProfiles(
run.pinnedProfiles,
{ ...run.facts, stage: "review" },
"review",
);
const candidate = evaluations.find(
(evaluation) =>
evaluation.eligible &&
!run.mutatorProfileNames.includes(evaluation.profile.name),
);
if (!candidate) {
throw new Error("No independent review Agent Profile remains eligible");
}
return {
profile: candidate.profile,
selection: {
eligibleProfiles: evaluations.map((evaluation) => ({
profileName: evaluation.profile.name,
profileDigest: evaluation.profile.digest,
eligible:
evaluation.eligible &&
!run.mutatorProfileNames.includes(evaluation.profile.name),
reasons: [
...evaluation.reasons,
...(run.mutatorProfileNames.includes(evaluation.profile.name)
? ["profile mutated the reviewed diff"]
: []),
],
specificity: evaluation.specificity,
})),
selectedProfileName: candidate.profile.name,
rationale: "Selected an eligible reviewer outside the cumulative mutator set.",
},
};
}
private async queueReviewAssignment(run: WorkflowRun) {
const selected = this.reviewSelection(run);
return this.transition(run, (next) => {
next.assignments.push({
id: this.createId(),
stageId: stage(next, "review").id,
role: "review",
profileName: selected.profile.name,
profileDigest: selected.profile.digest,
selection: selected.selection,
status: "queued",
mutates: false,
ownedPaths: [],
});
const review = stage(next, "review");
review.status = "pending";
review.startedAt = undefined;
review.finishedAt = undefined;
});
}
private async remediate(run: WorkflowRun, reviewerProfileName?: string) {
const persistedRemediation = this.queuedAssignment(run, "remediate")
? undefined
: stage(run, "remediate").status !== "completed"
? this.succeededAssignment(run, "remediate")
: undefined;
if (persistedRemediation) {
run = await this.markStageCompleted(
run,
"remediate",
implementationOutput(persistedRemediation.result),
);
return this.finishRemediation(run);
}
if (!reviewerProfileName) {
run = await this.transition(run, (next) => {
next.verificationCycles++;
});
if (run.verificationCycles >= run.effectiveConfig.maxReviewCycles) {
return this.openLifecycleRecovery(
run,
`Verification did not pass after ${run.verificationCycles} remediation cycles`,
);
}
} else if (run.reviewCycles >= run.effectiveConfig.maxReviewCycles) {
return this.openLifecycleRecovery(
run,
`Review did not approve after ${run.reviewCycles} cycles`,
);
}
const evaluations = eligibleProfiles(
run.pinnedProfiles,
{ ...run.facts, stage: "implement" },
"implement",
);
const selected = evaluations.find(
(evaluation) =>
evaluation.eligible && evaluation.profile.name !== reviewerProfileName,
);
if (!selected) {
return this.failRun(
run,
"remediation-profile-unavailable",
new Error("No eligible remediation profile remains"),
);
}
const ownedPaths = [
...new Set(
run.assignments
.filter((assignment) => assignment.mutates)
.flatMap((assignment) => assignment.ownedPaths),
),
];
run = await this.transition(run, (next) => {
const remediate = stage(next, "remediate");
remediate.status = "pending";
remediate.startedAt = undefined;
remediate.finishedAt = undefined;
next.assignments.push({
id: this.createId(),
stageId: remediate.id,
role: "remediate",
profileName: selected.profile.name,
profileDigest: selected.profile.digest,
selection: {
eligibleProfiles: evaluations.map((evaluation) => ({
profileName: evaluation.profile.name,
profileDigest: evaluation.profile.digest,
eligible:
evaluation.eligible &&
evaluation.profile.name !== reviewerProfileName,
reasons: [
...evaluation.reasons,
...(evaluation.profile.name === reviewerProfileName
? ["reviewer cannot remediate its own findings"]
: []),
],
specificity: evaluation.specificity,
})),
selectedProfileName: selected.profile.name,
rationale: "Selected an eligible implementer distinct from the reviewer.",
},
status: "queued",
mutates: true,
ownedPaths,
});
});
const assignment = this.queuedAssignment(run, "remediate");
if (!assignment) throw new Error("Remediation Assignment is missing");
const execution = await this.runPlannedAssignment(run, assignment);
run = execution.run;
if (!execution.output || run.status !== "running") return run;
const output = implementationOutput(execution.output);
run = await this.markStageCompleted(run, "remediate", output);
return this.finishRemediation(run);
}
private async finishRemediation(run: WorkflowRun) {
run = await this.transition(run, (next) => {
if (!next.verificationEvidence) throw new Error("Verification evidence is missing");
for (const command of next.verificationEvidence.commands) {
if (command.status === "superseded") continue;
command.status = "pending";
command.exitCode = undefined;
command.stdout = undefined;
command.stderr = undefined;
command.error = undefined;
}
next.verificationEvidence.focusedBehavioralTests = false;
next.verificationEvidence.verifierSucceeded = false;
next.reviewVerdict = undefined;
const verifier = selectionEvidence(
next.pinnedProfiles,
{ ...next.facts, stage: "verify" },
"verify",
);
next.assignments.push({
id: this.createId(),
stageId: stage(next, "verify").id,
role: "verify",
profileName: verifier.profile.name,
profileDigest: verifier.profile.digest,
selection: verifier.selection,
status: "queued",
mutates: false,
ownedPaths: [],
});
const verify = stage(next, "verify");
verify.status = "pending";
verify.startedAt = undefined;
verify.finishedAt = undefined;
});
if (!this.queuedAssignment(run, "review")) {
run = await this.queueReviewAssignment(run);
}
return this.runImplementationPipeline(run);
}
private async runPrototypeStage(run: WorkflowRun): Promise<WorkflowRun> {
if (run.uiImpact !== "non-trivial" || run.implementationContext) return run;
while (true) {
const assignment = run.assignments.find(
(candidate) =>
candidate.role === "prototype" &&
!run.prototypeCandidates.some(
(prototype) => prototype.assignmentId === candidate.id,
) &&
(candidate.status === "queued" || candidate.status === "succeeded"),
);
if (!assignment) break;
let rawOutput = assignment.result;
if (assignment.status === "queued") {
const execution = await this.runPlannedAssignment(run, assignment);
run = execution.run;
if (!execution.output || run.status !== "running") return run;
rawOutput = execution.output;
}
const output = prototypeOutput(rawOutput);
if (!this.options.prototypeValidator) {
return this.openLifecycleRecovery(
run,
"Prototype validation is unavailable",
[assignment.id],
);
}
let hostValidation: { artifactFiles: string[]; visualDigest: string };
try {
hostValidation = await this.options.prototypeValidator.validateCandidate({
worktreeRoot: run.workspace.root ?? run.workspace.sourceRoot,
assignment,
output,
});
} catch (error) {
return this.openLifecycleRecovery(
run,
`Prototype ${assignment.id} failed host validation: ${error instanceof Error ? error.message : String(error)}`,
[assignment.id],
);
}
const candidatePath = output.path.replaceAll("\\", "/").replace(/^\.\//, "");
if (
path.isAbsolute(output.path) ||
candidatePath.split("/").includes("..") ||
!assignment.ownedPaths.some((ownedPath) => {
const root = ownedPath.replaceAll("\\", "/").replace(/\/$/, "");
return candidatePath === root || candidatePath.startsWith(`${root}/`);
})
) {
return this.openLifecycleRecovery(
run,
`Prototype ${assignment.id} reported a path outside its ownership`,
[assignment.id],
);
}
const revision = run.revision + 1;
const candidate = {
id: assignment.id,
revision,
assignmentId: assignment.id,
profileName: assignment.profileName,
status: "succeeded" as const,
path: candidatePath,
route: output.route,
structuralRationale: output.structuralRationale,
responsiveChecks: output.responsiveChecks,
browserEvidence: output.browserEvidence,
launchInstructions: output.launchInstructions,
visualDigest: hostValidation.visualDigest,
artifactPath: "",
retainedArtifacts: [] as string[],
};
const artifact = await this.options.store.savePrototypeArtifact(
run.runId,
candidate.id,
candidate,
run.workspace.root ?? run.workspace.sourceRoot,
hostValidation.artifactFiles,
);
candidate.artifactPath = artifact.path;
candidate.retainedArtifacts = artifact.retainedFiles;
run = await this.transition(run, (next) => {
next.prototypeCandidates.push(structuredClone(candidate));
});
}
const candidates = run.prototypeCandidates.slice(-3);
if (candidates.length !== 3) {
return this.openLifecycleRecovery(
run,
"Non-trivial UI work requires three successful Prototype Candidates",
);
}
for (const key of [
"path",
"route",
"structuralRationale",
"visualDigest",
] as const) {
if (new Set(candidates.map((candidate) => candidate[key])).size !== 3) {
return this.openLifecycleRecovery(
run,
`Prototype Candidates must have distinct ${key} values`,
candidates.map((candidate) => candidate.assignmentId),
);
}
}
return this.transition(run, (next) => {
const prototype = stage(next, "prototype");
prototype.status = "waiting";
prototype.output = structuredClone(candidates);
next.status = "waiting";
const gate: WorkflowGate = {
id: this.createId(),
kind: "prototype-acceptance",
status: "open",
revision: next.revision + 1,
openedAt: this.now(),
payload: { candidates: structuredClone(candidates) },
};
openGate(next, gate);
});
}
private async runPrototypeCleanup(run: WorkflowRun) {
if (run.prototypeCleanupCompleted) return run;
const accepted = run.decisions.some(
(decision) => decision.value.kind === "accept-prototype",
);
if (!accepted) return run;
const cleanup = [...run.assignments]
.reverse()
.find(
(assignment) =>
assignment.role === "remediate" &&
assignment.selection.rationale.startsWith("Remove prototype-only"),
);
if (!cleanup) {
return this.openLifecycleRecovery(run, "Prototype cleanup Assignment is missing");
}
let rawOutput = cleanup.result;
if (cleanup.status === "queued") {
const execution = await this.runPlannedAssignment(run, cleanup);
run = execution.run;
if (!execution.output || run.status !== "running") return run;
rawOutput = execution.output;
} else if (cleanup.status !== "succeeded") {
return this.openLifecycleRecovery(
run,
"Prototype cleanup did not complete safely",
[cleanup.id],
);
}
const output = implementationOutput(rawOutput);
if (!this.options.prototypeValidator) {
return this.openLifecycleRecovery(run, "Prototype cleanup validation is unavailable");
}
try {
await this.options.prototypeValidator.validateCleanup({
worktreeRoot: run.workspace.root ?? run.workspace.sourceRoot,
candidates: run.prototypeCandidates,
});
} catch (error) {
return this.openLifecycleRecovery(
run,
`Prototype cleanup failed host validation: ${error instanceof Error ? error.message : String(error)}`,
[cleanup.id],
);
}
run = await this.markStageCompleted(run, "remediate", output);
return this.transition(run, (next) => {
next.prototypeCleanupCompleted = true;
});
}
private async runImplementationPipeline(run: WorkflowRun): Promise<WorkflowRun> {
if (run.implementationContext?.status !== "ready") return run;
const implementStage = stage(run, "implement");
if (implementStage.status !== "completed") {
const assignment = this.queuedAssignment(run, "implement");
const persisted = assignment
? undefined
: this.succeededAssignment(run, "implement");
if (!assignment && !persisted) {
return this.openLifecycleRecovery(run, "Implementation Assignment is missing");
}
if (persisted) {
run = await this.markStageCompleted(
run,
"implement",
implementationOutput(persisted.result),
);
} else if (assignment) {
const execution = await this.runPlannedAssignment(run, assignment);
run = execution.run;
if (!execution.output || run.status !== "running") return run;
const output = implementationOutput(execution.output);
run = await this.markStageCompleted(run, "implement", output);
}
}
run = await this.runPrototypeCleanup(run);
if (run.status !== "running") return run;
run = await this.runVerificationCommands(run);
if (run.status !== "running") return run;
if (
run.verificationEvidence?.commands.some(
(command) =>
command.status !== "succeeded" && command.status !== "superseded",
)
) {
return this.remediate(run);
}
const verifyAssignment = this.queuedAssignment(run, "verify");
const persistedVerification = verifyAssignment
? undefined
: stage(run, "verify").status !== "completed"
? this.succeededAssignment(run, "verify")
: undefined;
if (verifyAssignment || persistedVerification) {
let output: VerificationOutput;
if (persistedVerification) {
output = verificationOutput(persistedVerification.result);
} else {
const execution = await this.runPlannedAssignment(run, verifyAssignment!);
run = execution.run;
if (!execution.output || run.status !== "running") return run;
output = verificationOutput(execution.output);
}
const focusedCommandPassed =
run.verificationEvidence?.commands.some(
(command) => command.kind === "test" && command.status === "succeeded",
) ?? false;
run = await this.transition(run, (next) => {
if (!next.verificationEvidence) throw new Error("Verification evidence is missing");
next.verificationEvidence.focusedBehavioralTests =
output.focusedBehavioralTests && focusedCommandPassed;
next.verificationEvidence.verifierSucceeded = output.succeeded;
const verify = stage(next, "verify");
verify.output = output;
verify.status = output.succeeded ? "completed" : "failed";
verify.finishedAt = this.now();
});
if (
!output.succeeded ||
!output.focusedBehavioralTests ||
!focusedCommandPassed
) {
return this.remediate(run);
}
}
if (
!run.verificationEvidence?.verifierSucceeded ||
!run.verificationEvidence.focusedBehavioralTests
) {
return this.openLifecycleRecovery(
run,
"Completion requires successful verification and focused behavioral tests",
);
}
run = await this.runWorkflowScript(run, "before-review");
if (run.status === "failed") return run;
let reviewAssignment = this.queuedAssignment(run, "review");
let persistedReview = reviewAssignment
? undefined
: stage(run, "review").status !== "completed"
? this.succeededAssignment(run, "review")
: undefined;
if (!reviewAssignment && !persistedReview) {
run = await this.queueReviewAssignment(run);
reviewAssignment = this.queuedAssignment(run, "review");
persistedReview = undefined;
}
if (!reviewAssignment && !persistedReview) {
throw new Error("Review Assignment is missing");
}
const reviewer = reviewAssignment ?? persistedReview!;
if (run.mutatorProfileNames.includes(reviewer.profileName)) {
return this.failRun(
run,
"independent-reviewer-unavailable",
new Error("Review profile mutated the reviewed diff"),
);
}
let verdict: ReviewOutput;
if (persistedReview) {
verdict = reviewOutput(persistedReview.result);
} else {
const execution = await this.runAssignment(
run,
"review",
this.profileForAssignment(run, reviewer),
reviewer,
);
run = execution.run;
if (!execution.output || run.status !== "running") return run;
verdict = reviewOutput(execution.output);
}
run = await this.transition(run, (next) => {
next.reviewVerdict = verdict;
next.reviewCycles++;
const review = stage(next, "review");
review.output = verdict;
review.finishedAt = this.now();
review.status =
verdict.specification.verdict === "approved" &&
verdict.standards.verdict === "approved"
? "completed"
: "failed";
});
if (
verdict.specification.verdict !== "approved" ||
verdict.standards.verdict !== "approved"
) {
return this.remediate(run, reviewer.profileName);
}
return this.transition(run, (next) => {
stage(next, "complete").status = "completed";
stage(next, "complete").startedAt ??= this.now();
stage(next, "complete").finishedAt = this.now();
next.status = "completed";
next.error = undefined;
});
}
private assertContextCommand(
run: WorkflowRun,
command: ContextCommandBase,
) {
if (command.expectedRevision !== run.revision) {
throw new Error(
`Stale workflow revision ${command.expectedRevision}; current revision is ${run.revision}`,
);
}
if (run.status !== "waiting") {
throw new Error("Implementation Context transition requires a waiting Workflow Run");
}
const context = run.implementationContext;
const gate = activeGate(run);
if (
!context ||
!gate ||
gate.kind !== "implementation-context" ||
gate.id !== command.gateId ||
context.gateId !== command.gateId ||
context.preparationId !== command.preparationId ||
context.planRevision !== command.planRevision ||
context.runRevision !== command.contextRevision
) {
throw new Error("Implementation Context command is stale or mismatched");
}
if (!run.planArtifact || run.planArtifact.revision !== command.planRevision) {
throw new Error("Implementation Context plan revision is stale");
}
if (
run.assignments.some(
(assignment) =>
assignment.status === "running" ||
(assignment.status === "queued" && Boolean(assignment.executionToken)),
)
) {
throw new Error("Implementation Context cannot change while an Assignment is active");
}
return { context, gate };
}
private async continueContext(
run: WorkflowRun,
command: ContinueContextCommand,
) {
const { context } = this.assertContextCommand(run, command);
if (context.status === "handoff-pending" && context.strategy === "fresh-session") {
if (!command.sourceSessionFile) {
throw new Error("fresh-session continuation requires sourceSessionFile");
}
run = await this.transition(run, (next) => {
const state = next.implementationContext;
if (!state) throw new Error("Implementation Context state is missing");
state.sourceSessionFile = command.sourceSessionFile;
state.intentCreatedAt = this.now();
state.intentExpiresAt = this.now() + 5 * 60_000;
state.runRevision = next.revision + 1;
});
return view(run);
}
if (
context.status === "normal-compact-pending" &&
context.strategy === "smart-compact" &&
context.outcome === "unavailable"
) {
run = await this.transition(run, (next) => {
const state = next.implementationContext;
if (!state) throw new Error("Implementation Context state is missing");
state.status = "normal-compact-running";
state.method = "normal-compact";
state.error = undefined;
state.runRevision = next.revision + 1;
});
return view(run);
}
throw new Error("Workflow Run has no continuable context intent");
}
private async claimContextHandoff(
run: WorkflowRun,
command: ClaimContextHandoffCommand,
) {
const { context } = this.assertContextCommand(run, command);
if (
context.status !== "handoff-pending" ||
context.strategy !== "fresh-session" ||
context.sourceSessionFile !== command.sourceSessionFile ||
context.sourceParentSessionId !== this.options.parentSessionId
) {
throw new Error("Fresh-session handoff claim does not match the pending intent");
}
run = await this.transition(run, (next) => {
const state = next.implementationContext;
if (!state) throw new Error("Implementation Context state is missing");
state.targetSessionFile = command.targetSessionFile;
state.claimedAt = this.now();
state.status = "claimed-target-awaiting-withSession";
state.runRevision = next.revision + 1;
});
return view(run);
}
private async consumeContextHandoff(
run: WorkflowRun,
command: ConsumeContextHandoffCommand,
) {
const { context, gate } = this.assertContextCommand(run, command);
if (
context.status !== "claimed-target-awaiting-withSession" ||
context.strategy !== "fresh-session" ||
context.targetSessionFile !== command.targetSessionFile ||
context.sourceSessionFile !== command.previousSessionFile ||
context.activeParentSessionId === this.options.parentSessionId ||
(context.intentExpiresAt !== undefined && context.intentExpiresAt < this.now())
) {
throw new Error("Replacement session does not match the claimed handoff");
}
run = await this.transition(run, (next) => {
const state = next.implementationContext;
if (!state) throw new Error("Implementation Context state is missing");
state.activeParentSessionId = this.options.parentSessionId;
state.status = "ready";
state.method = "fresh-session";
state.outcome = "succeeded";
state.error = undefined;
state.runRevision = next.revision + 1;
next.parentSessionIds = [
...new Set([...next.parentSessionIds, this.options.parentSessionId]),
];
if (!next.lease) throw new Error("Workflow Run lease is missing");
next.lease.activeParentSessionId = this.options.parentSessionId;
const nextGate = next.gates.find((candidate) => candidate.id === gate.id);
if (!nextGate || nextGate.status !== "open") {
throw new Error("Implementation Context Gate is not open");
}
nextGate.status = "satisfied";
next.status = "running";
});
run = await this.runImplementationPipeline(run);
return view(run);
}
private async recoverContextIntent(
run: WorkflowRun,
command: RecoverContextIntentCommand,
) {
if (
command.reason !== "expired" &&
run.lease &&
run.lease.processId !== this.processId &&
this.isProcessAlive(run.lease.processId)
) {
throw new Error(
`Context handoff is owned by live process ${run.lease.processId}`,
);
}
const { context } = this.assertContextCommand(run, command);
const cancelledUnclaimed =
command.reason === "cancelled" &&
context.status === "handoff-pending" &&
Boolean(context.sourceSessionFile);
if (
context.status !== "claimed-target-awaiting-withSession" &&
!cancelledUnclaimed
) {
throw new Error("Workflow Run has no context intent to recover");
}
run = await this.transition(run, (next) => {
const state = next.implementationContext;
if (!state) throw new Error("Implementation Context state is missing");
state.status = "handoff-pending";
state.sourceSessionFile = undefined;
state.targetSessionFile = undefined;
state.claimedAt = undefined;
state.intentCreatedAt = undefined;
state.intentExpiresAt = undefined;
state.error =
command.reason === "cancelled"
? "Fresh-session handoff was cancelled before session replacement; retry when ready"
: `Fresh-session handoff was not consumed during ${command.reason}; resume the source session and retry`;
state.runRevision = next.revision + 1;
next.status = "waiting";
});
return view(run);
}
private async completeNormalCompaction(
run: WorkflowRun,
command: CompleteNormalCompactionCommand,
) {
const { context, gate } = this.assertContextCommand(run, command);
if (
context.status !== "normal-compact-running" ||
context.method !== "normal-compact"
) {
throw new Error("Normal compaction callback does not match the pending intent");
}
run = await this.transition(run, (next) => {
const state = next.implementationContext;
if (!state) throw new Error("Implementation Context state is missing");
state.outcome = command.outcome;
state.runRevision = next.revision + 1;
if (command.outcome === "failed") {
state.status = "failed";
state.error = command.error;
next.status = "waiting";
return;
}
state.status = "ready";
state.error = undefined;
const nextGate = next.gates.find((candidate) => candidate.id === gate.id);
if (!nextGate || nextGate.status !== "open") {
throw new Error("Implementation Context Gate is not open");
}
nextGate.status = "satisfied";
next.status = "running";
});
if (command.outcome !== "failed") {
run = await this.runImplementationPipeline(run);
}
return view(run);
}
private async abort(run: WorkflowRun, command: AbortWorkflowCommand) {
const duplicate = this.commandReceipt(run, command);
if (duplicate) {
if (duplicate.status === "completed" && duplicate.result) {
return duplicate.result as WorkflowView;
}
throw new Error("Abort command is pending recovery");
}
if (command.expectedRevision !== run.revision) {
throw new Error(
`Stale workflow revision ${command.expectedRevision}; current revision is ${run.revision}`,
);
}
if (
run.status === "completed" ||
run.status === "failed" ||
run.status === "aborted"
) {
throw new Error(`Cannot abort a ${run.status} Workflow Run`);
}
const commandFingerprint = fingerprint(command);
const activeExecutions = [...ACTIVE_ASSIGNMENT_EXECUTIONS.values()].filter(
(execution) => execution.runId === run.runId,
);
const localAssignmentIds = new Set(
activeExecutions.map((execution) => execution.assignmentId),
);
const externallyOwned = run.assignments.filter(
(assignment) =>
!localAssignmentIds.has(assignment.id) &&
Boolean(assignment.executionToken) &&
(assignment.status === "queued" || assignment.status === "running"),
);
for (const assignment of externallyOwned) {
const lease = await this.options.store.loadAssignmentLease(
run.runId,
assignment.id,
);
if (lease && this.isProcessAlive(lease.processId)) {
throw new Error(
`Cannot safely abort Assignment ${assignment.id} owned by live process ${lease.processId}; abort from the owning process or retry after it exits`,
);
}
}
const activeScript = ACTIVE_SCRIPT_EXECUTIONS.get(run.runId);
for (const execution of activeExecutions) {
execution.controller.abort(new Error("Workflow Run was aborted"));
}
activeScript?.controller.abort(new Error("Workflow Run was aborted"));
try {
await waitForAssignmentStops(
activeExecutions,
this.assignmentAbortTimeoutMs,
);
if (activeScript) {
const stopped = await Promise.race([
activeScript.settled.then(() => true),
new Promise<false>((resolve) => {
const timer = setTimeout(
() => resolve(false),
this.assignmentAbortTimeoutMs,
);
timer.unref?.();
}),
]);
if (!stopped) {
throw new Error(
"Timed out stopping active script; Workflow Run was not aborted",
);
}
}
} catch (error) {
for (const execution of activeExecutions) {
execution.resolveResumeAfterAbort();
}
throw error;
}
const latest =
activeExecutions.length > 0 || activeScript
? await this.options.store.loadRun(run.runId)
: run;
const next = structuredClone(latest);
next.revision++;
next.status = "aborted";
next.lease = undefined;
const interruptedIds = new Set([
...activeExecutions.map((execution) => execution.assignmentId),
...externallyOwned.map((assignment) => assignment.id),
]);
for (const assignment of next.assignments) {
if (!interruptedIds.has(assignment.id)) continue;
assignment.status = "interrupted";
assignment.error = "Workflow Run was aborted";
}
for (const execution of activeExecutions) {
const assignment = next.assignments.find(
(candidate) => candidate.id === execution.assignmentId,
);
if (!assignment) continue;
applyExecutionMetadata(
assignment,
execution.metadata,
execution.transcriptArtifact,
);
}
next.deliveries.push({
key: `terminal:${next.runId}:aborted:${next.revision}`,
state: "pending",
createdAt: this.now(),
});
next.reportArtifact = await this.options.store.saveReport(next);
const result = view(next);
next.commandReceipts.push({
key: command.idempotencyKey,
action: "abort",
fingerprint: commandFingerprint,
status: "completed",
acceptedRevision: next.revision,
result,
});
try {
await this.options.store.saveRun(next, latest.revision);
await this.options.store.publishResult(next).catch(() => {});
await Promise.resolve(this.options.afterRunPersist?.(next)).catch(() => {});
} finally {
for (const execution of activeExecutions) {
execution.resolveResumeAfterAbort();
}
}
return result;
}
private async resolveRecovery(run: WorkflowRun, decision: GateDecision) {
if (decision.kind !== "recover") return run;
if (decision.resolution === "abort") {
return this.transition(run, (next) => {
next.status = "aborted";
next.lease = undefined;
});
}
if (decision.resolution === "retry") {
if (run.mode === "feature" && run.workspace.state !== "ready") {
if (
run.lease &&
run.lease.processId !== this.processId &&
this.isProcessAlive(run.lease.processId)
) {
throw new Error(
"Workspace creation is still owned by a live process",
);
}
const creating = { ...run.workspace, state: "creating" as const };
let workspace = await this.options.workspace.reconcile(
creating,
run.lease?.worktreeIdentity,
);
if (workspace.state === "creating") {
try {
workspace = await this.options.workspace.create(
this.workspacePlanForRun(run),
);
} catch (error) {
workspace = {
...creating,
error: error instanceof Error ? error.message : String(error),
};
}
}
run = await this.persistWorkspaceOutcome(run, workspace);
if (run.workspace.state === "ready") return this.runDiscovery(run);
return run;
}
const interruptedMutating = run.assignments.filter(
(assignment) => assignment.status === "interrupted" && assignment.mutates,
);
const interruptedReadOnly = run.assignments.filter(
(assignment) => assignment.status === "interrupted" && !assignment.mutates,
);
const interruptedCommands =
run.verificationEvidence?.commands.filter(
(command) => command.status === "running" || command.status === "interrupted",
) ?? [];
run = await this.transition(run, (next) => {
for (const assignment of next.assignments) {
if (
interruptedReadOnly.some((candidate) => candidate.id === assignment.id)
) {
if (
assignment.selection.rationale.startsWith(
"Custom read-only script Assignment",
)
) {
assignment.status = "interrupted";
} else {
assignment.status = "queued";
}
assignment.executionToken = undefined;
}
}
if (
interruptedReadOnly.some((assignment) =>
assignment.selection.rationale.startsWith(
"Custom read-only script Assignment",
),
) &&
next.script
) {
next.script.status = "pending";
next.script.executed = false;
next.script.error = undefined;
}
if (next.verificationEvidence) {
for (const command of interruptedCommands) {
const persisted = next.verificationEvidence.commands.find(
(candidate) => candidate.id === command.id,
);
if (!persisted) continue;
persisted.status = "superseded";
next.verificationEvidence.commands.push({
...structuredClone(persisted),
id: this.createId(),
status: "pending",
exitCode: undefined,
stdout: undefined,
stderr: undefined,
error: undefined,
});
}
}
next.status = "running";
});
if (run.implementationContext?.status === "ready") {
if (interruptedMutating.length > 0) {
run = await this.transition(run, (next) => {
const implement = stage(next, "implement");
if (implement.status !== "completed") {
implement.status = "completed";
implement.finishedAt = this.now();
implement.output = {
recovery: "User requested a new remediation Assignment after interruption",
};
}
});
return this.remediate(run);
}
return this.runImplementationPipeline(run);
}
if (
interruptedMutating.length > 0 &&
interruptedMutating.every((assignment) => assignment.role === "prototype") &&
run.uiImpact === "non-trivial"
) {
run = await this.transition(run, (next) => {
for (const interrupted of interruptedMutating) {
const root = interrupted.ownedPaths[0]?.replace(
/[/\\](?:candidate|refinement-[^/\\]+-candidate)-\d+$/,
"",
);
if (!root) throw new Error("Interrupted Prototype ownership is invalid");
next.assignments.push({
...structuredClone(interrupted),
id: this.createId(),
status: "queued",
executionToken: undefined,
result: undefined,
error: undefined,
selection: {
...structuredClone(interrupted.selection),
rationale:
"Explicit Recovery retry creates a fresh Prototype Assignment after interruption.",
},
ownedPaths: [
path.join(root, `recovery-${next.revision + 1}-${this.createId()}`),
],
});
}
next.status = "running";
});
return this.runPrototypeStage(run);
}
if (interruptedMutating.length > 0) {
return this.openLifecycleRecovery(
run,
"Interrupted mutating Assignments remain preserved and cannot be replayed before implementation context is approved",
interruptedMutating.map((assignment) => assignment.id),
);
}
return this.resumePersistedContinuation(run);
}
return this.transition(run, (next) => {
next.status = "waiting";
});
}
private workspacePlanForRun(run: WorkflowRun): WorkspacePlan {
const { workspace } = run;
if (!workspace.root || !workspace.branch || !workspace.baseHead) {
throw new Error(
"Workflow Run is missing persisted workspace creation intent",
);
}
return {
sourceRoot: workspace.sourceRoot,
worktreeRoot: path.dirname(workspace.root),
worktreePath: workspace.root,
branch: workspace.branch,
baseHead: workspace.baseHead,
branchExists: false,
};
}
private async resumePendingDecision(
run: WorkflowRun,
receipt: CommandReceipt,
) {
const decision = run.decisions.find(
(candidate) => candidate.idempotencyKey === receipt.key,
);
if (!decision)
throw new Error(`Decision for receipt ${receipt.key} is missing`);
const gate = run.gates.find(
(candidate) => candidate.id === decision.gateId,
);
if (!gate) throw new Error(`Gate ${decision.gateId} is missing`);
const continuation =
receipt.continuation ?? decisionContinuation(gate.kind, decision.value);
if (continuation === "design") {
const laterPlanGate = run.gates.some(
(candidate) =>
candidate.kind === "plan-approval" &&
candidate.status === "open" &&
candidate.revision > receipt.acceptedRevision,
);
if (!laterPlanGate && run.status !== "failed") {
run = await this.runDesign(
run,
decision.value.kind === "revise-plan" &&
run.revision === receipt.acceptedRevision,
);
}
const planGateReady = run.gates.some(
(candidate) =>
candidate.kind === "plan-approval" &&
candidate.status === "open" &&
candidate.revision > receipt.acceptedRevision,
);
if (!planGateReady && run.status !== "failed") return view(run);
} else if (continuation === "prototype") {
if (run.status !== "failed") {
run = await this.runPrototypeStage(run);
}
} else if (continuation === "implementation") {
if (run.status !== "failed") {
run = await this.runImplementationPipeline(run);
}
} else if (continuation === "recovery") {
run = await this.resolveRecovery(run, decision.value);
}
return this.completeReceipt(run, receipt.key);
}
private commandReceipt(
run: WorkflowRun,
command: DecideWorkflowCommand | AbortWorkflowCommand,
) {
const receipt = run.commandReceipts.find(
(candidate) => candidate.key === command.idempotencyKey,
);
if (!receipt) return undefined;
if (receipt.fingerprint !== fingerprint(command)) {
throw new Error("idempotency key was already used with another command");
}
return receipt;
}
private async completeReceipt(run: WorkflowRun, key: string) {
const next = structuredClone(run);
next.revision++;
const receipt = next.commandReceipts.find(
(candidate) => candidate.key === key,
);
if (!receipt) throw new Error(`Command receipt ${key} is missing`);
const result = view(next);
receipt.status = "completed";
receipt.result = result;
await this.options.store.saveRun(next, run.revision);
return result;
}
private async transition(
run: WorkflowRun,
update: (next: WorkflowRun) => void,
) {
const next = structuredClone(run);
update(next);
next.revision = run.revision + 1;
if (next.lease) next.lease.heartbeatAt = this.now();
if (isTerminalStatus(next.status) && !isTerminalStatus(run.status)) {
next.deliveries.push({
key: `terminal:${next.runId}:${next.status}:${next.revision}`,
state: "pending",
createdAt: this.now(),
});
next.reportArtifact = await this.options.store.saveReport(next);
}
await this.options.store.saveRun(next, run.revision);
if (isTerminalStatus(next.status)) {
await this.options.store.publishResult(next).catch(() => {});
}
await Promise.resolve(this.options.afterRunPersist?.(next)).catch(() => {});
return next;
}
private async failRun(run: WorkflowRun, code: string, error: unknown) {
return this.transition(run, (next) => {
next.status = "failed";
next.error = {
code,
message: error instanceof Error ? error.message : String(error),
};
});
}
private reclaimStaleRecoveryLease(
run: WorkflowRun,
command: WorkflowCommand,
) {
if (
command.action !== "decide" ||
command.decision.kind !== "recover" ||
command.decision.resolution !== "retry" ||
activeGate(run)?.id !== command.gateId ||
!run.parentSessionIds.includes(this.options.parentSessionId) ||
!run.lease ||
this.isProcessAlive(run.lease.processId)
) {
return run;
}
const claimed = structuredClone(run);
claimed.lease = {
processId: this.processId,
activeParentSessionId: this.options.parentSessionId,
heartbeatAt: this.now(),
worktreeIdentity: claimed.workspace.root,
};
return claimed;
}
private assertActiveParent(run: WorkflowRun) {
if (
run.lease &&
run.lease.activeParentSessionId !== this.options.parentSessionId
) {
throw new Error(
"Historical parent sessions may inspect but cannot mutate this run",
);
}
}
private async resumePersistedContinuation(run: WorkflowRun) {
if (
run.script &&
!run.script.executed &&
(run.script.status === "pending" || run.script.status === "running")
) {
const insertionReady =
run.mode === "analysis" ||
(run.script.insertionPoint === "before-design" &&
stage(run, "discover").status === "completed") ||
(run.script.insertionPoint === "after-design" &&
run.gates.some((gate) => gate.kind === "plan-approval")) ||
(run.script.insertionPoint === "before-review" &&
stage(run, "verify").status === "completed");
if (insertionReady) {
return this.runWorkflowScript(run, run.script.insertionPoint);
}
}
const pendingDecision = run.commandReceipts.find(
(receipt) => receipt.action === "decide" && receipt.status === "pending",
);
if (pendingDecision) {
await this.resumePendingDecision(run, pendingDecision);
return this.options.store.loadRun(run.runId);
}
const discover = stage(run, "discover");
const design = stage(run, "design");
if (discover.status === "running") return this.runDiscovery(run);
if (discover.status === "completed" && design.status === "running") {
return this.runDesign(run);
}
if (
run.status === "running" &&
run.uiImpact === "non-trivial" &&
!run.implementationContext
) {
return this.runPrototypeStage(run);
}
if (
run.status === "running" &&
run.implementationContext?.status === "ready"
) {
return this.runImplementationPipeline(run);
}
return run;
}
private async hasActiveAssignmentLease(
runId: string,
assignments: WorkflowAssignment[],
) {
for (const assignment of assignments) {
if (!assignment.executionToken) continue;
const lease = await this.options.store.loadAssignmentLease(
runId,
assignment.id,
);
if (!lease || lease.token !== assignment.executionToken) continue;
const ownerIsRunning =
lease.processId === this.processId
? ACTIVE_ASSIGNMENT_TOKENS.has(lease.token)
: this.isProcessAlive(lease.processId);
const heartbeatIsFresh =
this.now() - lease.heartbeatAt <= this.leaseStaleMs;
if (ownerIsRunning && (heartbeatIsFresh || assignment.mutates))
return true;
}
return false;
}
private leaseIsStale(run: WorkflowRun) {
if (
run.lease?.processId === this.processId &&
run.lease.activeParentSessionId === this.options.parentSessionId
) {
return false;
}
return (
!run.lease ||
this.now() - run.lease.heartbeatAt > this.leaseStaleMs ||
!this.isProcessAlive(run.lease.processId)
);
}
private validateStart(command: StartWorkflowCommand) {
if (command.request.trim().length === 0)
throw new Error("request is required");
if (
!FEATURE_NAME.test(command.featureName) ||
command.featureName.length > MAX_FEATURE_NAME_LENGTH
) {
throw new Error(
`featureName must be lowercase kebab case and at most ${MAX_FEATURE_NAME_LENGTH} characters`,
);
}
if (command.idempotencyKey.trim().length === 0) {
throw new Error("idempotencyKey is required");
}
if (
command.concurrency !== undefined &&
command.concurrency > this.options.effectiveConfig.concurrency.ceiling
) {
throw new Error(
`concurrency cannot exceed ceiling ${this.options.effectiveConfig.concurrency.ceiling}`,
);
}
}
private preflightEligibility(initialFacts: WorkflowFacts) {
const roles: ProfileRole[] = [
"discover",
"design",
"implement",
"verify",
"review",
];
const evidence: StageEligibilityEvidence[] = roles.map((role) => {
const candidates = eligibleProfiles(
this.options.profiles,
{ ...initialFacts, stage: role },
role,
).map((evaluation) => ({
profileName: evaluation.profile.name,
profileDigest: evaluation.profile.digest,
eligible: evaluation.eligible,
reasons: [...evaluation.reasons],
specificity: evaluation.specificity,
}));
if (!candidates.some((candidate) => candidate.eligible)) {
throw new Error(`No eligible ${role} Agent Profile is available`);
}
return { role, candidates };
});
const eligibleNames = (role: ProfileRole) =>
evidence
.find((entry) => entry.role === role)
?.candidates.filter((candidate) => candidate.eligible)
.map((candidate) => candidate.profileName) ?? [];
const implementers = eligibleNames("implement");
const reviewers = eligibleNames("review");
if (
!reviewers.some((reviewer) =>
implementers.some((implementer) => implementer !== reviewer),
)
) {
throw new Error(
"No independently eligible review Agent Profile is available",
);
}
return evidence;
}
}