From 57b0f94b54500156dd36432b34349466d5bb7427 Mon Sep 17 00:00:00 2001 From: Kent Overstreet Date: Sun, 5 Apr 2026 04:29:56 -0400 Subject: [PATCH] move startup orchestration from mind to event_loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The top-level run() that creates Mind, wires channels, spawns the Mind event loop, and starts the UI event loop is orchestration — it belongs with the UI entry point, not in the cognitive layer. Renamed to event_loop::start(cli). mind/mod.rs is now purely the Mind struct: state machine, MindCommand, and the run loop. Co-Authored-By: Kent Overstreet --- src/mind/mod.rs | 45 ++++++------------------------------------ src/user/event_loop.rs | 31 +++++++++++++++++++++++++++++ src/user/mod.rs | 2 +- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/mind/mod.rs b/src/mind/mod.rs index 8f77a47..a2f3e30 100644 --- a/src/mind/mod.rs +++ b/src/mind/mod.rs @@ -24,10 +24,9 @@ use tokio::sync::{mpsc, Mutex}; use crate::agent::{Agent, TurnResult}; use crate::agent::api::ApiClient; -use crate::config::{self, AppConfig, SessionConfig}; -use crate::user::{self as tui}; -use crate::user::ui_channel::{self, StreamTarget}; +use crate::config::{AppConfig, SessionConfig}; use crate::subconscious::learn; +use crate::user::ui_channel::{self, StreamTarget}; /// Compaction threshold — context is rebuilt when prompt tokens exceed this. fn compaction_threshold(app: &AppConfig) -> u32 { @@ -92,7 +91,7 @@ impl MindState { } /// Consume pending input, return a Turn command if ready. - pub fn take_pending_input(&mut self) -> MindCommand { + fn take_pending_input(&mut self) -> MindCommand { if self.turn_active || self.input.is_empty() { return MindCommand::None; } @@ -106,7 +105,7 @@ impl MindState { } /// Process turn completion, return model switch name if requested. - pub fn complete_turn(&mut self, result: &Result, target: StreamTarget) -> Option { + fn complete_turn(&mut self, result: &Result, target: StreamTarget) -> Option { self.turn_active = false; match result { Ok(turn_result) => { @@ -137,7 +136,7 @@ impl MindState { } /// DMN tick — returns a Turn action with the DMN prompt, or None. - pub fn dmn_tick(&mut self) -> MindCommand { + fn dmn_tick(&mut self) -> MindCommand { if matches!(self.dmn, dmn::State::Paused | dmn::State::Off) { return MindCommand::None; } @@ -158,7 +157,7 @@ impl MindState { MindCommand::Turn(prompt, StreamTarget::Autonomous) } - pub fn interrupt(&mut self) { + fn interrupt(&mut self) { self.input.clear(); self.dmn = dmn::State::Resting { since: Instant::now() }; } @@ -383,35 +382,3 @@ impl Mind { } } } - -// --- Startup --- - -pub async fn run(cli: crate::user::CliArgs) -> Result<()> { - let (config, _figment) = config::load_session(&cli)?; - - if config.app.debug { - unsafe { std::env::set_var("POC_DEBUG", "1") }; - } - - let (ui_tx, ui_rx) = ui_channel::channel(); - let (turn_tx, turn_rx) = mpsc::channel::<(Result, StreamTarget)>(1); - let (mind_tx, mind_rx) = tokio::sync::mpsc::unbounded_channel(); - - let mut mind = Mind::new(config, ui_tx.clone(), turn_tx); - mind.init().await; - - let ui_agent = mind.agent.clone(); - let shared_mind = mind.shared.clone(); - let shared_context = mind.agent.lock().await.shared_context.clone(); - let shared_active_tools = mind.agent.lock().await.active_tools.clone(); - let turn_watch = mind.turn_watch(); - - tokio::spawn(async move { - mind.run(mind_rx, turn_rx).await; - }); - - crate::user::event_loop::run( - tui::App::new(String::new(), shared_context, shared_active_tools), - ui_agent, shared_mind, turn_watch, mind_tx, ui_tx, ui_rx, - ).await -} diff --git a/src/user/event_loop.rs b/src/user/event_loop.rs index aaaa56e..f3e47d7 100644 --- a/src/user/event_loop.rs +++ b/src/user/event_loop.rs @@ -18,6 +18,37 @@ use crate::user::ui_channel::{self, UiMessage}; pub use crate::mind::MindCommand; +/// Top-level entry point — creates Mind and UI, wires them together. +pub async fn start(cli: crate::user::CliArgs) -> Result<()> { + let (config, _figment) = crate::config::load_session(&cli)?; + + if config.app.debug { + unsafe { std::env::set_var("POC_DEBUG", "1") }; + } + + let (ui_tx, ui_rx) = ui_channel::channel(); + let (turn_tx, turn_rx) = tokio::sync::mpsc::channel(1); + let (mind_tx, mind_rx) = tokio::sync::mpsc::unbounded_channel(); + + let mut mind = crate::mind::Mind::new(config, ui_tx.clone(), turn_tx); + mind.init().await; + + let ui_agent = mind.agent.clone(); + let shared_mind = mind.shared.clone(); + let shared_context = mind.agent.lock().await.shared_context.clone(); + let shared_active_tools = mind.agent.lock().await.active_tools.clone(); + let turn_watch = mind.turn_watch(); + + tokio::spawn(async move { + mind.run(mind_rx, turn_rx).await; + }); + + run( + tui::App::new(String::new(), shared_context, shared_active_tools), + ui_agent, shared_mind, turn_watch, mind_tx, ui_tx, ui_rx, + ).await +} + fn send_help(ui_tx: &ui_channel::UiSender) { let commands = &[ ("/quit", "Exit consciousness"), diff --git a/src/user/mod.rs b/src/user/mod.rs index 1c6c118..46b0a16 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -757,7 +757,7 @@ pub async fn main() { return; } - if let Err(e) = crate::mind::run(cli).await { + if let Err(e) = crate::user::event_loop::start(cli).await { let _ = crossterm::terminal::disable_raw_mode(); let _ = crossterm::execute!( std::io::stdout(),