Skip to main content

cognee_session/
session_store.rs

1use std::collections::HashMap;
2
3use async_trait::async_trait;
4
5use crate::error::SessionError;
6use crate::types::{SessionQAEntry, SessionTraceStep, UsedGraphElementIds};
7
8/// Partial update DTO for a QA entry.
9///
10/// - Outer `None` means "leave field unchanged".
11/// - `Some(None)` means "clear the field".
12/// - `Some(Some(value))` means "set the field to this value".
13///
14/// For non-optional fields (`question`, `answer`) the outer `Option` controls
15/// whether an update is applied.
16#[derive(Debug, Clone, Default)]
17pub struct SessionQAUpdate {
18    pub question: Option<String>,
19    pub answer: Option<String>,
20    pub context: Option<Option<String>>,
21    pub feedback_text: Option<Option<String>>,
22    pub feedback_score: Option<Option<i32>>,
23    pub used_graph_element_ids: Option<Option<UsedGraphElementIds>>,
24    pub memify_metadata: Option<Option<HashMap<String, bool>>>,
25}
26
27/// Abstraction over session Q&A storage backends (SQLite, Redis, filesystem, etc.).
28///
29/// Analogous to Python's `CacheDBInterface`. All backends implement this trait.
30#[async_trait]
31pub trait SessionStore: Send + Sync {
32    /// Store a Q&A entry in the session. Returns the generated `qa_id`.
33    async fn create_qa_entry(
34        &self,
35        session_id: &str,
36        user_id: Option<&str>,
37        question: &str,
38        answer: &str,
39        context: Option<&str>,
40    ) -> Result<String, SessionError>;
41
42    /// Retrieve the most recent `last_n` Q&A entries for a session, ordered oldest-first.
43    async fn get_latest_qa_entries(
44        &self,
45        session_id: &str,
46        user_id: Option<&str>,
47        last_n: usize,
48    ) -> Result<Vec<SessionQAEntry>, SessionError>;
49
50    /// Retrieve all Q&A entries for a session, ordered oldest-first.
51    async fn get_all_qa_entries(
52        &self,
53        session_id: &str,
54        user_id: Option<&str>,
55    ) -> Result<Vec<SessionQAEntry>, SessionError>;
56
57    /// Delete all entries for a session. Returns `true` if the session existed.
58    async fn delete_session(
59        &self,
60        session_id: &str,
61        user_id: Option<&str>,
62    ) -> Result<bool, SessionError>;
63
64    /// Delete a single Q&A entry by id. Returns `true` if found and deleted.
65    async fn delete_qa_entry(
66        &self,
67        session_id: &str,
68        user_id: Option<&str>,
69        qa_id: &str,
70    ) -> Result<bool, SessionError>;
71
72    /// Delete ALL session data across all users and sessions.
73    /// Equivalent to Python's `CacheDBInterface.prune()`.
74    async fn prune(&self) -> Result<(), SessionError>;
75
76    /// Update fields on a QA entry. Only non-`None` fields in `updates` are applied.
77    /// Returns `true` if the entry was found and updated.
78    async fn update_qa_entry(
79        &self,
80        session_id: &str,
81        user_id: Option<&str>,
82        qa_id: &str,
83        updates: SessionQAUpdate,
84    ) -> Result<bool, SessionError>;
85
86    /// Return the `qa_id` of the most-recent Q&A entry in the session, or
87    /// `None` when the session has no entries yet.
88    ///
89    /// Used by the search orchestrator to route conversationally-detected
90    /// feedback to the previous answer (mirrors Python `session_manager.py:462-469`).
91    ///
92    /// Default impl loads the latest entry via `get_latest_qa_entries(limit=1)`
93    /// and extracts its id. Backends may override with a more efficient query.
94    async fn latest_qa_id(
95        &self,
96        session_id: &str,
97        user_id: Option<&str>,
98    ) -> Result<Option<String>, SessionError> {
99        let entries = self.get_latest_qa_entries(session_id, user_id, 1).await?;
100        Ok(entries.into_iter().next().map(|e| e.id.to_string()))
101    }
102
103    /// Retrieve the graph knowledge snapshot for a session, or `None`.
104    async fn get_graph_context(
105        &self,
106        session_id: &str,
107        user_id: Option<&str>,
108    ) -> Result<Option<String>, SessionError>;
109
110    /// Store (or overwrite) the graph knowledge snapshot for a session.
111    async fn set_graph_context(
112        &self,
113        session_id: &str,
114        user_id: Option<&str>,
115        context: &str,
116    ) -> Result<(), SessionError>;
117
118    /// Append one agent-trace step to the session's trace list.
119    ///
120    /// Returns the persisted `trace_id` (caller-provided — `SessionManager`
121    /// generates it via UUID4 before invoking).
122    ///
123    /// Default impl returns `SessionError::StoreError` so backends that have
124    /// not been updated to support trace steps still compile. The fs / redis /
125    /// sea-orm backends override this with a real implementation.
126    async fn save_trace_step(
127        &self,
128        user_id: &str,
129        session_id: &str,
130        step: SessionTraceStep,
131    ) -> Result<String, SessionError> {
132        let _ = (user_id, session_id, step);
133        Err(SessionError::StoreError(
134            "save_trace_step not implemented for this backend".into(),
135        ))
136    }
137
138    /// Retrieve agent-trace steps for the given session (oldest-first).
139    ///
140    /// `SessionManager::get_agent_trace_session` performs `last_n` slicing on
141    /// top of the returned list. Default impl returns `SessionError::StoreError`.
142    async fn read_trace_steps(
143        &self,
144        user_id: &str,
145        session_id: &str,
146    ) -> Result<Vec<SessionTraceStep>, SessionError> {
147        let _ = (user_id, session_id);
148        Err(SessionError::StoreError(
149            "read_trace_steps not implemented for this backend".into(),
150        ))
151    }
152}