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;