/** * Feedback router (Sprint 12). * * Maps a CheckpointId to the responsible agent and re-invokes that agent * with the prior feedback woven into its prompt. Per-agent adaptation * differs by agent role (s12-c6). The 'gate' checkpoints abort * the run on rejection — they are not iteration points. * * Abort token matching is case-SENSITIVE: `feedback.startsWith("!!abort")`. * This matches shell convention markers (e.g., `#!`) where case matters. * Tests must use the exact prefix "!!abort" (all lowercase). * * Edit delta application uses full-replacement semantics (no json-patch lib * installed). If editDelta is a string, it replaces the file content directly. * If it is { after: string }, the `after` property is used. Otherwise the * value is JSON.stringify-ed. The original is backed up to * `.bober/runs//edits/.original.` BEFORE overwrite * to satisfy evaluatorNotes reversibility requirement. * * Iteration counters are per-checkpoint-invocation (passed in by the caller), * NOT stored in a module-level map. The pipeline wires one counter per * checkpoint-invocation site so post-sprint for sprint-1 and post-sprint for * sprint-2 are independent. * * Sprint 12 — colocated in src/orchestrator/checkpoints/ per Sprint 7+8 precedent. */ import type { CheckpointId, CheckpointOutcome } from "./types.js"; import { type MechanismName } from "./audit.js"; /** * Agent types that can be re-invoked after a checkpoint rejection. * 'gate' checkpoints are not re-invoked; rejection always aborts. */ export type CheckpointAgent = "researcher" | "planner" | "generator" | "evaluator" | "gate"; /** * Per-checkpoint responsibility table. Source-of-truth for s12-c1 + s12-c6. * Gate entries abort the run on rejection; they have no agent to re-invoke. */ export declare const CHECKPOINT_TO_AGENT: Record; /** * Default escape-hatch prefix. Case-SENSITIVE: must be exactly "!!abort" * at the start of the feedback string. Documented here and matched in * shouldAbort() below. */ export declare const ABORT_TOKEN = "!!abort"; /** * Returns true if the feedback triggers an immediate abort. * * Two sources: * 1. Feedback starts with ABORT_TOKEN ("!!abort") — case-sensitive prefix match. * 2. envAbortToken is non-empty and appears anywhere in the feedback string. */ export declare function shouldAbort(feedback: string, envAbortToken?: string): boolean; /** * A single feedback/rejection event in the iteration history for a checkpoint. */ export interface FeedbackHistoryEntry { iteration: number; feedback: string; editDelta?: unknown; timestamp: string; } /** * Structured reason for run abort. Written to .bober/runs/.aborted.json. */ export interface RunAbortedReason { reason: "CHECKPOINT_ITERATION_EXHAUSTED" | "GATE_REJECTED" | "USER_ABORT"; checkpointId: CheckpointId; lastFeedback?: string; iterationsCompleted: number; } /** * Discriminated union returned by routeOutcome(). Callers MUST narrow on `kind`. */ export type RouterDecision = { kind: "approved"; } | { kind: "retry"; newPrompt: string; feedbackHistory: FeedbackHistoryEntry[]; } | { kind: "edit-applied"; updatedArtifact: unknown; } | { kind: "abort"; reason: RunAbortedReason; }; /** * Discriminated union returned by runCheckpointWithFeedback(). */ export type CheckpointResolution = { kind: "approved"; iterations: number; finalArtifact: unknown; } | { kind: "edited"; iterations: number; finalArtifact: unknown; editDelta: unknown; } | { kind: "aborted"; reason: RunAbortedReason; lastFeedback: string; }; /** * Get the responsible agent for a checkpoint ID. */ export declare function getResponsibleAgent(checkpointId: CheckpointId): CheckpointAgent; /** * Build a per-agent augmented prompt for a retry invocation. * Each agent type uses a distinct framing strategy (s12-c6). * * @throws Error if the agent is 'gate' — gate checkpoints must not be re-invoked. */ export declare function buildFeedbackPrompt(checkpointId: CheckpointId, originalPrompt: string, feedbackHistory: FeedbackHistoryEntry[], maxIterations: number): string; /** * Apply an edit delta to an artifact file on disk. * * Steps: * 1. Read original file content. * 2. Write backup to //edits/.original. * 3. Determine new content: * - string editDelta → full replacement * - { after: string } → use `after` property * - anything else → JSON.stringify(editDelta, null, 2) * 4. Atomic write: write to .tmp then fs.rename. */ export declare function applyEditDelta(artifactPath: string, editDelta: unknown, runsDir: string, runId: string, checkpointId: string): Promise; /** * Write an abort marker to .bober/runs/.aborted.json atomically. * Creates parent directories as needed. */ export declare function writeAbortMarker(projectRoot: string, runId: string, reason: RunAbortedReason): Promise; /** * Write a completion marker to .bober/runs/.completed.json atomically. * Creates parent directories as needed. */ export declare function writeCompletionMarker(projectRoot: string, runId: string, summary: Record): Promise; /** * Route a single CheckpointOutcome to a RouterDecision. * * This is a pure decision function — callers (pipeline.ts / runCheckpointWithFeedback) * are responsible for acting on the returned decision (re-invoking agents, writing * abort markers, etc.). * * @param checkpointId The checkpoint whose outcome is being routed. * @param outcome The discriminated outcome from the mechanism. * @param iteration Current iteration count (1-based). * @param maxIterations Cap from config.pipeline.maxCheckpointIterations. * @param feedbackHistory All prior feedback entries for this checkpoint invocation. * @param originalPrompt The agent's original prompt (for augmentation). * @param envAbortToken Optional env-var abort token (checked in addition to ABORT_TOKEN). */ export declare function routeOutcome(checkpointId: CheckpointId, outcome: CheckpointOutcome, iteration: number, maxIterations: number, feedbackHistory: FeedbackHistoryEntry[], originalPrompt: string, envAbortToken?: string): RouterDecision; /** * Options for runCheckpointWithFeedback. */ export interface RunCheckpointWithFeedbackOpts { /** The checkpoint to invoke. */ checkpointId: CheckpointId; /** The artifact to pass to the mechanism on the first call. */ artifact: unknown; /** The mechanism to use for this checkpoint. */ mechanism: { request: (id: CheckpointId, artifact: unknown) => Promise; }; /** Maximum re-invocations for this checkpoint (from config.pipeline.maxCheckpointIterations). */ maxIterations: number; /** Run identifier used for abort/edit markers on disk. */ runId: string; /** Absolute path to the project root (for .bober/runs/ markers). */ projectRoot: string; /** * Mechanism name for audit logging. Defaults to 'noop' when omitted * (safe for tests that don't exercise the audit path). */ mechanismName?: MechanismName; /** * Orchestrator-injected callback to re-run the responsible agent. * Returns the new artifact produced by the agent. * The callback receives: (agentType, augmentedPrompt) → Promise. */ reinvokeAgent: (agentType: CheckpointAgent, augmentedPrompt: string) => Promise; /** The original prompt that was passed to the responsible agent. */ originalPrompt: string; /** * Optional path to the artifact file on disk (needed for edit-delta application). * If omitted, edit deltas are returned but not written to disk. */ artifactPath?: string; /** * Optional env-var abort token (checked alongside ABORT_TOKEN). * If omitted, falls back to process.env['BOBER_CHECKPOINT_ABORT_TOKEN'] automatically. */ envAbortToken?: string; } /** * Run a checkpoint with automatic feedback propagation and iteration cap. * * Loop semantics: * - Start at iteration 1. * - Call mechanism.request(checkpointId, artifact). * - On approved → resolve { kind: 'approved', iterations: N, finalArtifact }. * - On edit → applyEditDelta if artifactPath is set; resolve { kind: 'edited', ... }. * - On rejection: * - shouldAbort → write .aborted.json; resolve { kind: 'aborted', reason: 'USER_ABORT' }. * - gate → write .aborted.json; resolve { kind: 'aborted', reason: 'GATE_REJECTED' }. * - iteration >= maxIterations → write .aborted.json; resolve 'CHECKPOINT_ITERATION_EXHAUSTED'. * - else → buildFeedbackPrompt; reinvokeAgent; new artifact; loop with N+1. * * Iteration counters are per-invocation of this function, not global. */ export declare function runCheckpointWithFeedback(opts: RunCheckpointWithFeedbackOpts): Promise; //# sourceMappingURL=feedback-router.d.ts.map