Skip to main content

lean_ctx/core/session_summary/
record.rs

1//! Persisted session-summary record + the lock-free candidate built under the
2//! session lock (#292).
3
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7/// A persisted, recallable session summary.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct SummaryRecord {
10    /// Stable id: `<session-id>-<seq>`.
11    pub id: String,
12    pub session_id: String,
13    pub created_at: DateTime<Utc>,
14    /// One-line headline (task description or inferred focus).
15    pub title: String,
16    /// Deterministic multi-line narrative of the session.
17    pub body: String,
18    pub files: Vec<String>,
19    pub decisions: Vec<String>,
20    pub next_steps: Vec<String>,
21    /// Tool-call count at the time of recording (also the cadence watermark).
22    pub tool_calls: u64,
23}
24
25impl SummaryRecord {
26    /// Text used for both lexical and semantic recall.
27    pub fn searchable_text(&self) -> String {
28        let mut t = String::with_capacity(self.title.len() + self.body.len() + 16);
29        t.push_str(&self.title);
30        t.push('\n');
31        t.push_str(&self.body);
32        t
33    }
34}
35
36/// An owned snapshot built while holding the session lock, then persisted off the
37/// hot path. Keeps the lock hold minimal (no disk I/O under the lock).
38#[derive(Debug, Clone)]
39pub struct SummaryCandidate {
40    pub session_id: String,
41    pub created_at: DateTime<Utc>,
42    pub title: String,
43    pub body: String,
44    pub files: Vec<String>,
45    pub decisions: Vec<String>,
46    pub next_steps: Vec<String>,
47    pub tool_calls: u64,
48    /// Whether the session carried anything worth summarizing.
49    pub has_content: bool,
50}
51
52impl SummaryCandidate {
53    /// Finalize into a persisted record with a sequence number.
54    pub fn into_record(self, seq: u32) -> SummaryRecord {
55        let short = self
56            .session_id
57            .split('-')
58            .next()
59            .unwrap_or(&self.session_id);
60        SummaryRecord {
61            id: format!("{short}-{seq:04}"),
62            session_id: self.session_id,
63            created_at: self.created_at,
64            title: self.title,
65            body: self.body,
66            files: self.files,
67            decisions: self.decisions,
68            next_steps: self.next_steps,
69            tool_calls: self.tool_calls,
70        }
71    }
72}