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.
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 let mut handler = MainEffectHandler::new(state.clone());
72 run_event_loop_driver(ctx, Some(state), config, &mut handler)
73}
74
75/// Run the event loop with a custom effect handler.
76///
77/// This variant allows injecting a custom effect handler for testing.
78/// The handler must implement `EffectHandler` and `StatefulHandler` traits.
79///
80/// # Arguments
81///
82/// * `ctx` - Phase context for effect handlers
83/// * `initial_state` - Optional initial state (if None, creates a new state)
84/// * `config` - Event loop configuration
85/// * `handler` - Custom effect handler (e.g., `MockEffectHandler` for testing)
86///
87/// # Returns
88///
89/// Returns the event loop result containing the completion status and final state.
90///
91/// # Errors
92///
93/// Returns error if the operation fails.
94pub fn run_event_loop_with_handler<'ctx, H>(
95 ctx: &mut PhaseContext<'_>,
96 initial_state: Option<PipelineState>,
97 config: EventLoopConfig,
98 handler: &mut H,
99) -> Result<EventLoopResult>
100where
101 H: EffectHandler<'ctx> + StatefulHandler,
102{
103 run_event_loop_driver(ctx, initial_state, config, handler)
104}