Skip to main content

synth_ai_core/tracing/
storage.rs

1//! Trace storage trait definition.
2//!
3//! This module defines the abstract `TraceStorage` trait that storage
4//! backends must implement.
5
6use async_trait::async_trait;
7use chrono::{DateTime, Utc};
8use serde_json::Value;
9
10use super::error::TracingError;
11use super::models::{
12    EventReward, MarkovBlanketMessage, OutcomeReward, SessionTimeStep, SessionTrace, TracingEvent,
13};
14
15/// Abstract storage backend for traces.
16///
17/// Implementations can store traces in SQLite, Turso, or other backends.
18#[async_trait]
19pub trait TraceStorage: Send + Sync {
20    // ========================================================================
21    // INITIALIZATION
22    // ========================================================================
23
24    /// Initialize the storage backend (create tables, etc.).
25    async fn initialize(&self) -> Result<(), TracingError>;
26
27    /// Close the storage connection.
28    async fn close(&self) -> Result<(), TracingError>;
29
30    // ========================================================================
31    // SESSION OPERATIONS
32    // ========================================================================
33
34    /// Ensure a session exists (create if not present).
35    ///
36    /// This is idempotent - calling multiple times with the same session_id
37    /// will not create duplicates.
38    async fn ensure_session(
39        &self,
40        session_id: &str,
41        created_at: DateTime<Utc>,
42        metadata: &Value,
43    ) -> Result<(), TracingError>;
44
45    /// Get a session trace by ID.
46    async fn get_session(&self, session_id: &str) -> Result<Option<SessionTrace>, TracingError>;
47
48    /// Delete a session and all related data.
49    async fn delete_session(&self, session_id: &str) -> Result<bool, TracingError>;
50
51    // ========================================================================
52    // TIMESTEP OPERATIONS
53    // ========================================================================
54
55    /// Ensure a timestep exists and return its database ID.
56    ///
57    /// This is idempotent - calling multiple times with the same session_id
58    /// and step_id will return the same database ID.
59    async fn ensure_timestep(
60        &self,
61        session_id: &str,
62        step: &SessionTimeStep,
63    ) -> Result<i64, TracingError>;
64
65    /// Update a timestep (e.g., mark as completed).
66    async fn update_timestep(
67        &self,
68        session_id: &str,
69        step_id: &str,
70        completed_at: Option<DateTime<Utc>>,
71    ) -> Result<(), TracingError>;
72
73    // ========================================================================
74    // EVENT OPERATIONS
75    // ========================================================================
76
77    /// Insert an event and return its database ID.
78    async fn insert_event(
79        &self,
80        session_id: &str,
81        timestep_db_id: Option<i64>,
82        event: &TracingEvent,
83    ) -> Result<i64, TracingError>;
84
85    // ========================================================================
86    // MESSAGE OPERATIONS
87    // ========================================================================
88
89    /// Insert a message and return its database ID.
90    async fn insert_message(
91        &self,
92        session_id: &str,
93        timestep_db_id: Option<i64>,
94        msg: &MarkovBlanketMessage,
95    ) -> Result<i64, TracingError>;
96
97    // ========================================================================
98    // REWARD OPERATIONS
99    // ========================================================================
100
101    /// Insert an outcome (session-level) reward.
102    async fn insert_outcome_reward(
103        &self,
104        session_id: &str,
105        reward: &OutcomeReward,
106    ) -> Result<i64, TracingError>;
107
108    /// Insert an event-level reward.
109    async fn insert_event_reward(
110        &self,
111        session_id: &str,
112        event_id: i64,
113        message_id: Option<i64>,
114        turn_number: Option<i32>,
115        reward: &EventReward,
116    ) -> Result<i64, TracingError>;
117
118    // ========================================================================
119    // QUERY OPERATIONS
120    // ========================================================================
121
122    /// Execute a raw SQL query and return results as JSON values.
123    async fn query(&self, sql: &str, params: Vec<Value>) -> Result<Vec<Value>, TracingError>;
124
125    /// Update session counts (num_timesteps, num_events, num_messages).
126    async fn update_session_counts(&self, session_id: &str) -> Result<(), TracingError>;
127}
128
129/// Storage configuration.
130#[derive(Debug, Clone)]
131pub struct StorageConfig {
132    /// Database URL (file path, :memory:, or libsql:// URL)
133    pub db_url: String,
134    /// Auth token for remote databases
135    pub auth_token: Option<String>,
136    /// Whether to auto-initialize schema
137    pub auto_init: bool,
138}
139
140impl Default for StorageConfig {
141    fn default() -> Self {
142        Self {
143            db_url: ":memory:".to_string(),
144            auth_token: None,
145            auto_init: true,
146        }
147    }
148}
149
150impl StorageConfig {
151    /// Create config for in-memory database.
152    pub fn memory() -> Self {
153        Self::default()
154    }
155
156    /// Create config for a file-based database.
157    pub fn file(path: impl Into<String>) -> Self {
158        Self {
159            db_url: path.into(),
160            auth_token: None,
161            auto_init: true,
162        }
163    }
164
165    /// Create config for a remote Turso database.
166    pub fn turso(url: impl Into<String>, token: impl Into<String>) -> Self {
167        Self {
168            db_url: url.into(),
169            auth_token: Some(token.into()),
170            auto_init: true,
171        }
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_storage_config_default() {
181        let config = StorageConfig::default();
182        assert_eq!(config.db_url, ":memory:");
183        assert!(config.auth_token.is_none());
184        assert!(config.auto_init);
185    }
186
187    #[test]
188    fn test_storage_config_file() {
189        let config = StorageConfig::file("/tmp/test.db");
190        assert_eq!(config.db_url, "/tmp/test.db");
191    }
192
193    #[test]
194    fn test_storage_config_turso() {
195        let config = StorageConfig::turso("libsql://test.turso.io", "token123");
196        assert_eq!(config.db_url, "libsql://test.turso.io");
197        assert_eq!(config.auth_token, Some("token123".to_string()));
198    }
199}