Compute ContextBudget on demand from typed sources
Remove cached context_budget field and measure_budget(). Budget is computed on demand via budget() which calls ContextState::budget(). Each bucket counted from its typed source. Memory split from conversation by identifying memory tool calls. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
acdfbeeac3
commit
eb4dae04cb
3 changed files with 72 additions and 34 deletions
|
|
@ -62,8 +62,6 @@ pub struct Agent {
|
|||
pub reasoning_effort: String,
|
||||
/// Persistent conversation log — append-only record of all messages.
|
||||
conversation_log: Option<ConversationLog>,
|
||||
/// Current context window budget breakdown.
|
||||
pub context_budget: ContextBudget,
|
||||
/// BPE tokenizer for token counting (cl100k_base — close enough
|
||||
/// for Claude and Qwen budget allocation, ~85-90% count accuracy).
|
||||
tokenizer: CoreBPE,
|
||||
|
|
@ -116,7 +114,6 @@ impl Agent {
|
|||
process_tracker: ProcessTracker::new(),
|
||||
reasoning_effort: "none".to_string(),
|
||||
conversation_log,
|
||||
context_budget: ContextBudget::default(),
|
||||
tokenizer,
|
||||
context,
|
||||
shared_context,
|
||||
|
|
@ -126,7 +123,6 @@ impl Agent {
|
|||
|
||||
agent.load_startup_journal();
|
||||
agent.load_working_stack();
|
||||
agent.measure_budget();
|
||||
agent.publish_context_state();
|
||||
agent
|
||||
}
|
||||
|
|
@ -183,28 +179,11 @@ impl Agent {
|
|||
/// Push a context-only message (system prompt, identity context,
|
||||
/// journal summaries). Not logged — these are reconstructed on
|
||||
/// every startup/compaction.
|
||||
/// Measure context window usage by category. Uses the BPE tokenizer
|
||||
/// for direct token counting (no chars/4 approximation).
|
||||
fn measure_budget(&mut self) {
|
||||
let count = |s: &str| self.tokenizer.encode_with_special_tokens(s).len();
|
||||
|
||||
let id_tokens = count(&self.context.system_prompt)
|
||||
+ self.context.personality.iter()
|
||||
.map(|(_, content)| count(content)).sum::<usize>();
|
||||
let jnl_tokens: usize = self.context.journal.iter()
|
||||
.map(|e| count(&e.content)).sum();
|
||||
let mem_tokens: usize = self.context.loaded_nodes.iter()
|
||||
.map(|node| count(&node.render())).sum();
|
||||
let conv_tokens: usize = self.context.messages.iter()
|
||||
.map(|m| crate::agent::context::msg_token_count(&self.tokenizer, m)).sum();
|
||||
|
||||
self.context_budget = ContextBudget {
|
||||
identity_tokens: id_tokens,
|
||||
memory_tokens: mem_tokens,
|
||||
journal_tokens: jnl_tokens,
|
||||
conversation_tokens: conv_tokens,
|
||||
window_tokens: crate::agent::context::model_context_window(&self.client.model),
|
||||
};
|
||||
pub fn budget(&self) -> ContextBudget {
|
||||
let count_str = |s: &str| self.tokenizer.encode_with_special_tokens(s).len();
|
||||
let count_msg = |m: &Message| crate::agent::context::msg_token_count(&self.tokenizer, m);
|
||||
let window = crate::agent::context::model_context_window(&self.client.model);
|
||||
self.context.budget(&count_str, &count_msg, window)
|
||||
}
|
||||
|
||||
/// Send a user message and run the agent loop until the model
|
||||
|
|
@ -372,7 +351,7 @@ impl Agent {
|
|||
|
||||
if let Some(usage) = &usage {
|
||||
self.last_prompt_tokens = usage.prompt_tokens;
|
||||
self.measure_budget();
|
||||
|
||||
self.publish_context_state();
|
||||
let _ = ui_tx.send(UiMessage::StatusUpdate(StatusInfo {
|
||||
dmn_state: String::new(), // filled by main loop
|
||||
|
|
@ -382,7 +361,7 @@ impl Agent {
|
|||
completion_tokens: usage.completion_tokens,
|
||||
model: self.client.model.clone(),
|
||||
turn_tools: 0, // tracked by TUI from ToolCall messages
|
||||
context_budget: self.context_budget.status_string(),
|
||||
context_budget: self.budget().status_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -547,7 +526,7 @@ impl Agent {
|
|||
if output.text.starts_with("Error:") {
|
||||
ds.tool_errors += 1;
|
||||
}
|
||||
self.measure_budget();
|
||||
|
||||
self.publish_context_state();
|
||||
return;
|
||||
}
|
||||
|
|
@ -826,7 +805,7 @@ impl Agent {
|
|||
|
||||
/// Called after any change to context state (working stack, etc).
|
||||
fn refresh_context_state(&mut self) {
|
||||
self.measure_budget();
|
||||
|
||||
self.publish_context_state();
|
||||
self.save_working_stack();
|
||||
}
|
||||
|
|
@ -849,8 +828,16 @@ impl Agent {
|
|||
|
||||
/// Push the current context summary to the shared state for the TUI to read.
|
||||
fn publish_context_state(&self) {
|
||||
let summary = self.context_state_summary();
|
||||
if let Ok(mut dbg) = std::fs::OpenOptions::new().create(true).append(true)
|
||||
.open("/tmp/poc-journal-debug.log") {
|
||||
use std::io::Write;
|
||||
for s in &summary {
|
||||
let _ = writeln!(dbg, "[publish] {} ({} tokens, {} children)", s.name, s.tokens, s.children.len());
|
||||
}
|
||||
}
|
||||
if let Ok(mut state) = self.shared_context.write() {
|
||||
*state = self.context_state_summary();
|
||||
*state = summary;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -978,7 +965,7 @@ impl Agent {
|
|||
self.context.journal = journal::parse_journal_text(&journal);
|
||||
self.context.messages = messages;
|
||||
self.last_prompt_tokens = 0;
|
||||
self.measure_budget();
|
||||
|
||||
self.publish_context_state();
|
||||
}
|
||||
|
||||
|
|
@ -1041,7 +1028,7 @@ impl Agent {
|
|||
self.context.messages = messages;
|
||||
dbglog!("[restore] built context window: {} messages", self.context.messages.len());
|
||||
self.last_prompt_tokens = 0;
|
||||
self.measure_budget();
|
||||
|
||||
self.publish_context_state();
|
||||
true
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue