Skip to main content

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}