a3s_code_core/store/mod.rs
1//! Session persistence layer
2//!
3//! Provides pluggable session storage via the `SessionStore` trait.
4//!
5//! ## Default Implementation
6//!
7//! `FileSessionStore` stores each session as a JSON file:
8//! - Session metadata (id, name, timestamps)
9//! - Configuration (system prompt, policies)
10//! - Conversation history (messages)
11//! - Context usage statistics
12//!
13//! ## Custom Backends
14//!
15//! Implement `SessionStore` trait for custom backends (Redis, PostgreSQL, etc.):
16//!
17//! ```ignore
18//! use a3s_code::store::{SessionStore, SessionData};
19//!
20//! struct RedisStore { /* ... */ }
21//!
22//! #[async_trait::async_trait]
23//! impl SessionStore for RedisStore {
24//! async fn save(&self, session: &SessionData) -> Result<()> { /* ... */ }
25//! async fn load(&self, id: &str) -> Result<Option<SessionData>> { /* ... */ }
26//! async fn delete(&self, id: &str) -> Result<()> { /* ... */ }
27//! async fn list(&self) -> Result<Vec<String>> { /* ... */ }
28//! async fn exists(&self, id: &str) -> Result<bool> { /* ... */ }
29//! }
30//! ```
31
32mod file_store;
33mod memory_store;
34mod session_data;
35
36#[cfg(test)]
37mod tests;
38
39pub use file_store::FileSessionStore;
40pub use memory_store::MemorySessionStore;
41pub use session_data::{
42 ContextUsage, LlmConfigData, SessionConfig, SessionData, SessionState,
43 DEFAULT_AUTO_COMPACT_THRESHOLD,
44};
45
46use crate::loop_checkpoint::LoopCheckpoint;
47use crate::run::RunRecord;
48use crate::subagent_task_tracker::SubagentTaskSnapshot;
49use crate::tools::ArtifactStore;
50use crate::trace::TraceEvent;
51use crate::verification::VerificationReport;
52use anyhow::Result;
53
54// ============================================================================
55// Session Store Trait
56// ============================================================================
57
58/// Session storage trait
59#[async_trait::async_trait]
60pub trait SessionStore: Send + Sync {
61 /// Save session data
62 async fn save(&self, session: &SessionData) -> Result<()>;
63
64 /// Load session data by ID
65 async fn load(&self, id: &str) -> Result<Option<SessionData>>;
66
67 /// Delete session data
68 async fn delete(&self, id: &str) -> Result<()>;
69
70 /// List all session IDs
71 async fn list(&self) -> Result<Vec<String>>;
72
73 /// Check if session exists
74 async fn exists(&self, id: &str) -> Result<bool>;
75
76 /// Save artifacts associated with a session.
77 async fn save_artifacts(&self, _id: &str, _artifacts: &ArtifactStore) -> Result<()> {
78 Ok(())
79 }
80
81 /// Load artifacts associated with a session.
82 async fn load_artifacts(&self, _id: &str) -> Result<Option<ArtifactStore>> {
83 Ok(None)
84 }
85
86 /// Save compact trace events associated with a session.
87 async fn save_trace_events(&self, _id: &str, _events: &[TraceEvent]) -> Result<()> {
88 Ok(())
89 }
90
91 /// Load compact trace events associated with a session.
92 async fn load_trace_events(&self, _id: &str) -> Result<Option<Vec<TraceEvent>>> {
93 Ok(None)
94 }
95
96 /// Save run snapshots and replayable runtime events associated with a session.
97 async fn save_run_records(&self, _id: &str, _records: &[RunRecord]) -> Result<()> {
98 Ok(())
99 }
100
101 /// Load run snapshots and replayable runtime events associated with a session.
102 async fn load_run_records(&self, _id: &str) -> Result<Option<Vec<RunRecord>>> {
103 Ok(None)
104 }
105
106 /// Save structured verification reports associated with a session.
107 async fn save_verification_reports(
108 &self,
109 _id: &str,
110 _reports: &[VerificationReport],
111 ) -> Result<()> {
112 Ok(())
113 }
114
115 /// Load structured verification reports associated with a session.
116 async fn load_verification_reports(
117 &self,
118 _id: &str,
119 ) -> Result<Option<Vec<VerificationReport>>> {
120 Ok(None)
121 }
122
123 /// Save the session's delegated subagent task tracker snapshots.
124 ///
125 /// Cluster-grade hosts need this so a migrated session keeps a
126 /// queryable history of its delegated child runs. Cancellers are
127 /// **not** persisted — they are runtime-only and re-attaching them
128 /// is the executor's job at task respawn time.
129 async fn save_subagent_tasks(&self, _id: &str, _tasks: &[SubagentTaskSnapshot]) -> Result<()> {
130 Ok(())
131 }
132
133 /// Load the session's delegated subagent task tracker snapshots.
134 async fn load_subagent_tasks(&self, _id: &str) -> Result<Option<Vec<SubagentTaskSnapshot>>> {
135 Ok(None)
136 }
137
138 /// Save the latest per-tool-round loop checkpoint for `run_id`.
139 ///
140 /// The agent loop calls this through the
141 /// [`SessionStoreCheckpointSink`](crate::loop_checkpoint::SessionStoreCheckpointSink)
142 /// adapter after each completed tool round. Implementations should
143 /// **overwrite** any earlier checkpoint for the same `run_id` — the
144 /// loop only ever needs the most recent boundary.
145 async fn save_loop_checkpoint(
146 &self,
147 _run_id: &str,
148 _checkpoint: &LoopCheckpoint,
149 ) -> Result<()> {
150 Ok(())
151 }
152
153 /// Load the latest loop checkpoint for `run_id`.
154 async fn load_loop_checkpoint(&self, _run_id: &str) -> Result<Option<LoopCheckpoint>> {
155 Ok(None)
156 }
157
158 /// Delete the loop checkpoint for `run_id`, if present.
159 ///
160 /// Called by the run lifecycle when a run reaches a terminal state
161 /// **in-process** (completed, failed, or cancelled) — at that point
162 /// the checkpoint is dead weight. Only a process crash (the agent
163 /// loop never returns) should leave a checkpoint behind for
164 /// crash-recovery resume. Without this, every tool-using run would
165 /// leak a checkpoint forever — the dominant unbounded-growth source
166 /// for long-running cluster deployments.
167 ///
168 /// Deleting a non-existent checkpoint is a no-op success.
169 async fn delete_loop_checkpoint(&self, _run_id: &str) -> Result<()> {
170 Ok(())
171 }
172
173 /// Persist a workflow checkpoint, overwriting any earlier one for the same
174 /// `workflow_id`. The resumable orchestration combinators call this at each
175 /// step boundary so an interrupted workflow resumes from the last
176 /// completed step (here or, after migration, on another node).
177 async fn save_workflow_checkpoint(
178 &self,
179 _workflow_id: &str,
180 _checkpoint: &crate::orchestration::WorkflowCheckpoint,
181 ) -> Result<()> {
182 Ok(())
183 }
184
185 /// Load the latest workflow checkpoint for `workflow_id`.
186 async fn load_workflow_checkpoint(
187 &self,
188 _workflow_id: &str,
189 ) -> Result<Option<crate::orchestration::WorkflowCheckpoint>> {
190 Ok(None)
191 }
192
193 /// Delete the workflow checkpoint for `workflow_id`, if present. Called
194 /// when a workflow reaches a terminal state in-process; only a crash should
195 /// leave one behind for resume. Deleting a non-existent checkpoint is a
196 /// no-op success.
197 async fn delete_workflow_checkpoint(&self, _workflow_id: &str) -> Result<()> {
198 Ok(())
199 }
200
201 /// Health check — verify the store backend is reachable and operational
202 async fn health_check(&self) -> Result<()> {
203 Ok(())
204 }
205
206 /// Backend name for diagnostics
207 fn backend_name(&self) -> &str {
208 "unknown"
209 }
210}