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

@ -376,7 +376,7 @@ pub(crate) struct InteractScreen {
call_timeout_secs: u64,
// State sync with agent — double buffer
last_generation: u64,
last_entries: Vec<crate::agent::context::ConversationEntry>,
last_entries: Vec<crate::agent::context::ContextEntry>,
pending_display_count: usize,
/// Reference to agent for state sync
agent: std::sync::Arc<tokio::sync::Mutex<crate::agent::Agent>>,
@ -482,21 +482,21 @@ impl InteractScreen {
for i in (0..self.last_entries.len()).rev() {
// Check if this entry is out of bounds or doesn't match
let matches = i < entries.len() && self.last_entries[i] == entries[i];
let matches = i < entries.len() && self.last_entries[i].entry == entries[i].entry;
if !matches {
pop = i;
}
// Only stop at assistant if it matches - otherwise keep going
if matches && self.last_entries[i].message().role == Role::Assistant {
if matches && self.last_entries[i].entry.message().role == Role::Assistant {
break;
}
}
while self.last_entries.len() > pop {
let popped = self.last_entries.pop().unwrap();
for (target, _, _) in Self::route_entry(&popped) {
for (target, _, _) in Self::route_entry(&popped.entry) {
match target {
PaneTarget::Conversation | PaneTarget::ConversationAssistant
=> self.conversation.pop_line(),
@ -510,7 +510,7 @@ impl InteractScreen {
// Phase 2: push new entries
let start = self.last_entries.len();
for entry in entries.iter().skip(start) {
for (target, text, marker) in Self::route_entry(entry) {
for (target, text, marker) in Self::route_entry(&entry.entry) {
match target {
PaneTarget::Conversation => {
self.conversation.current_color = Color::Cyan;