Unify budget and context state — single source of truth

Kill ContextBudget and recompute_budget entirely. Budget percentages,
used token counts, and compaction threshold checks now all derive from
the ContextSection tree built by context_state_summary(). This
eliminates the stale-budget bug where the cached budget diverged from
actual context contents.

Also: remove MindCommand::Turn — user input flows through
shared_mind.input exclusively. Mind::start_turn() atomically moves
text from pending input into the agent's context and spawns the turn.
Kill /retry. Make Agent::turn() take no input parameter.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-06 20:34:51 -04:00
parent f63c341f94
commit c22b8c3a6f
4 changed files with 83 additions and 117 deletions

View file

@ -13,7 +13,6 @@ use ratatui::{
};
use super::{App, ScreenView, screen_legend};
use crate::mind::StreamTarget;
use crate::mind::MindCommand;
// --- Slash command table ---
@ -35,28 +34,6 @@ fn commands() -> Vec<SlashCommand> { vec![
handler: |s, _| {
if let Ok(mut ag) = s.agent.try_lock() { ag.notify("saved"); }
} },
SlashCommand { name: "/retry", help: "Re-run last turn",
handler: |s, _| {
let agent = s.agent.clone();
let mind_tx = s.mind_tx.clone();
tokio::spawn(async move {
let _act = crate::agent::start_activity(&agent, "retrying...").await;
let mut ag = agent.lock().await;
let entries = ag.entries_mut();
let mut last_user_text = None;
while let Some(entry) = entries.last() {
if entry.message().role == crate::agent::api::types::Role::User {
last_user_text = Some(entries.pop().unwrap().message().content_text().to_string());
break;
}
entries.pop();
}
drop(ag);
if let Some(text) = last_user_text {
let _ = mind_tx.send(MindCommand::Turn(text, StreamTarget::Conversation));
}
});
} },
SlashCommand { name: "/model", help: "Show/switch model (/model <name>)",
handler: |s, arg| {
if arg.is_empty() {
@ -555,8 +532,9 @@ impl InteractScreen {
return;
}
// Regular input → queue to Mind
// Regular input → queue to Mind, then wake it
self.shared_mind.lock().unwrap().input.push(input.to_string());
let _ = self.mind_tx.send(MindCommand::None);
}
@ -848,7 +826,8 @@ impl ScreenView for InteractScreen {
agent.expire_activities();
app.status.prompt_tokens = agent.last_prompt_tokens();
app.status.model = agent.model().to_string();
app.status.context_budget = agent.context.budget.status_string();
let sections = agent.shared_context.read().map(|s| s.clone()).unwrap_or_default();
app.status.context_budget = crate::agent::context::sections_budget_string(&sections);
app.activity = agent.activities.last()
.map(|a| a.label.clone())
.unwrap_or_default();