Skip to main content

cel_memory/
session.rs

1//! Memory session — a coherent unit of work.
2//!
3//! A chat conversation or a delegated job. Memory chunks tie back to a session
4//! via [`MemoryChunk::session_id`]. The summarizer runs at end-of-session and
5//! produces a `JobSummary` chunk per session.
6//!
7//! [`MemoryChunk::session_id`]: crate::MemoryChunk::session_id
8
9use chrono::{DateTime, Utc};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13/// A session record.
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
15pub struct MemorySession {
16    /// uuid v7.
17    pub id: String,
18    /// When the session opened.
19    pub started_at: DateTime<Utc>,
20    /// When the session closed, if it has.
21    pub ended_at: Option<DateTime<Utc>>,
22    /// Normalised caller — `"embedded"`, `"mcp:cursor"`, etc.
23    pub caller_id: String,
24    /// Human-readable title (agent- or user-set).
25    pub title: Option<String>,
26    /// End-of-session synthesis. `None` until `close_session` has run and
27    /// summarization (where available) has produced one.
28    pub summary: Option<String>,
29    /// Outcome of the session.
30    pub outcome: SessionOutcome,
31    /// Free-form metadata.
32    #[serde(default)]
33    pub metadata: Value,
34}
35
36/// Input to [`crate::MemoryProvider::open_session`].
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
38pub struct NewMemorySession {
39    /// Caller opening the session.
40    pub caller_id: String,
41    /// Optional title.
42    #[serde(default)]
43    pub title: Option<String>,
44    /// Optional metadata.
45    #[serde(default)]
46    pub metadata: Value,
47}
48
49/// Outcome states for a session.
50#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
51#[serde(rename_all = "snake_case")]
52pub enum SessionOutcome {
53    /// Still in progress. The session has been opened but not closed.
54    Open,
55    /// The session completed and the agent considers the goal achieved.
56    Success,
57    /// The session completed but the goal was not achieved.
58    Failure,
59    /// The session was aborted (user closed the window mid-job and didn't
60    /// resume, agent self-terminated, etc.).
61    Aborted,
62}
63
64/// Filter for [`crate::MemoryProvider::list_sessions`].
65#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
66pub struct SessionFilter {
67    /// Restrict to sessions opened by a specific caller.
68    #[serde(default)]
69    pub caller_id: Option<String>,
70    /// Restrict to sessions with a specific outcome.
71    #[serde(default)]
72    pub outcome: Option<SessionOutcome>,
73    /// Lower bound on `started_at`.
74    #[serde(default)]
75    pub since: Option<DateTime<Utc>>,
76    /// Upper bound on `started_at`.
77    #[serde(default)]
78    pub until: Option<DateTime<Utc>>,
79    /// If true, only return sessions still `Open`.
80    #[serde(default)]
81    pub open_only: bool,
82    /// Maximum number of sessions to return. `None` = no limit.
83    #[serde(default)]
84    pub limit: Option<usize>,
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90    use serde_json::json;
91
92    #[test]
93    fn outcome_serializes_snake_case() {
94        assert_eq!(
95            serde_json::to_value(SessionOutcome::Open).unwrap(),
96            json!("open")
97        );
98        assert_eq!(
99            serde_json::to_value(SessionOutcome::Aborted).unwrap(),
100            json!("aborted")
101        );
102    }
103
104    #[test]
105    fn filter_defaults() {
106        let f = SessionFilter::default();
107        assert!(f.caller_id.is_none());
108        assert!(f.outcome.is_none());
109        assert!(!f.open_only);
110        assert!(f.limit.is_none());
111    }
112}