Skip to main content

ralph_workflow/app/event_loop/
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 super::config::{create_initial_state_with_config, EventLoopConfig, EventLoopResult};
27use super::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.
61pub fn run_event_loop(
62    ctx: &mut PhaseContext<'_>,
63    initial_state: Option<PipelineState>,
64    config: EventLoopConfig,
65) -> Result<EventLoopResult> {
66    let state = initial_state.unwrap_or_else(|| create_initial_state_with_config(ctx));
67    let mut handler = MainEffectHandler::new(state.clone());
68    run_event_loop_driver(ctx, Some(state), config, &mut handler)
69}
70
71/// Run the event loop with a custom effect handler.
72///
73/// This variant allows injecting a custom effect handler for testing.
74/// The handler must implement `EffectHandler` and `StatefulHandler` traits.
75///
76/// # Arguments
77///
78/// * `ctx` - Phase context for effect handlers
79/// * `initial_state` - Optional initial state (if None, creates a new state)
80/// * `config` - Event loop configuration
81/// * `handler` - Custom effect handler (e.g., MockEffectHandler for testing)
82///
83/// # Returns
84///
85/// Returns the event loop result containing the completion status and final state.
86pub fn run_event_loop_with_handler<'ctx, H>(
87    ctx: &mut PhaseContext<'_>,
88    initial_state: Option<PipelineState>,
89    config: EventLoopConfig,
90    handler: &mut H,
91) -> Result<EventLoopResult>
92where
93    H: EffectHandler<'ctx> + StatefulHandler,
94{
95    run_event_loop_driver(ctx, initial_state, config, handler)
96}