import { ModelRouter, type RoutingResponse, type TaskComplexity } from "./model-router.js"; import { IndependentVerifier, type VerificationResult } from "./independent-verifier.js"; import { FiveStageStateFile, type FiveStageState } from "./state-file-5stage.js"; import { GoalPattern, type GoalDefinition } from "./goal-pattern.js"; import { WorktreeManager } from "./worktree-isolation.js"; import { DynamicWorkflows, type AdversarialResult, type FanOutResult, type FusionPanelResult } from "./dynamic-workflows.js"; import { ProxyClient } from "../pai/proxy-client.js"; import { SkillSync } from "../pai/skill-sync.js"; import type { LoopIteration } from "../core/types.js"; import { SafetyBoundary } from "../upgrades/safety-boundary.js"; import { VisionSelfCheck, type VisualArtifact, type VisionCheckResult, } from "../upgrades/vision-self-check.js"; import { FeedbackLoop, type PhaseExecutor } from "../tier2-primitives/loops/feedback-loop.js"; import { type FusionPanelSlug } from "../core/fusion-types.js"; export interface StackConfig { modelRouter: boolean; verifier: boolean; stateFile: boolean; goalPattern: boolean; worktree: boolean; visionCheck: boolean; safetyBoundary: boolean; dynamicWorkflows: boolean; skillCompounding: boolean; lifecycleHooks: boolean; fusionPanel: boolean; godModeRace: boolean; ultraPlinian: boolean; parseltongue: boolean; autoTune: boolean; stmModules: boolean; abliteration: boolean; transparency: boolean; promptObservatory: boolean; promptLiberation: boolean; } export interface StackStatus { layers: Record; modelRoute: RoutingResponse | null; verification: VerificationResult | null; stateFile: FiveStageState | null; activeGoal: string | null; worktrees: number; sessionId: string; } const DEFAULT_CONFIG: StackConfig = { modelRouter: true, verifier: true, stateFile: true, goalPattern: true, worktree: false, visionCheck: true, // enabled by default; only runs for UI/visual tasks safetyBoundary: true, dynamicWorkflows: false, skillCompounding: true, lifecycleHooks: true, fusionPanel: false, godModeRace: false, // enabled when GodModeClassic workflow is requested ultraPlinian: false, // enabled when UltraPlinian evaluation is requested parseltongue: false, // enabled when perturbation testing is requested autoTune: false, // enabled when adaptive params are configured stmModules: false, // enabled when STM pipeline is configured abliteration: false, // enabled when refusal analysis is needed transparency: true, // enabled by default — records all decisions promptObservatory: false, // enabled when observatory is queried promptLiberation: false, // enabled when liberation testing is active }; /** Keywords that suggest a task produces UI or visual output. */ const VISUAL_HINTS = new Set([ "image", "picture", "photo", "screenshot", "ui", "ux", "interface", "render", "design", "visual", "graphic", "layout", "frontend", "web page", "webpage", "html", "css", "svg", "canvas", "figma", "mockup", "wireframe", "icon", "logo", "banner", "thumbnail", "diagram", "chart", "plot", "illustration", ]); export class CompoundStack { readonly modelRouter: ModelRouter; readonly verifier: IndependentVerifier; readonly stateFile: FiveStageStateFile; readonly goalPattern: GoalPattern; readonly worktree: WorktreeManager; readonly safetyBoundary: SafetyBoundary; readonly dynamicWorkflows: DynamicWorkflows; readonly skillSync: SkillSync; readonly proxyClient: ProxyClient; readonly visionSelfCheck: VisionSelfCheck; public config: StackConfig; private activeGoalId: string | null = null; private currentProject: string = ""; private currentTask: string = ""; private sessionId: string; constructor(config?: Partial) { this.config = { ...DEFAULT_CONFIG, ...config }; this.modelRouter = new ModelRouter(); this.verifier = new IndependentVerifier(); this.stateFile = new FiveStageStateFile(); this.goalPattern = new GoalPattern(); this.worktree = new WorktreeManager(); this.safetyBoundary = new SafetyBoundary(); this.dynamicWorkflows = new DynamicWorkflows(this.verifier, this.modelRouter); this.skillSync = new SkillSync(); this.proxyClient = new ProxyClient(); this.visionSelfCheck = new VisionSelfCheck({ proxyClient: this.proxyClient }); this.sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; } async run(task: string, project: string): Promise { this.currentProject = project; this.currentTask = task; const status: StackStatus = { layers: {}, modelRoute: null, verification: null, stateFile: null, activeGoal: null, worktrees: 0, sessionId: this.sessionId, }; // ── Layer 1: Lifecycle — read state file at session start ── if (this.config.lifecycleHooks && this.config.stateFile) { const { contextPrompt, isResume } = this.stateFile.beginSession(project, this.sessionId); status.layers["lifecycle"] = isResume ? `resumed (${this.sessionId.slice(0, 16)}...)` : "fresh"; status.stateFile = this.stateFile.load(project); } // ── Layer 2: Model Router ── if (this.config.modelRouter) { const complexity = this.modelRouter.taskComplexity(task); const route = this.modelRouter.route({ task, domain: "code", complexity, requiresVision: false }); status.modelRoute = route; status.layers["model-router"] = `${route.primary.tier}(${route.primary.displayName})`; } // ── Layer 3: Safety Boundary ── if (this.config.safetyBoundary) { const available = this.safetyBoundary.getAvailable(); if (available.length === 0) { status.layers["safety-boundary"] = "BLOCKED: No available models"; await this.writeExit("BLOCKED — No available models", []); return status; } const route = this.safetyBoundary.route("task"); if (route.modelId === "none") { status.layers["safety-boundary"] = `BLOCKED: ${route.reason}`; await this.writeExit(`BLOCKED — ${route.reason}`, []); return status; } status.layers["safety-boundary"] = `PASS (${route.modelName})`; } // ── Layer 4: Goal Pattern ── if (this.config.goalPattern) { const goalDef: GoalDefinition = { text: task, criteria: [ { label: "Correctness", description: "Output is technically correct", weight: 0.4, required: true }, { label: "Completeness", description: "All requirements addressed", weight: 0.3, required: true }, { label: "Clarity", description: "Output is clear", weight: 0.2, required: false }, { label: "Efficiency", description: "Solution is efficient", weight: 0.1, required: false }, ], maxIterations: 10, minScore: 0.7, }; this.activeGoalId = this.goalPattern.setGoal(goalDef); status.activeGoal = this.activeGoalId; status.layers["goal-pattern"] = `goal set: ${goalDef.text.slice(0, 40)}...`; } // ── Layer 5: Verifier ── if (this.config.verifier) { status.layers["verifier"] = "ready"; } // ── Layer 6: State File ── if (this.config.stateFile && !status.stateFile) { const existing = this.stateFile.load(project); status.stateFile = existing; status.layers["state-file"] = existing ? `loaded (${existing.verifiedFacts.length} facts, ${existing.generalRules.length} rules)` : "created"; } // ── Layer 7: Dynamic Workflows (fan-out + adversarial + fusion) ── if (this.config.dynamicWorkflows) { status.layers["dynamic-workflows"] = "ready (fan-out + adversarial + loop-until-done + fusion)"; } // ── Layer 7b: Fusion Panel ── if (this.config.fusionPanel) { status.layers["fusion-panel"] = "ready (draft → critique → fuse)"; } // ── Layer 8: Worktrees ── if (this.config.godModeRace) { status.layers["godmode-race"] = "ready (parallel model racing)"; } if (this.config.ultraPlinian) { status.layers["ultra-plinian"] = "ready (5-tier output evaluation)"; } if (this.config.parseltongue) { status.layers["parseltongue"] = "ready (safety-gate perturbation testing)"; } if (this.config.autoTune) { status.layers["auto-tune"] = "ready (adaptive sampling parameters)"; } if (this.config.stmModules) { status.layers["stm-modules"] = "ready (semantic output transforms)"; } if (this.config.abliteration) { status.layers["abliteration-awareness"] = "ready (refusal pattern analysis)"; } if (this.config.transparency) { status.layers["transparency"] = "ready (prompt and routing records)"; } if (this.config.promptObservatory) { status.layers["prompt-observatory"] = "ready (system prompt catalog)"; } if (this.config.promptLiberation) { status.layers["prompt-liberation"] = "ready (prompt constraint analysis)"; } if (this.config.worktree) { const trees = this.worktree.list(); status.worktrees = trees.length; status.layers["worktree"] = `${trees.length} active`; } // ── Layer 9: Vision Check ── if (this.config.visionCheck) { const visual = this.isVisualTask(task); status.layers["vision-check"] = visual ? "ready (visual task detected — vision check active)" : "ready (non-visual task — vision check skipped)"; } return status; } /** * Determine whether a task likely produces UI/visual output. * Used to gate vision self-checks so they only run when relevant. */ isVisualTask(task: string): boolean { const lower = task.toLowerCase(); for (const hint of VISUAL_HINTS) { if (lower.includes(hint)) return true; } return false; } /** * Run a vision self-check after a loop iteration. * If the task is non-visual or vision checks are disabled, returns null. * Results are recorded in the state file: Lessons Learned for PASS/PARTIAL, * Open Failures for FAIL. */ async visionCheckIteration( iteration: LoopIteration, task?: string, artifact?: VisualArtifact ): Promise { const goal = task ?? this.currentTask; if (!this.config.visionCheck || !this.isVisualTask(goal)) { return null; } const resolvedArtifact = artifact ?? this.extractVisualArtifact(iteration); if (!resolvedArtifact) { return null; } const result = await this.visionSelfCheck.check(resolvedArtifact, goal); this.recordVisionResult(result, goal, iteration.number); return result; } /** * Run a full feedback loop with vision self-checks hooked after each iteration. * Only runs vision checks when the task hints at UI/visual output. */ async runLoopWithVision( task: string, executor: PhaseExecutor, options?: { maxIterations?: number; convergenceThreshold?: number; loopDelayMs?: number; } ): Promise<{ loopResult: import("../core/types.js").LoopResult; visionResults: VisionCheckResult[] }> { this.currentTask = task; const loop = new FeedbackLoop(undefined, this.sessionId, options); const visionResults: VisionCheckResult[] = []; loop.onIteration(async (iteration) => { const result = await this.visionCheckIteration(iteration, task); if (result) { visionResults.push(result); } }); const loopResult = await loop.run(task, executor); return { loopResult, visionResults }; } private extractVisualArtifact(iteration: LoopIteration): VisualArtifact | null { const haystack = `${iteration.executed ?? ""}\n${iteration.observation ?? ""}`; // Look for base64 data URIs const dataUriMatch = haystack.match(/data:image\/[a-zA-Z0-9.+]+;base64,[A-Za-z0-9+/=]+/); if (dataUriMatch) { const source = dataUriMatch[0]; const mime = source.match(/data:([^;]+)/)?.[1] ?? "image/png"; return { source, mimeType: mime, type: "image", provenance: "extracted from iteration output" }; } // Look for file paths or URLs ending in image extensions const imageRefMatch = haystack.match(/(?:file:\/\/|\b)\S+\.(?:png|jpe?g|gif|webp|svg|bmp)(?:\?\S*)?/i); if (imageRefMatch) { const source = imageRefMatch[0]; return { source, mimeType: this.guessMimeType(source), type: "image", provenance: "extracted from iteration output" }; } return null; } private recordVisionResult(result: VisionCheckResult, goal: string, iterationNumber: number): void { if (!this.config.stateFile || !this.currentProject) return; const summary = `Iteration ${iterationNumber} vision check for "${goal.slice(0, 80)}...": ${result.verdict} (${(result.confidence * 100).toFixed(0)}% confidence)`; if (result.verdict === "FAIL") { const detail = result.gaps.length > 0 ? ` — ${result.gaps.join("; ")}` : ""; this.stateFile.addFailure(this.currentProject, `${summary}${detail}`); } else { const detail = result.gaps.length > 0 ? ` — gaps: ${result.gaps.join("; ")}` : ` — matches: ${result.matches.join("; ") || "visual output aligned with goal"}`; this.stateFile.addLesson(this.currentProject, `${summary}${detail}`); } } private guessMimeType(path: string): string { const ext = path.split(".").pop()?.toLowerCase() ?? ""; const mimeMap: Record = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", bmp: "image/bmp", svg: "image/svg+xml", }; return mimeMap[ext] ?? "image/png"; } /** * Execute a fan-out-and-synthesize dynamic workflow. * Splits a task into N sub-tasks, runs each independently, synthesizes results. */ async fanOut( task: string, subTasks: string[], executor: (subTask: string, index: number) => LoopIteration | Promise, synthesizer: (results: LoopIteration[], originalTask: string) => LoopIteration | Promise, maxConcurrency?: number, ): Promise { this.ensureConfig("dynamicWorkflows"); return this.dynamicWorkflows.fanOutAndSynthesize(task, subTasks, executor, synthesizer, maxConcurrency); } /** * Execute adversarial verification: maker artifact → independent verifier. */ async adversarialVerify( makerIteration: LoopIteration, task: string, ): Promise { this.ensureConfig("dynamicWorkflows"); return this.dynamicWorkflows.adversarialVerify(makerIteration, task); } /** * Execute a fusion panel: dispatch task to multiple panelists in parallel, * then synthesize results with the judge model. * * Implements the draft → critique → fuse pattern. */ async fusionPanel( task: string, executor: (prompt: string, panelistIndex: number) => LoopIteration | Promise, judge: (panelists: LoopIteration[], originalTask: string) => LoopIteration | Promise, panelSlug: FusionPanelSlug = "opus4.8-4.8", maxConcurrency?: number, ): Promise { this.ensureConfig("fusionPanel"); return this.dynamicWorkflows.fusionPanel(task, executor, judge, panelSlug, maxConcurrency); } /** * Compound a verified failure into the most relevant PAI skill. * Implements the "write the lesson into the Skill" pattern. */ compoundLesson(lesson: string, context: string): { skillName: string; path: string } | null { if (!this.config.skillCompounding) return null; const relevant = this.skillSync.findRelevantSkill(context); if (!relevant) return null; const path = this.skillSync.compoundLesson(relevant.name, lesson, "compound-stack"); if (!path) return null; return { skillName: relevant.name, path }; } /** * End the current session: write summary + next actions to state file. * Implements the "write before walking away" rule. */ async writeExit(summary: string, nextActions: string[], options?: { lessons?: string[]; facts?: string[]; failures?: string[] }): Promise { if (!this.config.lifecycleHooks || !this.config.stateFile) return; this.stateFile.endSession(this.currentProject, summary, nextActions, this.sessionId, { addLesson: options?.lessons?.[0], addFact: options?.facts?.[0], failures: options?.failures, }); } /** * Get a status report string for display. */ getStatus(): string { const parts: string[] = ["Fable 5 Compound Stack", "━━━━━━━━━━━━━━━━━"]; const layerOrder: Array = [ "lifecycleHooks", "modelRouter", "safetyBoundary", "goalPattern", "verifier", "dynamicWorkflows", "fusionPanel", "godModeRace", "ultraPlinian", "parseltongue", "autoTune", "stmModules", "abliteration", "transparency", "promptObservatory", "promptLiberation", "stateFile", "worktree", "visionCheck", "skillCompounding", ]; for (const layer of layerOrder) { const enabled = this.config[layer]; parts.push(` ${enabled ? "✓" : "○"} ${layer}: ${enabled ? "enabled" : "disabled"}`); } const goals = this.goalPattern.getStatus(); parts.push(` ${goals}`); parts.push(` ${this.activeGoalId ? `Active goal: ${this.activeGoalId}` : "No active goal"}`); parts.push(` Session: ${this.sessionId.slice(0, 16)}...`); return parts.join("\n"); } private ensureConfig(key: keyof StackConfig): void { if (!this.config[key]) { throw new Error(`Layer "${key}" is disabled in config. Enable it to use this feature.`); } } }