Journal: walk backwards with token budget, not load-all

Iterate journal entries backwards from the conversation cutoff,
accumulating within ~10K token budget (~8% of context window).
Stops when budget is full, keeps at least one entry. Much more
efficient than loading all entries and trimming.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-02 01:50:36 -04:00
parent e4285ba75f
commit 7776d87d53

View file

@ -800,51 +800,51 @@ impl Agent {
.collect(); .collect();
journal_nodes.sort_by_key(|n| n.created_at); journal_nodes.sort_by_key(|n| n.created_at);
// Filter: entries older than oldest conversation message, // Find the cutoff index — entries older than conversation, plus one overlap
// plus the first one that overlaps (no gap) let cutoff_idx = if let Some(cutoff) = oldest_msg_ts {
let entries: Vec<journal::JournalEntry> = if let Some(cutoff) = oldest_msg_ts { let mut idx = journal_nodes.len();
let mut result = Vec::new(); for (i, node) in journal_nodes.iter().enumerate() {
let mut included_overlap = false;
for node in &journal_nodes {
let ts = chrono::DateTime::from_timestamp(node.created_at, 0) let ts = chrono::DateTime::from_timestamp(node.created_at, 0)
.unwrap_or_default(); .unwrap_or_default();
if ts < cutoff || !included_overlap {
result.push(journal::JournalEntry {
timestamp: ts,
content: node.content.clone(),
});
if ts >= cutoff { if ts >= cutoff {
included_overlap = true; idx = i + 1; // include this overlapping entry
}
} else {
break; break;
} }
} }
result idx
} else { } else {
// No conversation yet — include recent entries for orientation journal_nodes.len()
let n = 20; };
let skip = journal_nodes.len().saturating_sub(n);
journal_nodes.iter().skip(skip).map(|node| { // Walk backwards from cutoff, accumulating entries within token budget
journal::JournalEntry { let count = |s: &str| self.tokenizer.encode_with_special_tokens(s).len();
let journal_budget_tokens = 10_000; // ~8% of 128K context
let mut entries = Vec::new();
let mut total_tokens = 0;
for node in journal_nodes[..cutoff_idx].iter().rev() {
let tokens = count(&node.content);
if total_tokens + tokens > journal_budget_tokens && !entries.is_empty() {
break;
}
entries.push(journal::JournalEntry {
timestamp: chrono::DateTime::from_timestamp(node.created_at, 0) timestamp: chrono::DateTime::from_timestamp(node.created_at, 0)
.unwrap_or_default(), .unwrap_or_default(),
content: node.content.clone(), content: node.content.clone(),
});
total_tokens += tokens;
} }
}).collect() entries.reverse(); // chronological order
};
if entries.is_empty() { if entries.is_empty() {
return; return;
} }
let count = |s: &str| self.tokenizer.encode_with_special_tokens(s).len();
let context_message = self.context.render_context_message(); let context_message = self.context.render_context_message();
let plan = crate::agent::context::plan_context( let plan = crate::agent::context::plan_context(
&self.context.system_prompt, &self.context.system_prompt,
&context_message, &context_message,
&[], // no conversation yet &[],
&entries, &entries,
&self.client.model, &self.client.model,
&count, &count,