Skip to main content

ralph_workflow/reducer/state/
metrics.rs

1// Run-level execution metrics for the pipeline.
2//
3// This is the single source of truth for all iteration/attempt/retry/fallback statistics.
4//
5// # Where Metrics Are Updated
6//
7// Metrics are updated **only** in reducer code paths (`state_reduction/*.rs`):
8//
9// - `development.rs`: dev_iterations_started, dev_iterations_completed,
10//                     dev_attempts_total, dev_continuation_attempt,
11//                     analysis_attempts_*, xsd_retry_development
12// - `review.rs`: review_passes_started, review_passes_completed, review_runs_total,
13//                fix_runs_total, fix_continuations_total, fix_continuation_attempt,
14//                current_review_pass, xsd_retry_review, xsd_retry_fix
15// - `commit.rs`: commits_created_total, xsd_retry_commit
16// - `planning.rs`: xsd_retry_planning
17// - `agent.rs`: same_agent_retry_attempts_total, agent_fallbacks_total,
18//               model_fallbacks_total, retry_cycles_started_total
19//
20// # Event-to-Metric Mapping
21//
22// | Metric                              | Incremented On Event                                      | Notes                                    |
23// |-------------------------------------|-----------------------------------------------------------|------------------------------------------|
24// | dev_iterations_started              | DevelopmentEvent::IterationStarted                        | Not incremented on continuations         |
25// | dev_iterations_completed            | DevelopmentEvent::IterationCompleted { output_valid: true } | Advanced to commit phase                |
26// |                                     | DevelopmentEvent::ContinuationSucceeded                   | Continuation advanced to commit phase    |
27// | dev_attempts_total                  | DevelopmentEvent::AgentInvoked                            | Includes initial + continuations         |
28// | dev_continuation_attempt            | DevelopmentEvent::ContinuationTriggered                   | Reset on IterationStarted                |
29// | analysis_attempts_total             | DevelopmentEvent::AnalysisAgentInvoked                    | Total across all iterations              |
30// | analysis_attempts_in_current_iteration | DevelopmentEvent::AnalysisAgentInvoked                 | Reset on IterationStarted                |
31// | review_passes_started               | ReviewEvent::PassStarted                                  | Increments when pass != previous         |
32// | review_passes_completed             | ReviewEvent::Completed { issues_found: false }            | Clean pass                               |
33// |                                     | ReviewEvent::PassCompletedClean                           | Alternative event for clean pass         |
34// |                                     | ReviewEvent::FixAttemptCompleted                          | Fix completed, pass advances             |
35// | review_runs_total                   | ReviewEvent::AgentInvoked                                 | Total reviewer invocations               |
36// | fix_runs_total                      | ReviewEvent::FixAgentInvoked                              | Total fix invocations                    |
37// | fix_continuations_total             | ReviewEvent::FixContinuationTriggered                     | Fix continuation attempts                |
38// | fix_continuation_attempt            | ReviewEvent::FixContinuationTriggered                     | Reset on PassStarted                     |
39// | current_review_pass                 | ReviewEvent::PassStarted                                  | Tracks current pass number               |
40// | xsd_retry_*                         | *Event::OutputValidationFailed (when will_retry == true)  | Only when retrying, not when exhausted   |
41// | same_agent_retry_attempts_total     | AgentEvent::TimedOut / InternalError (when will_retry)    | Only when retrying same agent            |
42// | agent_fallbacks_total               | AgentEvent::FallbackTriggered                             | Agent switched in chain                  |
43// | model_fallbacks_total               | AgentEvent::ModelFallbackTriggered                        | Model switched for agent                 |
44// | retry_cycles_started_total          | AgentEvent::RetryCycleStarted                             | Chain exhausted, restarting              |
45// | commits_created_total               | CommitEvent::Created                                      | Actual git commit created                |
46//
47// # How to Add New Metrics
48//
49// 1. Add field to `RunMetrics` struct with `#[serde(default)]`
50// 2. Update `RunMetrics::new()` if config-derived display field
51// 3. Update appropriate reducer in `state_reduction/` to increment on event
52// 4. Add unit test in `state_reduction/tests/metrics.rs`
53// 5. Update `finalize_pipeline()` if displayed in final summary
54// 6. Add checkpoint compatibility test
55//
56// # Checkpoint Compatibility
57//
58// All fields have `#[serde(default)]` to ensure old checkpoints can be loaded
59// with new metrics fields defaulting to 0.
60
61/// Run-level execution metrics tracked by the reducer.
62///
63/// This struct provides a complete picture of pipeline execution progress,
64/// including iteration counts, attempt counts, retry counts, and fallback events.
65/// All fields are monotonic counters that only increment during a run.
66///
67/// # Checkpoint Compatibility
68///
69/// All fields have `#[serde(default)]` to ensure backward compatibility when
70/// loading checkpoints created before metrics were added or when new fields
71/// are introduced in future versions.
72///
73/// # Single Source of Truth
74///
75/// The reducer is the **only** code that mutates these metrics. They are
76/// updated deterministically based on events, ensuring:
77/// - Metrics survive checkpoint/resume
78/// - No drift between runtime state and actual progress
79/// - Final summary is always consistent with reducer state
80#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
81pub struct RunMetrics {
82    // Development iteration tracking
83    /// Number of development iterations started.
84    /// Incremented on `DevelopmentEvent::IterationStarted` (not on continuations).
85    #[serde(default)]
86    pub dev_iterations_started: u32,
87    /// Number of development iterations completed (advanced to commit phase).
88    /// A dev iteration is "completed" when the reducer transitions to `PipelinePhase::CommitMessage`
89    /// after dev output is valid, regardless of whether an actual git commit is created.
90    /// Incremented on `DevelopmentEvent::IterationCompleted { output_valid: true }` and
91    /// `DevelopmentEvent::ContinuationSucceeded`.
92    #[serde(default)]
93    pub dev_iterations_completed: u32,
94    /// Total number of developer agent invocations (includes continuations).
95    #[serde(default)]
96    pub dev_attempts_total: u32,
97    /// Current continuation attempt within the current development iteration (0 = initial).
98    /// Reset when starting a new iteration.
99    #[serde(default)]
100    pub dev_continuation_attempt: u32,
101
102    // Analysis tracking
103    /// Total number of analysis agent invocations across all iterations.
104    #[serde(default)]
105    pub analysis_attempts_total: u32,
106    /// Analysis attempts in the current development iteration (reset per iteration).
107    #[serde(default)]
108    pub analysis_attempts_in_current_iteration: u32,
109
110    // Review tracking
111    /// Number of review passes started.
112    /// Incremented on `ReviewEvent::PassStarted` when `pass != previous_pass`.
113    #[serde(default)]
114    pub review_passes_started: u32,
115    /// Number of review passes completed (advanced past without issues or after fixes).
116    /// A review pass is "completed" when it advances to the next pass or to commit phase,
117    /// either because no issues were found or because fixes were successfully applied.
118    /// Incremented on `ReviewEvent::Completed { issues_found: false }`,
119    /// `ReviewEvent::PassCompletedClean`, and `ReviewEvent::FixAttemptCompleted`.
120    #[serde(default)]
121    pub review_passes_completed: u32,
122    /// Total number of reviewer agent invocations.
123    #[serde(default)]
124    pub review_runs_total: u32,
125    /// Total number of fix agent invocations.
126    #[serde(default)]
127    pub fix_runs_total: u32,
128    /// Total number of fix continuation attempts.
129    #[serde(default)]
130    pub fix_continuations_total: u32,
131    /// Current fix continuation attempt within the current review pass (0 = initial).
132    ///
133    /// Reset when starting a new review pass.
134    /// Note: fix-attempt boundaries do not reset this counter; it is scoped to the review pass.
135    #[serde(default)]
136    pub fix_continuation_attempt: u32,
137    /// Current review pass number (for X/Y display).
138    #[serde(default)]
139    pub current_review_pass: u32,
140
141    // XSD retry tracking
142    /// Total XSD retry attempts across all phases.
143    #[serde(default)]
144    pub xsd_retry_attempts_total: u32,
145    /// XSD retry attempts in planning phase.
146    #[serde(default)]
147    pub xsd_retry_planning: u32,
148    /// XSD retry attempts in development/analysis phase.
149    #[serde(default)]
150    pub xsd_retry_development: u32,
151    /// XSD retry attempts in review phase.
152    #[serde(default)]
153    pub xsd_retry_review: u32,
154    /// XSD retry attempts in fix phase.
155    #[serde(default)]
156    pub xsd_retry_fix: u32,
157    /// XSD retry attempts in commit phase.
158    #[serde(default)]
159    pub xsd_retry_commit: u32,
160
161    // Same-agent retry tracking
162    /// Total same-agent retry attempts (for transient failures like timeout).
163    #[serde(default)]
164    pub same_agent_retry_attempts_total: u32,
165
166    // Agent/model fallback tracking
167    /// Total agent fallback events.
168    #[serde(default)]
169    pub agent_fallbacks_total: u32,
170    /// Total model fallback events.
171    #[serde(default)]
172    pub model_fallbacks_total: u32,
173    /// Total retry cycles started (agent chain exhaustion + restart).
174    #[serde(default)]
175    pub retry_cycles_started_total: u32,
176
177    // Commit tracking
178    /// Total commits created during the run.
179    #[serde(default)]
180    pub commits_created_total: u32,
181
182    // Config-derived display fields (set once at init, not serialized from events)
183    /// Maximum development iterations (from config, for X/Y display).
184    #[serde(default)]
185    pub max_dev_iterations: u32,
186    /// Maximum review passes (from config, for X/Y display).
187    #[serde(default)]
188    pub max_review_passes: u32,
189    /// Maximum XSD retry count (from config, for X/max display).
190    #[serde(default)]
191    pub max_xsd_retry_count: u32,
192    /// Maximum development continuation count (from config, for X/max display).
193    #[serde(default)]
194    pub max_dev_continuation_count: u32,
195    /// Maximum fix continuation count (from config, for X/max display).
196    #[serde(default)]
197    pub max_fix_continuation_count: u32,
198    /// Maximum same-agent retry count (from config, for X/max display).
199    #[serde(default)]
200    pub max_same_agent_retry_count: u32,
201}
202
203impl RunMetrics {
204    /// Create metrics with config-derived display fields.
205    pub fn new(
206        max_dev_iterations: u32,
207        max_review_passes: u32,
208        continuation: &ContinuationState,
209    ) -> Self {
210        Self {
211            max_dev_iterations,
212            max_review_passes,
213            max_xsd_retry_count: continuation.max_xsd_retry_count,
214            max_dev_continuation_count: continuation.max_continue_count,
215            max_fix_continuation_count: continuation.max_fix_continue_count,
216            max_same_agent_retry_count: continuation.max_same_agent_retry_count,
217            ..Self::default()
218        }
219    }
220}