ralph_workflow/reducer/state/enums.rs
1// State enums and basic types.
2//
3// Contains ArtifactType, PromptMode, DevelopmentStatus, FixStatus, RebaseState, CommitState.
4
5/// Artifact type being processed by the current phase.
6///
7/// Used to track which XML artifact type is expected for XSD validation,
8/// enabling role-specific retry prompts and error messages.
9#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
10pub enum ArtifactType {
11 /// Plan XML from planning phase.
12 Plan,
13 /// `DevelopmentResult` XML from development phase.
14 DevelopmentResult,
15 /// Issues XML from review phase.
16 Issues,
17 /// `FixResult` XML from fix phase.
18 FixResult,
19 /// `CommitMessage` XML from commit message generation.
20 CommitMessage,
21}
22
23/// Prompt rendering mode chosen by the reducer.
24#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
25pub enum PromptMode {
26 /// Standard prompt rendering.
27 Normal,
28 /// XSD retry prompt rendering for invalid XML outputs.
29 XsdRetry,
30 /// Continuation prompt rendering for partial/failed outputs.
31 Continuation,
32 /// Same-agent retry prompt rendering for transient invocation failures.
33 ///
34 /// Used for timeouts and internal/unknown errors where we want to retry the
35 /// same agent first with additional guidance (reduce scope, chunk work, etc.).
36 SameAgentRetry,
37}
38
39/// Reason a same-agent retry is pending.
40#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub enum SameAgentRetryReason {
42 /// The agent invocation timed out with no meaningful output.
43 ///
44 /// This is the legacy timeout behavior - treated like other same-agent retries.
45 Timeout,
46 /// The agent invocation timed out with meaningful partial output.
47 ///
48 /// Context should be preserved for the retry. If the agent supports session IDs,
49 /// the session is reused. If not, the prior context is extracted from the logfile
50 /// and written to a temp file for the retry prompt.
51 TimeoutWithContext,
52 /// The agent invocation failed with an internal/unknown error.
53 InternalError,
54 /// The agent invocation failed with a non-auth, non-rate-limit, non-timeout error.
55 ///
56 /// This is a catch-all category used to ensure immediate agent fallback only happens
57 /// for rate limit (429) and authentication failures.
58 Other,
59}
60
61impl std::fmt::Display for ArtifactType {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::Plan => write!(f, "plan"),
65 Self::DevelopmentResult => write!(f, "development_result"),
66 Self::Issues => write!(f, "issues"),
67 Self::FixResult => write!(f, "fix_result"),
68 Self::CommitMessage => write!(f, "commit_message"),
69 }
70 }
71}
72
73/// Development status from agent output.
74///
75/// These values map to the `<ralph-status>` element in `development_result.xml`.
76/// Used to track whether work is complete or needs continuation.
77#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
78pub enum DevelopmentStatus {
79 /// Work completed successfully - no continuation needed.
80 Completed,
81 /// Work partially done - needs continuation.
82 Partial,
83 /// Work failed - needs continuation with different approach.
84 Failed,
85}
86
87/// Fix status from agent output.
88///
89/// These values map to the `<ralph-status>` element in `fix_result.xml`.
90/// Used to track whether fix work is complete or needs continuation.
91///
92/// # Continuation Semantics
93///
94/// - `AllIssuesAddressed`: Complete, no continuation needed
95/// - `NoIssuesFound`: Complete, no continuation needed
96/// - `IssuesRemain`: Work incomplete, needs continuation
97/// - `Failed`: Fix attempt failed, needs continuation with different approach
98#[derive(Copy, Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
99pub enum FixStatus {
100 /// All issues have been addressed - no continuation needed.
101 #[default]
102 AllIssuesAddressed,
103 /// Issues remain - needs continuation.
104 IssuesRemain,
105 /// No issues were found - nothing to fix.
106 NoIssuesFound,
107 /// Fix attempt failed - needs continuation with different approach.
108 ///
109 /// This status indicates the fix attempt produced valid XML output but
110 /// the agent could not fix the issues (e.g., blocked by external factors,
111 /// needs different strategy). Triggers continuation like `IssuesRemain`.
112 Failed,
113}
114
115impl std::fmt::Display for DevelopmentStatus {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 match self {
118 Self::Completed => write!(f, "completed"),
119 Self::Partial => write!(f, "partial"),
120 Self::Failed => write!(f, "failed"),
121 }
122 }
123}
124
125impl std::fmt::Display for FixStatus {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 match self {
128 Self::AllIssuesAddressed => write!(f, "all_issues_addressed"),
129 Self::IssuesRemain => write!(f, "issues_remain"),
130 Self::NoIssuesFound => write!(f, "no_issues_found"),
131 Self::Failed => write!(f, "failed"),
132 }
133 }
134}
135
136impl FixStatus {
137 /// Parse a fix status string from XML.
138 ///
139 /// This is intentionally not implementing `std::str::FromStr` because it returns
140 /// Option<Self> for easier handling of unknown values without error types.
141 #[must_use]
142 pub fn parse(s: &str) -> Option<Self> {
143 match s {
144 "all_issues_addressed" => Some(Self::AllIssuesAddressed),
145 "issues_remain" => Some(Self::IssuesRemain),
146 "no_issues_found" => Some(Self::NoIssuesFound),
147 "failed" => Some(Self::Failed),
148 _ => None,
149 }
150 }
151
152 /// Returns true if the fix is complete (no more work needed).
153 #[must_use]
154 pub const fn is_complete(&self) -> bool {
155 matches!(self, Self::AllIssuesAddressed | Self::NoIssuesFound)
156 }
157
158 /// Returns true if continuation is needed (incomplete work or failure).
159 ///
160 /// Both `IssuesRemain` and `Failed` trigger continuation:
161 /// - `IssuesRemain`: Some issues fixed, others remain
162 /// - `Failed`: Fix attempt failed, needs different approach
163 #[must_use]
164 pub const fn needs_continuation(&self) -> bool {
165 matches!(self, Self::IssuesRemain | Self::Failed)
166 }
167}
168
169/// Rebase operation state.
170///
171/// Tracks rebase progress through the state machine:
172/// `NotStarted` → `InProgress` → Conflicted → Completed/Skipped
173#[derive(Clone, Serialize, Deserialize, Debug)]
174pub enum RebaseState {
175 NotStarted,
176 InProgress {
177 original_head: String,
178 target_branch: String,
179 },
180 Conflicted {
181 original_head: String,
182 target_branch: String,
183 files: Vec<PathBuf>,
184 resolution_attempts: u32,
185 },
186 Completed {
187 new_head: String,
188 },
189 Skipped,
190}
191
192impl RebaseState {
193 #[doc(hidden)]
194 #[cfg(any(test, feature = "test-utils"))]
195 #[must_use]
196 pub const fn is_terminal(&self) -> bool {
197 matches!(self, Self::Completed { .. } | Self::Skipped)
198 }
199
200 #[must_use]
201 pub fn current_head(&self) -> Option<String> {
202 match self {
203 Self::InProgress { original_head, .. } => Some(original_head.clone()),
204 Self::NotStarted | Self::Skipped | Self::Conflicted { .. } => None,
205 Self::Completed { new_head } => Some(new_head.clone()),
206 }
207 }
208
209 #[doc(hidden)]
210 #[cfg(any(test, feature = "test-utils"))]
211 #[must_use]
212 pub const fn is_in_progress(&self) -> bool {
213 matches!(
214 self,
215 Self::InProgress { .. } | Self::Conflicted { .. }
216 )
217 }
218}
219
220/// Maximum number of retry attempts when XML/format validation fails.
221///
222/// This applies across the pipeline for:
223/// - Commit message generation validation failures
224/// - Plan generation validation failures
225/// - Development output validation failures
226/// - Review output validation failures
227///
228/// When an agent produces output that fails XML parsing or format validation,
229/// we retry with corrective prompts up to this many times before giving up.
230pub const MAX_VALIDATION_RETRY_ATTEMPTS: u32 = 100;
231
232/// Maximum number of developer validation retry attempts before giving up.
233///
234/// Specifically for developer iterations - this is for XSD validation failures
235/// (malformed XML). After exhausting these retries, the system will fall back
236/// to a continuation attempt with a fresh prompt rather than failing entirely.
237/// This separates XSD retry (can't parse the response) from continuation
238/// (understood the response but work is incomplete).
239pub const MAX_DEV_VALIDATION_RETRY_ATTEMPTS: u32 = 10;
240
241/// Maximum number of invalid XML output reruns before aborting the iteration.
242pub const MAX_DEV_INVALID_OUTPUT_RERUNS: u32 = 2;
243
244/// Maximum number of invalid planning output reruns before switching agents.
245pub const MAX_PLAN_INVALID_OUTPUT_RERUNS: u32 = 2;
246
247/// Commit generation state.
248///
249/// Tracks commit message generation progress through retries:
250/// `NotStarted` → Generating → Generated → Committed/Skipped
251#[derive(Clone, Serialize, Deserialize, Debug)]
252pub enum CommitState {
253 NotStarted,
254 Generating { attempt: u32, max_attempts: u32 },
255 Generated { message: String },
256 Committed { hash: String },
257 Skipped,
258}
259
260impl CommitState {
261 #[doc(hidden)]
262 #[cfg(any(test, feature = "test-utils"))]
263 #[must_use]
264 pub const fn is_terminal(&self) -> bool {
265 matches!(self, Self::Committed { .. } | Self::Skipped)
266 }
267}
268
269/// Kind of prompt input that may require oversize handling.
270#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
271pub enum PromptInputKind {
272 Prompt,
273 Plan,
274 Diff,
275 LastOutput,
276}
277
278/// How an input is represented to downstream prompt templates.
279#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
280pub enum PromptInputRepresentation {
281 /// Input is embedded inline in the prompt template.
282 Inline,
283 /// Input is referenced by a workspace-relative file path.
284 ///
285 /// Important: this path is serialized into checkpoints. Storing absolute paths
286 /// would leak local filesystem layout and can break resuming a run from a
287 /// different checkout location.
288 FileReference {
289 /// Workspace-relative path to the materialized artifact.
290 path: PathBuf,
291 },
292}
293
294/// Reason an input was materialized in a non-default way.
295#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
296pub enum PromptMaterializationReason {
297 /// Input was within all configured budgets (no oversize handling required).
298 WithinBudgets,
299 /// Input exceeded the inline-embedding budget and must be referenced by file.
300 InlineBudgetExceeded,
301 /// Input exceeded the model-context budget and was truncated before use.
302 ModelBudgetExceeded,
303 /// Input was referenced even though it was within budgets (explicit policy).
304 PolicyForcedReference,
305}
306
307/// Canonical, reducer-visible record of prompt input materialization.
308///
309/// This records what the downstream prompt template will embed (inline vs file
310/// reference), along with stable identifiers so the reducer can dedupe repeated
311/// attempts in the event loop.
312#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
313pub struct MaterializedPromptInput {
314 pub kind: PromptInputKind,
315 pub content_id_sha256: String,
316 pub consumer_signature_sha256: String,
317 pub original_bytes: u64,
318 pub final_bytes: u64,
319 #[serde(default)]
320 pub model_budget_bytes: Option<u64>,
321 #[serde(default)]
322 pub inline_budget_bytes: Option<u64>,
323 pub representation: PromptInputRepresentation,
324 pub reason: PromptMaterializationReason,
325}