ralph_workflow/app/core.rs
1//! Main event loop implementation.
2//!
3//! This module contains the core orchestrate-handle-reduce cycle that drives
4//! the reducer-based pipeline. The event loop coordinates pure reducer logic
5//! with impure effect handlers, maintaining strict separation of concerns.
6//!
7//! ## Event Loop Cycle
8//!
9//! ```text
10//! State → Orchestrate → Effect → Handle → Event → Reduce → Next State
11//! (pure) (impure) (pure)
12//! ```
13//!
14//! The loop continues until reaching a terminal state (Interrupted, Completed)
15//! or until max iterations is exceeded.
16//!
17//! ## Architecture
18//!
19//! The event loop is organized into several modules:
20//! - `driver` - Main iteration loop implementing orchestrate→handle→reduce cycle
21//! - `recovery` - Defensive completion and max iterations handling
22//! - `error_handling` - Panic recovery and error routing
23//! - `trace` - Trace buffer and diagnostic dumps
24//! - `config` - Configuration and initialization
25
26use crate::app::config::{create_initial_state_with_config, EventLoopConfig, EventLoopResult};
27use crate::app::driver::run_event_loop_driver;
28use crate::phases::PhaseContext;
29use crate::reducer::{EffectHandler, MainEffectHandler, PipelineState};
30use anyhow::Result;
31
32/// Trait for handlers that maintain internal state.
33///
34/// This trait allows the event loop to update the handler's internal state
35/// after each event is processed.
36pub trait StatefulHandler {
37 /// Update the handler's internal state.
38 fn update_state(&mut self, state: PipelineState);
39}
40
41/// Run the main event loop for the reducer-based pipeline.
42///
43/// This function orchestrates pipeline execution by repeatedly:
44/// 1. Determining the next effect based on the current state
45/// 2. Executing the effect through the effect handler (which performs side effects)
46/// 3. Applying the resulting event to state through the reducer (pure function)
47/// 4. Repeating until a terminal state is reached or max iterations exceeded
48///
49/// The entire event loop is wrapped in panic recovery to ensure the pipeline
50/// never crashes due to agent failures (panics only; aborts/segfaults cannot be recovered).
51///
52/// # Arguments
53///
54/// * `ctx` - Phase context for effect handlers
55/// * `initial_state` - Optional initial state (if None, creates a new state)
56/// * `config` - Event loop configuration
57///
58/// # Returns
59///
60/// Returns the event loop result containing the completion status and final state.
61///
62/// # Errors
63///
64/// Returns error if the operation fails.
65pub fn run_event_loop(
66 ctx: &mut PhaseContext<'_>,
67 initial_state: Option<PipelineState>,
68 config: EventLoopConfig,
69) -> Result<EventLoopResult> {
70 let state = initial_state.unwrap_or_else(|| create_initial_state_with_config(ctx));
71 run_event_loop_driver(
72 ctx,
73 Some(state.clone()),
74 config,
75 &mut MainEffectHandler::new(state),
76 )
77}
78
79/// Run the event loop with a custom effect handler.
80///
81/// This variant allows injecting a custom effect handler for testing.
82/// The handler must implement `EffectHandler` and `StatefulHandler` traits.
83///
84/// # Arguments
85///
86/// * `ctx` - Phase context for effect handlers
87/// * `initial_state` - Optional initial state (if None, creates a new state)
88/// * `config` - Event loop configuration
89/// * `handler` - Custom effect handler (e.g., `MockEffectHandler` for testing)
90///
91/// # Returns
92///
93/// Returns the event loop result containing the completion status and final state.
94///
95/// # Errors
96///
97/// Returns error if the operation fails.
98pub fn run_event_loop_with_handler<'ctx, H>(
99 ctx: &mut PhaseContext<'_>,
100 initial_state: Option<PipelineState>,
101 config: EventLoopConfig,
102 handler: &mut H,
103) -> Result<EventLoopResult>
104where
105 H: EffectHandler<'ctx> + StatefulHandler,
106{
107 run_event_loop_driver(ctx, initial_state, config, handler)
108}