Fix context budgeting and compaction

- Budget now counts exact message tokens matching what assemble_api_messages
  sends, not raw string content. Eliminates undercounting from formatting
  overhead (journal headers, personality separators, working stack).

- Load journal before trimming so trim accounts for journal cost.

- Compact before every turn, not just after turn completion. Prevents
  agent_cycle surfaced memories from pushing context over budget.

- Move agent_cycle orchestration from Agent::turn to Mind::start_turn —
  surfaced memories and reflections now precede the user message.

- Move AgentCycleState from Agent to Mind — it's orchestration, not
  per-agent state. memory_scoring_in_flight and memory_scores stay on
  Agent where they belong.

- Tag DMN entries as ConversationEntry::Dmn — compaction evicts them
  first since they're ephemeral. Compaction also prefers evicting
  memories over conversation when memories exceed 50% of entry tokens.

- Kill /retry slash command.

Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-06 21:48:12 -04:00
parent c22b8c3a6f
commit d5e6f55da9
5 changed files with 194 additions and 170 deletions

View file

@ -104,10 +104,6 @@ pub struct AgentCycleState {
log_file: Option<File>,
pub agents: Vec<AgentInfo>,
pub last_output: AgentCycleOutput,
/// Whether incremental memory scoring is currently running.
pub memory_scoring_in_flight: bool,
/// Latest per-memory scores from incremental scoring.
pub memory_scores: Vec<(String, f64)>,
}
const AGENT_CYCLE_NAMES: &[&str] = &["surface-observe", "journal", "reflect"];
@ -134,8 +130,6 @@ impl AgentCycleState {
reflection: None,
sleep_secs: None,
},
memory_scoring_in_flight: false,
memory_scores: Vec::new(),
}
}
@ -180,17 +174,17 @@ impl AgentCycleState {
}
}
pub fn snapshots(&self) -> Vec<AgentSnapshot> {
pub fn snapshots(&self, scoring_in_flight: bool, scored_count: usize) -> Vec<AgentSnapshot> {
let mut snaps: Vec<AgentSnapshot> = self.agents.iter().map(|a| a.snapshot()).collect();
snaps.push(AgentSnapshot {
name: "memory-scoring".to_string(),
pid: None,
phase: if self.memory_scoring_in_flight {
phase: if scoring_in_flight {
Some("scoring...".into())
} else if self.memory_scores.is_empty() {
} else if scored_count == 0 {
None
} else {
Some(format!("{} scored", self.memory_scores.len()))
Some(format!("{} scored", scored_count))
},
log_path: None,
});
@ -210,7 +204,7 @@ impl AgentCycleState {
/// Save current state for the Claude Code hook path.
pub fn save(&self, session_id: &str) {
let state = SavedAgentState { agents: self.snapshots() };
let state = SavedAgentState { agents: self.snapshots(false, 0) };
state.save(session_id);
}