session: add TranscriptInfo struct, consolidate transcript lookups
TranscriptInfo provides cached transcript metadata (path, size) with a single read. Replaces scattered fs::metadata calls in surface_observe_cycle, reflection_cycle, resolve_conversation, and resolve_memory_ratio. Session::transcript() resolves the path from transcript_path or by searching projects dir, returning a TranscriptInfo. Co-Authored-By: Kent Overstreet <kent.overstreet@linux.dev>
This commit is contained in:
parent
8ccc30d97e
commit
bb2e3b9fbb
3 changed files with 79 additions and 45 deletions
|
|
@ -10,7 +10,7 @@ use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, Instant, SystemTime};
|
||||||
|
|
||||||
|
|
||||||
/// Max bytes per context chunk (hook output limit is ~10K chars)
|
/// Max bytes per context chunk (hook output limit is ~10K chars)
|
||||||
|
|
@ -188,9 +188,8 @@ fn surface_observe_cycle(session: &Session, out: &mut String, log_f: &mut File)
|
||||||
// (studying, reading a book, etc.)
|
// (studying, reading a book, etc.)
|
||||||
let conversation_budget: u64 = 50_000;
|
let conversation_budget: u64 = 50_000;
|
||||||
let offset_path = state_dir.join("transcript-offset");
|
let offset_path = state_dir.join("transcript-offset");
|
||||||
let transcript_size = if !session.transcript_path.is_empty() {
|
let transcript = session.transcript();
|
||||||
fs::metadata(&session.transcript_path).map(|m| m.len()).unwrap_or(0)
|
let transcript_size = transcript.size;
|
||||||
} else { 0 };
|
|
||||||
|
|
||||||
if !live.is_empty() && transcript_size > 0 {
|
if !live.is_empty() && transcript_size > 0 {
|
||||||
let last_offset: u64 = fs::read_to_string(&offset_path).ok()
|
let last_offset: u64 = fs::read_to_string(&offset_path).ok()
|
||||||
|
|
@ -199,14 +198,20 @@ fn surface_observe_cycle(session: &Session, out: &mut String, log_f: &mut File)
|
||||||
let behind = transcript_size.saturating_sub(last_offset);
|
let behind = transcript_size.saturating_sub(last_offset);
|
||||||
|
|
||||||
if behind > conversation_budget / 2 {
|
if behind > conversation_budget / 2 {
|
||||||
let _ = writeln!(log_f, "agent {}KB behind (budget {}KB), waiting for catchup",
|
|
||||||
behind / 1024, conversation_budget / 1024);
|
|
||||||
// Wait up to 30s for the current agent to finish
|
// Wait up to 30s for the current agent to finish
|
||||||
|
let sleep_start = Instant::now();
|
||||||
|
|
||||||
for _ in 0..30 {
|
for _ in 0..30 {
|
||||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||||
let still_live = crate::agents::knowledge::scan_pid_files(&state_dir, timeout);
|
let still_live = crate::agents::knowledge::scan_pid_files(&state_dir, timeout);
|
||||||
if still_live.is_empty() { break; }
|
if still_live.is_empty() { break; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sleep_duration = (Instant::now() - sleep_start).as_secs();
|
||||||
|
|
||||||
|
let _ = writeln!(log_f, "agent {}KB behind (budget {}KB), slept for {sleep_duration} seconds",
|
||||||
|
behind / 1024, conversation_budget / 1024);
|
||||||
|
out.push_str(&format!("Slept for {sleep_duration} seconds to let observe catch up\n"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -240,9 +245,8 @@ fn reflection_cycle(session: &Session, out: &mut String, log_f: &mut File) {
|
||||||
|
|
||||||
// Check transcript growth since last reflection
|
// Check transcript growth since last reflection
|
||||||
let offset_path = state_dir.join("transcript-offset");
|
let offset_path = state_dir.join("transcript-offset");
|
||||||
let transcript_size = if !session.transcript_path.is_empty() {
|
let transcript = session.transcript();
|
||||||
fs::metadata(&session.transcript_path).map(|m| m.len()).unwrap_or(0)
|
let transcript_size = transcript.size;
|
||||||
} else { 0 };
|
|
||||||
|
|
||||||
let last_offset: u64 = fs::read_to_string(&offset_path).ok()
|
let last_offset: u64 = fs::read_to_string(&offset_path).ok()
|
||||||
.and_then(|s| s.trim().parse().ok())
|
.and_then(|s| s.trim().parse().ok())
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
// session.rs — Session state for ambient hooks and agent interactions
|
// session.rs — Session state and transcript info
|
||||||
//
|
//
|
||||||
// Tracks per-session state (seen set, state directory) across hook
|
// Session: per-session state (seen set, state directory) across hook
|
||||||
// invocations. Created from hook JSON input or POC_SESSION_ID env var.
|
// invocations. Created from hook JSON input or POC_SESSION_ID env var.
|
||||||
|
//
|
||||||
|
// TranscriptInfo: cached metadata about the current session's transcript
|
||||||
|
// file — size, path, compaction offset. Read once, used by all callers.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
@ -53,4 +56,51 @@ impl Session {
|
||||||
pub fn seen(&self) -> HashSet<String> {
|
pub fn seen(&self) -> HashSet<String> {
|
||||||
super::hippocampus::memory_search::load_seen(&self.state_dir, &self.session_id)
|
super::hippocampus::memory_search::load_seen(&self.state_dir, &self.session_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get transcript metadata, resolving the path if needed.
|
||||||
|
pub fn transcript(&self) -> TranscriptInfo {
|
||||||
|
if !self.transcript_path.is_empty() {
|
||||||
|
return TranscriptInfo::from_path(&self.transcript_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find transcript by session ID in projects dir
|
||||||
|
let projects = crate::config::get().projects_dir.clone();
|
||||||
|
if let Ok(dirs) = fs::read_dir(&projects) {
|
||||||
|
for dir in dirs.filter_map(|e| e.ok()) {
|
||||||
|
let path = dir.path().join(format!("{}.jsonl", self.session_id));
|
||||||
|
if path.exists() {
|
||||||
|
return TranscriptInfo::from_path(&path.to_string_lossy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TranscriptInfo::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cached transcript metadata — read once, use everywhere.
|
||||||
|
pub struct TranscriptInfo {
|
||||||
|
pub path: String,
|
||||||
|
pub size: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TranscriptInfo {
|
||||||
|
pub fn from_path(path: &str) -> Self {
|
||||||
|
let size = fs::metadata(path).map(|m| m.len()).unwrap_or(0);
|
||||||
|
TranscriptInfo {
|
||||||
|
path: path.to_string(),
|
||||||
|
size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
TranscriptInfo {
|
||||||
|
path: String::new(),
|
||||||
|
size: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exists(&self) -> bool {
|
||||||
|
!self.path.is_empty() && self.size > 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -572,26 +572,14 @@ fn resolve(
|
||||||
/// Reads POC_SESSION_ID to find the transcript, extracts the last
|
/// Reads POC_SESSION_ID to find the transcript, extracts the last
|
||||||
/// segment (post-compaction), returns the tail (~100K chars).
|
/// segment (post-compaction), returns the tail (~100K chars).
|
||||||
fn resolve_conversation(budget: Option<usize>) -> String {
|
fn resolve_conversation(budget: Option<usize>) -> String {
|
||||||
let session_id = std::env::var("POC_SESSION_ID").unwrap_or_default();
|
let session = crate::session::Session::from_env();
|
||||||
if session_id.is_empty() { return String::new(); }
|
let transcript = session.as_ref()
|
||||||
|
.map(|s| s.transcript())
|
||||||
|
.unwrap_or_else(crate::session::TranscriptInfo::empty);
|
||||||
|
|
||||||
let projects = crate::config::get().projects_dir.clone();
|
if !transcript.exists() { return String::new(); }
|
||||||
// Find the transcript file matching this session
|
|
||||||
let mut transcript = None;
|
|
||||||
if let Ok(dirs) = std::fs::read_dir(&projects) {
|
|
||||||
for dir in dirs.filter_map(|e| e.ok()) {
|
|
||||||
let path = dir.path().join(format!("{}.jsonl", session_id));
|
|
||||||
if path.exists() {
|
|
||||||
transcript = Some(path);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let Some(path) = transcript else { return String::new() };
|
let Some(iter) = crate::transcript::TailMessages::open(&transcript.path) else {
|
||||||
let path_str = path.to_string_lossy();
|
|
||||||
|
|
||||||
let Some(iter) = crate::transcript::TailMessages::open(&path_str) else {
|
|
||||||
return String::new();
|
return String::new();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -680,22 +668,14 @@ fn resolve_memory_ratio() -> String {
|
||||||
let state_dir = std::path::PathBuf::from("/tmp/claude-memory-search");
|
let state_dir = std::path::PathBuf::from("/tmp/claude-memory-search");
|
||||||
|
|
||||||
// Get post-compaction transcript size
|
// Get post-compaction transcript size
|
||||||
let projects = crate::config::get().projects_dir.clone();
|
let session = crate::session::Session::from_env();
|
||||||
let transcript_size: u64 = std::fs::read_dir(&projects).ok()
|
let transcript = session.as_ref()
|
||||||
.and_then(|dirs| {
|
.map(|s| s.transcript())
|
||||||
for dir in dirs.filter_map(|e| e.ok()) {
|
.unwrap_or_else(crate::session::TranscriptInfo::empty);
|
||||||
let path = dir.path().join(format!("{}.jsonl", session_id));
|
|
||||||
if path.exists() {
|
|
||||||
let file_len = path.metadata().map(|m| m.len()).unwrap_or(0);
|
|
||||||
let compaction_offset: u64 = std::fs::read_to_string(
|
let compaction_offset: u64 = std::fs::read_to_string(
|
||||||
state_dir.join(format!("compaction-{}", session_id))
|
state_dir.join(format!("compaction-{}", session_id))
|
||||||
).ok().and_then(|s| s.trim().parse().ok()).unwrap_or(0);
|
).ok().and_then(|s| s.trim().parse().ok()).unwrap_or(0);
|
||||||
return Some(file_len.saturating_sub(compaction_offset));
|
let transcript_size = transcript.size.saturating_sub(compaction_offset);
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
if transcript_size == 0 {
|
if transcript_size == 0 {
|
||||||
return "0% of context is recalled memories (new session)".to_string();
|
return "0% of context is recalled memories (new session)".to_string();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue