mind: zero UI dependencies — init() + run() split

Mind::init() restores conversation from log and starts scoring.
No UiMessages sent. The UI event loop reads Mind's state after
init and displays startup info (model, restored conversation)
by reading the agent directly.

mind/mod.rs has zero UiMessage imports or sends. Complete
separation between cognitive state machine and user interface.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 04:02:16 -04:00
parent 3ee1aa69b0
commit 9d597b5eff
2 changed files with 25 additions and 40 deletions

View file

@ -26,7 +26,7 @@ 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, StatusInfo, StreamTarget, UiMessage};
use crate::user::ui_channel::{self, StreamTarget};
use crate::subconscious::learn;
/// Compaction threshold — context is rebuilt when prompt tokens exceed this.
@ -192,6 +192,17 @@ impl Mind {
Self { agent, shared, config, ui_tx, turn_tx, turn_handle: None, turn_watch }
}
/// Initialize — restore conversation from log, start background agents.
pub async fn init(&mut self) {
let mut ag = self.agent.lock().await;
ag.restore_from_log();
drop(ag);
if !self.config.no_agents {
self.start_memory_scoring();
}
}
pub fn turn_watch(&self) -> tokio::sync::watch::Receiver<bool> {
self.turn_watch.subscribe()
}
@ -381,23 +392,11 @@ pub async fn run(cli: crate::user::CliArgs) -> Result<()> {
let shared_context = ui_channel::shared_context_state();
let shared_active_tools = ui_channel::shared_active_tools();
// Startup info
let _ = ui_tx.send(UiMessage::Info("consciousness v0.3 (tui)".into()));
let _ = ui_tx.send(UiMessage::Info(format!(
" model: {} (available: {})", config.model, config.app.model_names().join(", "),
)));
let client = ApiClient::new(&config.api_base, &config.api_key, &config.model);
let _ = ui_tx.send(UiMessage::Info(format!(" api: {} ({})", config.api_base, client.backend_label())));
let _ = ui_tx.send(UiMessage::Info(format!(
" context: {}K chars ({} config, {} memory files)",
config.context_parts.iter().map(|(_, c)| c.len()).sum::<usize>() / 1024,
config.config_file_count, config.memory_file_count,
)));
let conversation_log_path = config.session_dir.join("conversation.jsonl");
let conversation_log = log::ConversationLog::new(conversation_log_path.clone())
.expect("failed to create conversation log");
let _ = ui_tx.send(UiMessage::Info(format!(" log: {}", conversation_log.path().display())));
let agent = Arc::new(Mutex::new(Agent::new(
client,
@ -410,37 +409,12 @@ pub async fn run(cli: crate::user::CliArgs) -> Result<()> {
shared_active_tools.clone(),
)));
// Restore conversation from log
{
let mut agent_guard = agent.lock().await;
if agent_guard.restore_from_log() {
ui_channel::replay_session_to_ui(agent_guard.entries(), &ui_tx);
let _ = ui_tx.send(UiMessage::Info("--- restored from conversation log ---".into()));
}
}
// Send initial budget to status bar
{
let agent_guard = agent.lock().await;
let _ = ui_tx.send(UiMessage::StatusUpdate(StatusInfo {
dmn_state: "resting".to_string(),
dmn_turns: 0, dmn_max_turns: 0,
prompt_tokens: 0, completion_tokens: 0,
model: agent_guard.model().to_string(),
turn_tools: 0,
context_budget: agent_guard.budget().status_string(),
}));
}
let (turn_tx, turn_rx) = mpsc::channel::<(Result<TurnResult>, StreamTarget)>(1);
let no_agents = config.no_agents;
let shared_mind = shared_mind_state(config.app.dmn.max_turns);
crate::user::event_loop::send_context_info(&config, &ui_tx);
let mut mind = Mind::new(agent, shared_mind.clone(), config, ui_tx.clone(), turn_tx);
if !no_agents {
mind.start_memory_scoring();
}
mind.init().await;
// Start observation socket
let socket_path = mind.config.session_dir.join("agent.sock");

View file

@ -181,7 +181,6 @@ pub async fn run(
mut app: tui::App,
agent: Arc<Mutex<Agent>>,
shared_mind: crate::mind::SharedMindState,
turn_watch: tokio::sync::watch::Receiver<bool>,
mind_tx: tokio::sync::mpsc::UnboundedSender<MindCommand>,
ui_tx: ui_channel::UiSender,
@ -202,6 +201,18 @@ pub async fn run(
terminal.hide_cursor()?;
// Startup info
let _ = ui_tx.send(UiMessage::Info("consciousness v0.3 (tui)".into()));
{
let ag = agent.lock().await;
let _ = ui_tx.send(UiMessage::Info(format!(" model: {}", ag.model())));
// Replay restored conversation to UI
if !ag.entries().is_empty() {
ui_channel::replay_session_to_ui(ag.entries(), &ui_tx);
let _ = ui_tx.send(UiMessage::Info("--- restored from conversation log ---".into()));
}
}
// Initial render
app.drain_messages(&mut ui_rx);
terminal.draw(|f| app.draw(f))?;