journal: wire standalone agent into hook cycle

Add journal_cycle() to memory_search.rs, triggered every 20KB of
transcript growth. Runs independently of the surface-observe pipeline
so it doesn't depend on the 5-step pipeline surviving bail checks.

Journal agent doesn't inject output into conversation context (unlike
surface and reflect) — it just writes episodic memory entries.
This commit is contained in:
ProofOfConcept 2026-03-27 20:41:41 -04:00
parent 43f0abeaec
commit 3a8383ba37

View file

@ -287,6 +287,39 @@ fn reflection_cycle(session: &Session, out: &mut String, log_f: &mut File) {
let _ = writeln!(log_f, "reflect: spawned {:?}", pid); let _ = writeln!(log_f, "reflect: spawned {:?}", pid);
} }
/// Run the journal agent on its own cadence — every 20KB of transcript.
/// Standalone agent that captures episodic memory independently of the
/// surface-observe pipeline.
fn journal_cycle(session: &Session, log_f: &mut File) {
let state_dir = crate::store::memory_dir()
.join("agent-output")
.join("journal");
fs::create_dir_all(&state_dir).ok();
let offset_path = state_dir.join("transcript-offset");
let transcript = session.transcript();
let last_offset: u64 = fs::read_to_string(&offset_path).ok()
.and_then(|s| s.trim().parse().ok())
.unwrap_or(0);
const JOURNAL_INTERVAL: u64 = 20_000;
if transcript.size.saturating_sub(last_offset) < JOURNAL_INTERVAL {
return;
}
let live = crate::agents::knowledge::scan_pid_files(&state_dir, 300);
if !live.is_empty() {
let _ = writeln!(log_f, "journal: already running {:?}", live);
return;
}
fs::write(&offset_path, transcript.size.to_string()).ok();
let pid = crate::agents::knowledge::spawn_agent(
"journal", &state_dir, &session.session_id);
let _ = writeln!(log_f, "journal: spawned {:?}", pid);
}
fn cleanup_stale_files(dir: &Path, max_age: Duration) { fn cleanup_stale_files(dir: &Path, max_age: Duration) {
let entries = match fs::read_dir(dir) { let entries = match fs::read_dir(dir) {
Ok(e) => e, Ok(e) => e,
@ -367,6 +400,7 @@ fn hook(session: &Session) -> String {
if cfg.surface_hooks.iter().any(|h| h == &session.hook_event) { if cfg.surface_hooks.iter().any(|h| h == &session.hook_event) {
surface_observe_cycle(session, &mut out, &mut log_f); surface_observe_cycle(session, &mut out, &mut log_f);
reflection_cycle(session, &mut out, &mut log_f); reflection_cycle(session, &mut out, &mut log_f);
journal_cycle(session, &mut log_f);
} }
} }