Skip to main content

codetether_agent/session/index/
types.rs

1//! Core types for the hierarchical summary index.
2//!
3//! These types are persisted as part of the session JSON and must
4//! remain serde-compatible across restarts.
5
6use serde::{Deserialize, Serialize};
7
8/// Half-open `[start, end)` range over session message indices.
9///
10/// # Examples
11///
12/// ```rust
13/// use codetether_agent::session::index::SummaryRange;
14///
15/// let r = SummaryRange::new(2, 5).unwrap();
16/// assert!(r.contains(2));
17/// assert!(r.contains(4));
18/// assert!(!r.contains(5));
19/// assert!(SummaryRange::new(5, 5).is_none());
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
22pub struct SummaryRange {
23    /// Inclusive lower bound (message index in session transcript).
24    pub start: usize,
25    /// Exclusive upper bound.
26    pub end: usize,
27}
28
29impl SummaryRange {
30    /// Construct a range. Returns `None` if `start >= end`.
31    pub fn new(start: usize, end: usize) -> Option<Self> {
32        (start < end).then_some(Self { start, end })
33    }
34
35    /// Whether this range covers message index `idx`.
36    pub fn contains(&self, idx: usize) -> bool {
37        self.start <= idx && idx < self.end
38    }
39}
40
41/// Granularity at which a [`SummaryNode`] was produced.
42///
43/// Step 18 uses this to choose between turn-level RLM summaries
44/// and coarser phase-/session-level rollups.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "snake_case")]
47pub enum Granularity {
48    /// Single turn or small window of turns.
49    Turn,
50    /// A logical phase of work (e.g. "implement feature X").
51    Phase,
52    /// Full session summarisation.
53    Session,
54}
55
56/// One cached summary entry — produced once, consumed many times until
57/// the underlying range is invalidated.
58///
59/// # Examples
60///
61/// ```rust
62/// use codetether_agent::session::index::{Granularity, SummaryNode};
63///
64/// let node = SummaryNode {
65///     content: "Discussed auth refactor".into(),
66///     target_tokens: 512,
67///     granularity: Granularity::Turn,
68///     generation: 1,
69/// };
70/// assert_eq!(node.content, "Discussed auth refactor");
71/// ```
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
73pub struct SummaryNode {
74    /// The summary text.
75    pub content: String,
76    /// Target token budget the producer was asked to fit.
77    pub target_tokens: usize,
78    /// Granularity tag.
79    pub granularity: Granularity,
80    /// Generation counter when this node was produced.
81    pub generation: u64,
82}
83
84/// Upper bound on cached summaries before LRU eviction kicks in.
85///
86/// Chosen so the index sidecar stays under ~1 MB for typical sessions.
87pub const MAX_CACHED_SUMMARIES: usize = 128;