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