simplify compaction: Agent owns config, compact() reloads everything
Agent stores AppConfig and prompt_file, so compact() reloads identity internally — callers no longer pass system_prompt and personality. restore_from_log() loads entries and calls compact(). Remove soft compaction threshold and pre-compaction nudge (journal agent handles this). Remove /compact and /context commands (F10 debug screen replaces both). Inline do_compact, emergency_compact, trim_and_reload into compact(). Rename model_context_window to context_window, drop unused model parameter. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
d419587c1b
commit
af3929cc65
2 changed files with 53 additions and 179 deletions
|
|
@ -69,6 +69,9 @@ pub struct Agent {
|
|||
pub context: ContextState,
|
||||
/// Shared live context summary — TUI reads this directly for debug screen.
|
||||
pub shared_context: SharedContextState,
|
||||
/// App config — used to reload identity on compaction.
|
||||
app_config: crate::config::AppConfig,
|
||||
pub prompt_file: String,
|
||||
/// Stable session ID for memory-search dedup across turns.
|
||||
session_id: String,
|
||||
/// Agent orchestration state (surface-observe, journal, reflect).
|
||||
|
|
@ -90,6 +93,8 @@ impl Agent {
|
|||
client: ApiClient,
|
||||
system_prompt: String,
|
||||
personality: Vec<(String, String)>,
|
||||
app_config: crate::config::AppConfig,
|
||||
prompt_file: String,
|
||||
conversation_log: Option<ConversationLog>,
|
||||
shared_context: SharedContextState,
|
||||
) -> Self {
|
||||
|
|
@ -116,6 +121,8 @@ impl Agent {
|
|||
tokenizer,
|
||||
context,
|
||||
shared_context,
|
||||
app_config,
|
||||
prompt_file,
|
||||
session_id,
|
||||
agent_cycles,
|
||||
};
|
||||
|
|
@ -181,7 +188,7 @@ impl Agent {
|
|||
pub fn budget(&self) -> ContextBudget {
|
||||
let count_str = |s: &str| self.tokenizer.encode_with_special_tokens(s).len();
|
||||
let count_msg = |m: &Message| crate::thought::context::msg_token_count(&self.tokenizer, m);
|
||||
let window = crate::thought::context::model_context_window(&self.client.model);
|
||||
let window = crate::thought::context::context_window();
|
||||
self.context.budget(&count_str, &count_msg, window)
|
||||
}
|
||||
|
||||
|
|
@ -332,7 +339,7 @@ impl Agent {
|
|||
"[context overflow — compacting and retrying ({}/2)]",
|
||||
overflow_retries,
|
||||
)));
|
||||
self.emergency_compact();
|
||||
self.compact();
|
||||
continue;
|
||||
}
|
||||
if crate::thought::context::is_stream_error(&err) && empty_retries < 2 {
|
||||
|
|
@ -787,7 +794,7 @@ impl Agent {
|
|||
|
||||
// Walk backwards from cutoff, accumulating entries within 5% of context
|
||||
let count = |s: &str| self.tokenizer.encode_with_special_tokens(s).len();
|
||||
let context_window = crate::thought::context::model_context_window(&self.client.model);
|
||||
let context_window = crate::thought::context::context_window();
|
||||
let journal_budget = context_window * 5 / 100;
|
||||
dbg_log!("[journal] budget={} tokens ({}*5%)", journal_budget, context_window);
|
||||
|
||||
|
|
@ -899,21 +906,23 @@ impl Agent {
|
|||
self.last_prompt_tokens
|
||||
}
|
||||
|
||||
/// Build context window from conversation messages + journal.
|
||||
/// Used by both compact() (in-memory messages) and restore_from_log()
|
||||
/// (conversation log). The context window is always:
|
||||
/// identity + journal summaries + raw recent messages
|
||||
pub fn compact(&mut self, new_system_prompt: String, new_personality: Vec<(String, String)>) {
|
||||
self.context.system_prompt = new_system_prompt;
|
||||
self.context.personality = new_personality;
|
||||
self.do_compact();
|
||||
}
|
||||
|
||||
/// Dedup memory entries, trim to fit, reload journal for new time range.
|
||||
fn trim_and_reload(&mut self, entries: &[ConversationEntry]) {
|
||||
/// Rebuild the context window: reload identity, dedup, trim, reload journal.
|
||||
pub fn compact(&mut self) {
|
||||
// Reload identity from config
|
||||
match crate::config::reload_for_model(&self.app_config, &self.prompt_file) {
|
||||
Ok((system_prompt, personality)) => {
|
||||
self.context.system_prompt = system_prompt;
|
||||
self.context.personality = personality;
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("warning: failed to reload identity: {:#}", e);
|
||||
}
|
||||
}
|
||||
// Dedup memory, trim to budget, reload journal
|
||||
let entries = self.context.entries.clone();
|
||||
self.context.entries = crate::thought::context::trim_entries(
|
||||
&self.context,
|
||||
entries,
|
||||
&entries,
|
||||
&self.tokenizer,
|
||||
);
|
||||
self.load_startup_journal();
|
||||
|
|
@ -921,58 +930,24 @@ impl Agent {
|
|||
self.publish_context_state();
|
||||
}
|
||||
|
||||
/// Internal compaction — dedup memory entries and trim to fit.
|
||||
fn do_compact(&mut self) {
|
||||
let entries = self.context.entries.clone();
|
||||
self.trim_and_reload(&entries);
|
||||
}
|
||||
|
||||
/// Emergency compaction using stored config — called on context overflow.
|
||||
fn emergency_compact(&mut self) {
|
||||
self.do_compact();
|
||||
}
|
||||
|
||||
/// Restore from the conversation log. Builds the context window
|
||||
/// the same way compact() does — journal summaries for old messages,
|
||||
/// raw recent messages. This is the unified startup path.
|
||||
/// Returns true if the log had content to restore.
|
||||
pub fn restore_from_log(
|
||||
&mut self,
|
||||
system_prompt: String,
|
||||
personality: Vec<(String, String)>,
|
||||
) -> bool {
|
||||
self.context.system_prompt = system_prompt;
|
||||
self.context.personality = personality;
|
||||
|
||||
pub fn restore_from_log(&mut self) -> bool {
|
||||
let entries = match &self.conversation_log {
|
||||
Some(log) => match log.read_tail(512 * 1024) {
|
||||
Ok(entries) if !entries.is_empty() => {
|
||||
dbglog!("[restore] read {} entries from log tail", entries.len());
|
||||
entries
|
||||
}
|
||||
Ok(_) => {
|
||||
dbglog!("[restore] log exists but is empty");
|
||||
return false;
|
||||
}
|
||||
Err(e) => {
|
||||
dbglog!("[restore] failed to read log: {}", e);
|
||||
return false;
|
||||
}
|
||||
Some(log) => match log.read_tail(2 * 1024 * 1024) {
|
||||
Ok(entries) if !entries.is_empty() => entries,
|
||||
_ => return false,
|
||||
},
|
||||
None => {
|
||||
dbglog!("[restore] no conversation log configured");
|
||||
return false;
|
||||
}
|
||||
None => return false,
|
||||
};
|
||||
|
||||
// Filter out system messages, dedup memory, trim to fit
|
||||
let entries: Vec<ConversationEntry> = entries
|
||||
.into_iter()
|
||||
// Load extra — compact() will dedup, trim, reload identity + journal
|
||||
self.context.entries = entries.into_iter()
|
||||
.filter(|e| e.message().role != Role::System)
|
||||
.collect();
|
||||
self.trim_and_reload(&entries);
|
||||
dbglog!("[restore] {} entries, journal: {} entries",
|
||||
self.context.entries.len(), self.context.journal.len());
|
||||
self.compact();
|
||||
true
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue