Log full agent context window on completion

Save all context sections (system, identity, journal, conversation)
to per-agent log files for both subconscious and unconscious agents.

Co-Authored-By: ProofOfConcept <poc@bcachefs.org>
This commit is contained in:
Kent Overstreet 2026-04-10 03:28:00 -04:00
parent be44a3bb0d
commit b4dfd3c092
4 changed files with 55 additions and 15 deletions

View file

@ -191,9 +191,13 @@ impl State {
fn push_message(&mut self, line: String, urgency: u8, channel: &str) {
// Store in per-channel log
let ch = channel.to_string();
self.channel_logs
.entry(channel.to_string())
.or_insert_with(ChannelLog::new)
.entry(ch.clone())
.or_insert_with(|| {
let target = channel_to_target(&ch);
channel_log::load_disk_log(&log_dir(), &target)
})
.push(line.clone());
// Notify all subscribers
@ -221,7 +225,34 @@ impl State {
}
async fn send_privmsg(&mut self, target: &str, msg: &str) -> io::Result<()> {
self.send_raw(&format!("PRIVMSG {target} :{msg}")).await
// IRC max line = 512 bytes including CRLF. The server prepends
// our prefix when relaying: ":nick!~user@host PRIVMSG target :msg\r\n"
// User is often ~nick (nick_len + 1). Host is up to 63 bytes.
let nick_len = self.config.nick.len();
let overhead = 1 + nick_len + 2 + nick_len + 1 + 63
+ " PRIVMSG ".len() + target.len() + " :".len() + 2;
let max_msg = 512_usize.saturating_sub(overhead);
if max_msg == 0 {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "target too long"));
}
// Split on UTF-8 char boundaries
let mut remaining = msg;
while !remaining.is_empty() {
let split_at = if remaining.len() <= max_msg {
remaining.len()
} else {
// Find last char boundary at or before max_msg
let mut i = max_msg;
while i > 0 && !remaining.is_char_boundary(i) { i -= 1; }
if i == 0 { max_msg } else { i }
};
let (chunk, rest) = remaining.split_at(split_at);
self.send_raw(&format!("PRIVMSG {target} :{chunk}")).await?;
remaining = rest;
}
Ok(())
}
}
@ -389,11 +420,11 @@ async fn register_and_read<R: tokio::io::AsyncRead + Unpin>(
if let Err(e) = state.borrow_mut().send_raw(&format!("JOIN {ch}")).await {
warn!("irc: failed to join {ch}: {e}");
}
// Create log entry so channel appears in list()
// Load history from disk so recv has scrollback
let key = format!("irc.{ch}");
state.borrow_mut().channel_logs
.entry(key)
.or_insert_with(ChannelLog::new);
.or_insert_with(|| channel_log::load_disk_log(&log_dir(), ch));
}
}
@ -536,7 +567,10 @@ impl channel_server::Server for ChannelServerImpl {
};
state.borrow_mut().channel_logs
.entry(channel.clone())
.or_insert_with(ChannelLog::new)
.or_insert_with(|| {
let target = channel_to_target(&channel);
channel_log::load_disk_log(&log_dir(), &target)
})
.push_own(log_line);
Ok(())