import { type TaskResult } from "@sema-agent/core"; import type { Pool } from "mysql2/promise"; import type { Metrics } from "./observability/metrics.js"; import type { Logger } from "./observability/logger.js"; export type DegenerateClass = "a" | "b" | "unknown"; export interface DegenerateSignals { class: DegenerateClass; /** Assistant turns since the last user message, INCLUDING the degenerate one. */ turnsSinceUser: number; /** Substantive (non-looping) assistant text before the degenerate turn, since the last user message. */ priorChars: number; salvagedLen: number; /** `salvagedLen` minus the estimated looped tail = how much real answer the degenerate turn carries. */ uniquePrefixLen: number; repeatedTailLen: number; } /** * Length of the longest periodic suffix of `text` (≥ {@link MIN_REPEATS} contiguous copies of some * unit ≤ {@link MAX_UNIT}). 0 when no qualifying loop is found. Bounded to the last {@link REP_WINDOW} * chars. This estimates where the degenerate repetition begins — a measurement aid, not a trimmer. */ export declare function repetitionTail(text: string): number; /** * Classify a degenerate failure from its salvaged text + the substantive content of the assistant * turns that preceded it (since the last user message). Pure — unit-tested directly. */ export declare function classifyDegenerate(salvaged: string, priorChars: number, turnsSinceUser: number): DegenerateSignals; /** * Build the fire-and-forget instrument. Returns a sync `(result) => void` so the hot result paths * (`recordTaskResult` / `recordDone`) stay unblocked; the session read + classification run in the * background and never throw into the response. No-op for non-degenerate results. */ export declare function makeDegenerateInstrument(pool: Pool, metrics?: Metrics, logger?: Logger): (result: TaskResult) => void; //# sourceMappingURL=degenerate-instrument.d.ts.map