Agent/AgentState split complete — separate context and state locks

Agent is now Arc<Agent> (immutable config). ContextState and AgentState
have separate tokio::sync::Mutex locks. The parser locks only context,
tool dispatch locks only state. No contention between the two.

All callers migrated: mind/, user/, tools/, oneshot, dmn, learn.
28 tests pass, zero errors.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-08 15:47:21 -04:00
parent 1d61b091b0
commit 0b9813431a
8 changed files with 156 additions and 159 deletions

View file

@ -179,9 +179,9 @@ async fn start(cli: crate::user::CliArgs) -> Result<()> {
let (turn_tx, turn_rx) = tokio::sync::mpsc::channel(1);
let (mind_tx, mind_rx) = tokio::sync::mpsc::unbounded_channel();
let mind = crate::mind::Mind::new(config, turn_tx);
let mind = crate::mind::Mind::new(config, turn_tx).await;
let shared_active_tools = mind.agent.lock().await.active_tools.clone();
let shared_active_tools = mind.agent.state.lock().await.active_tools.clone();
let mut result = Ok(());
tokio_scoped::scope(|s| {
@ -203,7 +203,7 @@ async fn start(cli: crate::user::CliArgs) -> Result<()> {
}
fn hotkey_cycle_reasoning(mind: &crate::mind::Mind) {
if let Ok(mut ag) = mind.agent.try_lock() {
if let Ok(mut ag) = mind.agent.state.try_lock() {
let next = match ag.reasoning_effort.as_str() {
"none" => "low",
"low" => "high",
@ -221,17 +221,17 @@ fn hotkey_cycle_reasoning(mind: &crate::mind::Mind) {
}
async fn hotkey_kill_processes(mind: &crate::mind::Mind) {
let mut ag = mind.agent.lock().await;
let active_tools = ag.active_tools.clone();
let mut st = mind.agent.state.lock().await;
let active_tools = st.active_tools.clone();
let mut tools = active_tools.lock().unwrap();
if tools.is_empty() {
ag.notify("no running tools");
st.notify("no running tools");
} else {
let count = tools.len();
for entry in tools.drain(..) {
entry.handle.abort();
}
ag.notify(format!("killed {} tools", count));
st.notify(format!("killed {} tools", count));
}
}
@ -259,7 +259,7 @@ fn hotkey_cycle_autonomy(mind: &crate::mind::Mind) {
};
s.dmn_turns = 0;
drop(s);
if let Ok(mut ag) = mind.agent.try_lock() {
if let Ok(mut ag) = mind.agent.state.try_lock() {
ag.notify(format!("DMN → {}", label));
}
}
@ -325,13 +325,13 @@ async fn run(
}
});
let agent_changed = agent.lock().await.changed.clone();
let agent_changed = agent.state.lock().await.changed.clone();
let mut turn_watch = mind.turn_watch();
let mut pending: Vec<ratatui::crossterm::event::Event> = Vec::new();
terminal.hide_cursor()?;
if let Ok(mut ag) = agent.try_lock() { ag.notify("consciousness v0.3"); }
if let Ok(mut ag) = agent.state.try_lock() { ag.notify("consciousness v0.3"); }
// Initial render
{
@ -378,8 +378,8 @@ async fn run(
app.agent_state = mind.subconscious_snapshots().await;
app.walked_count = mind.subconscious_walked().await.len();
if !startup_done {
if let Ok(mut ag) = agent.try_lock() {
let model = ag.model().to_string();
if let Ok(mut ag) = agent.state.try_lock() {
let model = agent.model().to_string();
ag.notify(format!("model: {}", model));
startup_done = true;
}