Skip to main content

codetether_agent/session/
tail_load.rs

1//! Bounded-memory session load using the tail-cap deserializer.
2//!
3//! `Session::load_tail` opens the session file with an incremental JSON
4//! reader and keeps only the last `window` messages/tool uses, so resuming
5//! a very large session does not allocate the full transcript.
6
7use std::fs::File;
8use std::io::BufReader;
9use std::path::{Path, PathBuf};
10
11use anyhow::{Context, Result};
12
13use super::tail_seed::with_tail_cap;
14use super::types::Session;
15
16/// Result of a bounded session load.
17#[derive(Debug)]
18pub struct TailLoad {
19    /// The session with its message/tool_use vectors capped to the window.
20    pub session: Session,
21    /// Total number of messages + tool_use entries discarded from the tail
22    /// cap. Zero means the full transcript fit within the window.
23    pub dropped: usize,
24    /// Size of the session file on disk, in bytes.
25    pub file_bytes: u64,
26}
27
28impl Session {
29    /// Load a session by UUID, keeping only the last `window` messages and
30    /// tool uses. Use `window = 0` to drop all messages (cheap directory
31    /// scan).
32    ///
33    /// # Errors
34    /// Returns an error if the file cannot be opened or the JSON is malformed.
35    pub async fn load_tail(id: &str, window: usize) -> Result<TailLoad> {
36        let path = Self::session_path(id)?;
37        Self::load_tail_from_path(path, window).await
38    }
39
40    /// Load a session from an explicit path with a bounded message window.
41    pub async fn load_tail_from_path(path: PathBuf, window: usize) -> Result<TailLoad> {
42        tokio::task::spawn_blocking(move || parse_tail(&path, window))
43            .await
44            .context("session tail-load task panicked")?
45    }
46}
47
48fn parse_tail(path: &Path, window: usize) -> Result<TailLoad> {
49    let file_bytes = std::fs::metadata(path).map(|m| m.len()).unwrap_or(0);
50    let file = File::open(path).with_context(|| format!("open session file {}", path.display()))?;
51    let reader = BufReader::with_capacity(64 * 1024, file);
52    let (parsed, dropped) = with_tail_cap(window, || {
53        serde_json::from_reader::<_, Session>(reader)
54            .with_context(|| format!("parse session file {}", path.display()))
55    });
56    Ok(TailLoad {
57        session: parsed?,
58        dropped,
59        file_bytes,
60    })
61}