//! Observability seam for the full-replace (kigi) pass. //! //! The shared orchestrator reports per-attempt and terminal outcomes through //! this trait so each harness can emit its own telemetry (kigi: //! `CompactionAttempt` rows, `CompactionRetryDegraded` events, span records, //! request-artifact persistence) without the shared crate depending on a //! telemetry backend. Mirrors //! [`IntraCompactionObserver`](crate::intra_compaction::IntraCompactionObserver) //! / [`InterCompactionObserver`](crate::inter_compaction::InterCompactionObserver). //! //! Emission points are part of the behavior contract: the kigi observer //! preserves the pre-migration `CompactionAttempt`/`CompactionRetryDegraded` //! semantics byte-for-byte. use std::time::Duration; /// Classified outcome of a single full-replace sample attempt. /// /// The harness turns this into its per-attempt telemetry row. `summary` is the /// raw model output (the harness bounds/captures it as needed); it is borrowed /// for the duration of the callback so no allocation happens on the hot path. #[derive(Debug)] pub enum FullReplaceAttemptOutcome<'a> { /// A usable, non-degenerate summary was produced; the pass will succeed. Success { /// Raw model summary text. summary: &'a str, }, /// The model returned an empty / whitespace-only response. EmptyResponse { /// Whether the orchestrator will retry after this attempt. will_retry: bool, }, /// The cleaned summary seed was too short to carry the conversation's task /// state; retried like a transient failure. Degenerate { /// Raw model summary text (still captured for offline inspection). summary: &'a str, /// Whether the orchestrator will retry after this attempt. will_retry: bool, }, /// The sampler returned an error. Failure { /// Rendered error message. message: &'a str, /// Whether re-sending the *same* input cannot help (auth / schema / /// size). Transient failures (timeout / stream blip / 5xx) are `false`. deterministic: bool, /// Whether the failure was a context-length overflow — the signal the /// harness uses to step its input ladder rather than suppress. context_overflow: bool, /// Whether the orchestrator will retry after this attempt (always /// `false` for deterministic failures and context overflows). will_retry: bool, }, } /// Receives full-replace compaction outcomes. All methods default to no-ops so /// harnesses without telemetry (and tests) can use `()`. pub trait FullReplaceObserver: Send + Sync { /// One sample attempt finished with the given classified outcome. /// `attempt` is 1-based and cumulative across the pass. fn on_attempt(&self, _attempt: u32, _outcome: &FullReplaceAttemptOutcome<'_>) {} /// The pass succeeded after `attempts` total attempts. fn on_success(&self, _attempts: u32, _summary_chars: usize, _elapsed: Duration) {} /// The pass failed terminally after `attempts` total attempts. fn on_error(&self, _attempts: u32) {} } /// No-op observer for tests and harnesses without telemetry. impl FullReplaceObserver for () {}