mind: use tokio-scoped for Mind/UI loop lifetimes

Both event loops borrow &mind through a scoped spawn — no Arc on
Mind needed. Interior Arcs on agent/shared stay (background spawns
need 'static), but event_loop::run() now takes &Arc refs instead
of cloned Arcs.

Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
Kent Overstreet 2026-04-05 04:53:33 -04:00
parent aae9687de2
commit 5eaba3c951
3 changed files with 30 additions and 12 deletions

11
Cargo.lock generated
View file

@ -2953,6 +2953,7 @@ dependencies = [
"tiktoken-rs", "tiktoken-rs",
"tokio", "tokio",
"tokio-rustls", "tokio-rustls",
"tokio-scoped",
"tokio-util", "tokio-util",
"toml", "toml",
"tracing", "tracing",
@ -4371,6 +4372,16 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-scoped"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4beb8ba13bc53ac53ce1d52b42f02e5d8060f0f42138862869beb769722b256"
dependencies = [
"tokio",
"tokio-stream",
]
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.18" version = "0.1.18"

View file

@ -65,6 +65,7 @@ webpki-roots = "1"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2" tracing-appender = "0.2"
tokio-scoped = "0.2.0"
[build-dependencies] [build-dependencies]
capnpc = "0.20" capnpc = "0.20"

View file

@ -31,22 +31,28 @@ pub async fn start(cli: crate::user::CliArgs) -> Result<()> {
let (mind_tx, mind_rx) = tokio::sync::mpsc::unbounded_channel(); let (mind_tx, mind_rx) = tokio::sync::mpsc::unbounded_channel();
let mind = crate::mind::Mind::new(config, ui_tx.clone(), turn_tx); let 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_context = mind.agent.lock().await.shared_context.clone();
let shared_active_tools = mind.agent.lock().await.active_tools.clone(); let shared_active_tools = mind.agent.lock().await.active_tools.clone();
let turn_watch = mind.turn_watch(); let turn_watch = mind.turn_watch();
tokio::spawn(async move { let mut result = Ok(());
mind.run(mind_rx, turn_rx).await; tokio_scoped::scope(|s| {
}); // Mind event loop — init + run
s.spawn(async {
mind.init().await;
mind.run(mind_rx, turn_rx).await;
});
run( // UI event loop
tui::App::new(String::new(), shared_context, shared_active_tools), s.spawn(async {
ui_agent, shared_mind, turn_watch, mind_tx, ui_tx, ui_rx, result = run(
).await tui::App::new(String::new(), shared_context, shared_active_tools),
&mind.agent, &mind.shared, turn_watch, mind_tx, ui_tx, ui_rx,
).await;
});
});
result
} }
fn send_help(ui_tx: &ui_channel::UiSender) { fn send_help(ui_tx: &ui_channel::UiSender) {
@ -210,8 +216,8 @@ pub async fn cmd_switch_model(
pub async fn run( pub async fn run(
mut app: tui::App, mut app: tui::App,
agent: Arc<Mutex<Agent>>, agent: &Arc<Mutex<Agent>>,
shared_mind: crate::mind::SharedMindState, shared_mind: &crate::mind::SharedMindState,
turn_watch: tokio::sync::watch::Receiver<bool>, turn_watch: tokio::sync::watch::Receiver<bool>,
mind_tx: tokio::sync::mpsc::UnboundedSender<MindCommand>, mind_tx: tokio::sync::mpsc::UnboundedSender<MindCommand>,
ui_tx: ui_channel::UiSender, ui_tx: ui_channel::UiSender,