WIP: ContextEntry/ContextSection data structures for incremental token counting

New types — not yet wired to callers:

- ContextEntry: wraps ConversationEntry with cached token count and
  timestamp
- ContextSection: named group of entries with cached token total.
  Private entries/tokens, read via entries()/tokens().
  Mutation via push(entry), set(index, entry), del(index).
- ContextState: system/identity/journal/conversation sections + working_stack
- ConversationEntry::System variant for system prompt entries

Token counting happens once at push time. Sections maintain their
totals incrementally via push/set/del. No more recomputing from
scratch on every budget check.

Does not compile — callers need updating.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-07 20:15:31 -04:00
parent 776ac527f1
commit 62996e27d7
10 changed files with 450 additions and 403 deletions

View file

@ -16,7 +16,8 @@ use ratatui::{
use super::{App, ScreenView, screen_legend};
use super::widgets::{SectionTree, pane_block_focused, render_scrollable, tree_legend, format_age, format_ts_age};
use crate::agent::context::ContextSection;
use crate::agent::context::{ContextSection, ContextEntry, ConversationEntry};
use crate::agent::api::Message;
#[derive(Clone, Copy, PartialEq)]
enum Pane { Agents, Outputs, History, Context }
@ -135,12 +136,13 @@ impl SubconsciousScreen {
None => return Vec::new(),
};
snap.state.iter().map(|(key, val)| {
ContextSection {
name: key.clone(),
let mut section = ContextSection::new(key.clone());
section.push(ContextEntry {
entry: ConversationEntry::Message(Message::user(val)),
tokens: 0,
content: val.clone(),
children: Vec::new(),
}
timestamp: None,
});
section
}).collect()
}
@ -151,7 +153,15 @@ impl SubconsciousScreen {
};
snap.forked_agent.as_ref()
.and_then(|agent| agent.try_lock().ok())
.map(|ag| ag.conversation_sections_from(snap.fork_point))
.map(|ag| {
// Build a single section from the forked conversation entries
let entries = ag.conversation_entries_from(snap.fork_point);
let mut section = ContextSection::new("Conversation");
for e in entries {
section.push(e.clone());
}
vec![section]
})
.unwrap_or_default()
}
@ -172,7 +182,7 @@ impl SubconsciousScreen {
.unwrap_or_else(|| "".to_string());
let entries = snap.forked_agent.as_ref()
.and_then(|a| a.try_lock().ok())
.map(|ag| ag.context.entries.len().saturating_sub(snap.fork_point))
.map(|ag| ag.context.conversation.len().saturating_sub(snap.fork_point))
.unwrap_or(0);
ListItem::from(Line::from(vec![
Span::styled(&snap.name, Style::default().fg(Color::Gray)),