agent: don't hold agent lock across I/O
The agent lock was held for the entire duration of turn() — including API streaming and tool dispatch awaits. This blocked the UI thread whenever it needed the lock (render tick, compaction check, etc.), causing 20+ second freezes. Fix: turn() takes Arc<Mutex<Agent>> and manages locking internally. Lock is held briefly for prepare/process phases, released during all I/O (streaming, tool awaits, sleep retries). Also: - check_compaction: spawns task instead of awaiting on event loop - start_memory_scoring: already spawned, no change needed - dispatch_tool_call_unlocked: drops lock before tool handle await - Subconscious screen: renders all agents from state dynamically (no more hardcoded SUBCONSCIOUS_AGENTS list) - Memory scoring shows n/m progress in snapshots Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
6fa881f811
commit
fb54488f30
5 changed files with 301 additions and 269 deletions
|
|
@ -127,8 +127,7 @@ impl Session {
|
|||
let result_tx = self.turn_tx.clone();
|
||||
self.turn_in_progress = true;
|
||||
self.turn_handle = Some(tokio::spawn(async move {
|
||||
let mut agent = agent.lock().await;
|
||||
let result = agent.turn(&input, &ui_tx, target).await;
|
||||
let result = Agent::turn(agent, &input, &ui_tx, target).await;
|
||||
let _ = result_tx.send((result, target)).await;
|
||||
}));
|
||||
}
|
||||
|
|
@ -209,40 +208,54 @@ impl Session {
|
|||
}
|
||||
|
||||
self.update_status();
|
||||
self.check_compaction().await;
|
||||
self.maybe_start_memory_scoring().await;
|
||||
self.check_compaction();
|
||||
self.start_memory_scoring();
|
||||
self.drain_pending();
|
||||
}
|
||||
|
||||
/// Spawn incremental memory scoring if not already running.
|
||||
async fn maybe_start_memory_scoring(&mut self) {
|
||||
{
|
||||
let agent = self.agent.lock().await;
|
||||
if agent.agent_cycles.memory_scoring_in_flight {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let (context, client, cursor) = {
|
||||
let mut agent = self.agent.lock().await;
|
||||
let cursor = agent.agent_cycles.memory_score_cursor;
|
||||
agent.agent_cycles.memory_scoring_in_flight = true;
|
||||
(agent.context.clone(), agent.client_clone(), cursor)
|
||||
};
|
||||
|
||||
/// Non-blocking — all async work happens in the spawned task.
|
||||
fn start_memory_scoring(&self) {
|
||||
let agent = self.agent.clone();
|
||||
let ui_tx = self.ui_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
// Check + snapshot under one brief lock
|
||||
let (context, client, cursor) = {
|
||||
let mut agent = agent.lock().await;
|
||||
if agent.agent_cycles.memory_scoring_in_flight {
|
||||
return;
|
||||
}
|
||||
let cursor = agent.agent_cycles.memory_score_cursor;
|
||||
agent.agent_cycles.memory_scoring_in_flight = true;
|
||||
// Count total unique memories
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for entry in &agent.context.entries {
|
||||
if let crate::agent::context::ConversationEntry::Memory { key, .. } = entry {
|
||||
seen.insert(key.clone());
|
||||
}
|
||||
}
|
||||
agent.agent_cycles.memory_total = seen.len();
|
||||
let _ = ui_tx.send(UiMessage::AgentUpdate(agent.agent_cycles.snapshots()));
|
||||
(agent.context.clone(), agent.client_clone(), cursor)
|
||||
};
|
||||
// Lock released — event loop is free
|
||||
let result = crate::agent::training::score_memories_incremental(
|
||||
&context, cursor, &client, &ui_tx,
|
||||
).await;
|
||||
|
||||
let mut agent = agent.lock().await;
|
||||
agent.agent_cycles.memory_scoring_in_flight = false;
|
||||
match result {
|
||||
Ok((new_cursor, scores)) => {
|
||||
// Brief lock — just update fields, no heavy work
|
||||
{
|
||||
let mut agent = agent.lock().await;
|
||||
agent.agent_cycles.memory_scoring_in_flight = false;
|
||||
if let Ok((new_cursor, ref scores)) = result {
|
||||
agent.agent_cycles.memory_score_cursor = new_cursor;
|
||||
agent.agent_cycles.memory_scores.extend(scores);
|
||||
agent.agent_cycles.memory_scores.extend(scores.clone());
|
||||
}
|
||||
}
|
||||
// Snapshot and log outside the lock
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let agent = agent.lock().await;
|
||||
let _ = ui_tx.send(UiMessage::AgentUpdate(agent.agent_cycles.snapshots()));
|
||||
}
|
||||
Err(e) => {
|
||||
|
|
@ -255,23 +268,25 @@ impl Session {
|
|||
}
|
||||
|
||||
/// Check if compaction is needed after a turn.
|
||||
async fn check_compaction(&mut self) {
|
||||
let mut agent_guard = self.agent.lock().await;
|
||||
let tokens = agent_guard.last_prompt_tokens();
|
||||
fn check_compaction(&self) {
|
||||
let threshold = compaction_threshold(&self.config.app);
|
||||
|
||||
if tokens > threshold {
|
||||
let _ = self.ui_tx.send(UiMessage::Info(format!(
|
||||
"[compaction: {}K > {}K threshold]",
|
||||
tokens / 1000,
|
||||
threshold / 1000,
|
||||
)));
|
||||
agent_guard.compact();
|
||||
let _ = self.ui_tx.send(UiMessage::Info(
|
||||
"[compacted — journal + recent messages]".into(),
|
||||
));
|
||||
self.send_context_info();
|
||||
}
|
||||
let agent = self.agent.clone();
|
||||
let ui_tx = self.ui_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut agent_guard = agent.lock().await;
|
||||
let tokens = agent_guard.last_prompt_tokens();
|
||||
if tokens > threshold {
|
||||
let _ = ui_tx.send(UiMessage::Info(format!(
|
||||
"[compaction: {}K > {}K threshold]",
|
||||
tokens / 1000,
|
||||
threshold / 1000,
|
||||
)));
|
||||
agent_guard.compact();
|
||||
let _ = ui_tx.send(UiMessage::Info(
|
||||
"[compacted — journal + recent messages]".into(),
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Send any consolidated pending input as a single turn.
|
||||
|
|
@ -791,6 +806,7 @@ pub async fn run(cli: crate::user::CliArgs) -> Result<()> {
|
|||
|
||||
let mut session = Session::new(agent, config, ui_tx.clone(), turn_tx);
|
||||
session.update_status();
|
||||
session.start_memory_scoring(); // also sends initial agent snapshots
|
||||
session.send_context_info();
|
||||
|
||||
// Start observation socket
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue