/** * agent-runner-validator.ts — Adversarial validation loop for agent output. * * Extracted from agent-runner.ts to reduce its size (~100 fewer lines) and * to make validation logic independently testable. Depends on `runAgent` / * `resumeAgent` being injected as higher-order functions to avoid a circular * dependency with agent-runner.ts. * * ## Design * * The validation loop runs N validator agents (N ≤ MAX_CRITERIA_COUNT) against * the main agent's output, with up to VALIDATION_MAX_RETRIES self-healing * rounds when validation fails. Each validator runs with strict isolation: * - `isolated: true` — no extension/MCP tools * - `skipValidators: true` — no recursive validation * - `levelLimit: 0` — no nested sub-agents * - Tight quotas (50k tokens, 120s, 10 tool calls) * * On failure, `resumeAgent` is called with structured feedback so the main * agent can self-correct before the next retry round. */ import type { Api, Model } from "@earendil-works/pi-ai"; import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import type { HookRegistry } from "./hooks.js"; import type { AgentConfig, ValidationResult } from "./types.js"; /** Minimal shape of the `runAgent` options the validator loop needs. */ export interface ValidatorRunOptions { pi: ExtensionAPI; model?: Model; isolated: boolean; skipValidators: boolean; levelLimit: number; signal?: AbortSignal; quotas: { maxTokens: number; maxDurationMs: number; maxToolCalls: number; }; } /** Minimum return type from `runAgent` for the validator consumer. */ export interface ValidatorRunResult { responseText: string; } /** Signature of `runAgent` as consumed by this module. */ export type RunAgentFn = (ctx: ExtensionContext, type: string, prompt: string, options: ValidatorRunOptions) => Promise; /** Options for the injected `resumeAgent` call. */ export interface ValidatorResumeOptions { onToolActivity?: (activity: { type: "start" | "end"; toolName: string; }) => void; onAssistantUsage?: (usage: { input: number; output: number; cacheWrite: number; }) => void; onCompaction?: (info: { reason: "manual" | "threshold" | "overflow"; tokensBefore: number; }) => void; signal?: AbortSignal; } /** Signature of `resumeAgent` as consumed by this module. */ export type ResumeAgentFn = (session: AgentSession, prompt: string, options: ValidatorResumeOptions) => Promise; /** All external dependencies the validation loop needs. */ export interface ValidationDeps { pi: ExtensionAPI; model?: Model; signal?: AbortSignal; hooks?: HookRegistry; runAgent: RunAgentFn; resumeAgent: ResumeAgentFn; onToolActivity?: (activity: { type: "start" | "end"; toolName: string; }) => void; onAssistantUsage?: (usage: { input: number; output: number; cacheWrite: number; }) => void; onCompaction?: (info: { reason: "manual" | "threshold" | "overflow"; tokensBefore: number; }) => void; onValidationComplete?: (results: ValidationResult[]) => void; } /** Return type of the adversarial validation function. */ export interface ValidationOutput { responseText: string; validationResults?: ValidationResult[]; validated?: boolean; } /** * Run adversarial validation on an agent's output. * * Takes the current `responseText`, the `agentConfig` (for validators), and * the `session` (for self-healing via `resumeAgent`). Skips validation when * the agent has no validators configured or when `skipValidators` is true * (caller should check this before calling). * * @param session - Agent session, used for self-healing resume calls * @param ctx - Extension context for recursive runAgent calls * @param responseText - Current agent output to validate * @param agentConfig - Agent config (may have validators defined) * @param agentId - Agent ID for hook dispatch * @param deps - Injected dependencies (pi, model, signal, hooks, callbacks, runAgent, resumeAgent) * @returns Updated response text (may have been self-healed) and validation results */ export declare function runAdversarialValidation(session: AgentSession, ctx: ExtensionContext, responseText: string, agentConfig: AgentConfig | undefined, agentId: string, deps: ValidationDeps): Promise;