Skip to main content

starweaver_session/store/
contract.rs

1//! Durable session store contract.
2
3use async_trait::async_trait;
4use starweaver_context::{AgentCheckpoint, ResumableState};
5use starweaver_core::{RunId, SessionId};
6use starweaver_stream::AgentStreamRecord;
7
8use crate::{
9    AcquireBackgroundSubagentContinuation, AcquireRunAdmission,
10    BackgroundSubagentContinuationReceipt, BackgroundSubagentRecord,
11    DurableBackgroundSubagentDeliveryClaim, DurableBackgroundSubagentDeliveryRelease,
12    DurableControlReceipt, RunAdmissionLease, RunAdmissionReceipt, SessionContinuationFence,
13    UpdateManagedSession,
14    approval::{ApprovalRecord, DeferredToolRecord},
15    claim::HitlResumeClaim,
16    error::{SessionStoreError, SessionStoreResult},
17    evidence::RunEvidenceCommit,
18    publication::{PendingStreamPublication, StreamPublicationTarget},
19    records::{
20        EnvironmentStateRef, RunRecord, RunStatus, SessionRecord, SessionStatus, StreamCursorRef,
21    },
22    resume::SessionResumeSnapshot,
23    trace::{CompactRunTrace, CompactSessionTrace},
24};
25
26fn management_unsupported<T>() -> SessionStoreResult<T> {
27    Err(SessionStoreError::Failed(
28        "session store does not support agent session management".to_string(),
29    ))
30}
31
32/// Query filters for listing sessions.
33#[derive(Clone, Debug, Default, Eq, PartialEq)]
34pub struct SessionFilter {
35    /// Required session status.
36    pub status: Option<SessionStatus>,
37    /// Required profile name.
38    pub profile: Option<String>,
39    /// Required workspace identifier or path.
40    pub workspace: Option<String>,
41    /// Maximum number of sessions returned.
42    pub limit: Option<usize>,
43}
44
45/// Durable session store contract.
46#[async_trait]
47pub trait SessionStore: Send + Sync {
48    /// Atomically persist a complete run evidence bundle.
49    ///
50    /// Implementations must leave either the complete previous state or the complete committed
51    /// state visible. Identical retries are idempotent and conflicting retries must fail.
52    async fn commit_run_evidence(&self, commit: RunEvidenceCommit)
53    -> SessionStoreResult<RunRecord>;
54
55    /// Atomically bootstrap missing session/run records and persist one runtime checkpoint.
56    ///
57    /// This is the executor write path. Implementations must not expose a session or run without
58    /// the checkpoint when the operation fails. Exact checkpoint retries are idempotent and
59    /// conflicting retries fail.
60    async fn commit_checkpoint(
61        &self,
62        session_id: &SessionId,
63        checkpoint: AgentCheckpoint,
64    ) -> SessionStoreResult<()>;
65
66    /// Acquire exclusive ownership of a waiting run before any continuation side effect.
67    ///
68    /// A run may have at most one active claim. Claims are consumed by the related-run update in
69    /// [`Self::commit_run_evidence`].
70    async fn claim_hitl_resume(&self, _claim: HitlResumeClaim) -> SessionStoreResult<()> {
71        Err(SessionStoreError::Failed(
72            "session store does not support exclusive HITL resume claims".to_string(),
73        ))
74    }
75
76    /// Mark a claim started immediately before the first continuation hook or tool executes.
77    async fn mark_hitl_resume_started(
78        &self,
79        _session_id: &SessionId,
80        _run_id: &RunId,
81        _claim_id: &str,
82    ) -> SessionStoreResult<()> {
83        Err(SessionStoreError::Failed(
84            "session store does not support exclusive HITL resume claims".to_string(),
85        ))
86    }
87
88    /// Release a preflight claim. Stores must reject release after execution has started.
89    async fn release_hitl_resume_claim(
90        &self,
91        _session_id: &SessionId,
92        _run_id: &RunId,
93        _claim_id: &str,
94    ) -> SessionStoreResult<()> {
95        Err(SessionStoreError::Failed(
96            "session store does not support exclusive HITL resume claims".to_string(),
97        ))
98    }
99
100    /// List transactionally enqueued stream publications still awaiting at least one sink.
101    async fn pending_stream_publications(
102        &self,
103        _session_id: &SessionId,
104    ) -> SessionStoreResult<Vec<PendingStreamPublication>> {
105        Err(SessionStoreError::Failed(
106            "session store does not support transactional stream publication".to_string(),
107        ))
108    }
109
110    /// Acknowledge one sink only after its complete idempotent delivery succeeds.
111    async fn acknowledge_stream_publication(
112        &self,
113        _publication_id: &str,
114        _target: StreamPublicationTarget,
115    ) -> SessionStoreResult<()> {
116        Err(SessionStoreError::Failed(
117            "session store does not support transactional stream publication".to_string(),
118        ))
119    }
120
121    /// Atomically create a session and bind an idempotency key to a normalized fingerprint.
122    async fn create_session_idempotent(
123        &self,
124        _session: SessionRecord,
125        _idempotency_key: &str,
126        _command_fingerprint: &str,
127    ) -> SessionStoreResult<SessionRecord> {
128        management_unsupported()
129    }
130
131    /// Apply an allowlisted session patch with expected-revision and idempotency checks.
132    async fn update_managed_session(
133        &self,
134        _command: UpdateManagedSession,
135        _command_fingerprint: &str,
136    ) -> SessionStoreResult<SessionRecord> {
137        management_unsupported()
138    }
139
140    /// Acquire a deletion fence that blocks run, continuation, and delegation admission.
141    async fn acquire_session_deletion_fence(
142        &self,
143        _session_id: &SessionId,
144        _expected_revision: u64,
145        _fence_id: &str,
146        _requested_by: &str,
147        _idempotency_key: &str,
148        _command_fingerprint: &str,
149    ) -> SessionStoreResult<SessionRecord> {
150        management_unsupported()
151    }
152
153    /// Complete a fenced session tombstone. This never purges retained evidence.
154    async fn tombstone_session(
155        &self,
156        _session_id: &SessionId,
157        _fence_id: &str,
158    ) -> SessionStoreResult<SessionRecord> {
159        management_unsupported()
160    }
161
162    /// Load the deletion/continuation fence used by async supervisors before side effects.
163    async fn session_continuation_fence(
164        &self,
165        _namespace_id: &str,
166        _session_id: &SessionId,
167    ) -> SessionStoreResult<SessionContinuationFence> {
168        management_unsupported()
169    }
170
171    /// Atomically persist a queued run and acquire the session's single active lease.
172    async fn acquire_run_admission(
173        &self,
174        _request: AcquireRunAdmission,
175    ) -> SessionStoreResult<RunAdmissionReceipt> {
176        management_unsupported()
177    }
178
179    /// Extend a lease only for its current host and fencing generation.
180    async fn heartbeat_run_admission(
181        &self,
182        _lease: &RunAdmissionLease,
183        _lease_expires_at: chrono::DateTime<chrono::Utc>,
184    ) -> SessionStoreResult<RunAdmissionLease> {
185        management_unsupported()
186    }
187
188    /// Release a lease after terminal run durability; stale generations cannot release a new owner.
189    async fn release_run_admission(&self, _lease: &RunAdmissionLease) -> SessionStoreResult<()> {
190        management_unsupported()
191    }
192
193    /// Load durable admission truth for a composite target.
194    async fn load_run_admission(
195        &self,
196        _target: &crate::ManagedRunTarget,
197    ) -> SessionStoreResult<Option<RunAdmissionLease>> {
198        management_unsupported()
199    }
200
201    /// Deterministically terminalize expired active leases owned by prior host instances.
202    async fn reconcile_expired_run_admissions(
203        &self,
204        _namespace_id: &str,
205        _now: chrono::DateTime<chrono::Utc>,
206    ) -> SessionStoreResult<Vec<crate::ManagedRunTarget>> {
207        management_unsupported()
208    }
209
210    /// Load a durable control receipt by composite target and idempotency key.
211    async fn load_control_receipt(
212        &self,
213        _target: &crate::ManagedRunTarget,
214        _idempotency_key: &str,
215    ) -> SessionStoreResult<Option<DurableControlReceipt>> {
216        management_unsupported()
217    }
218
219    /// Reserve or replay a durable fenced control receipt.
220    async fn reserve_control_receipt(
221        &self,
222        _receipt: DurableControlReceipt,
223    ) -> SessionStoreResult<DurableControlReceipt> {
224        management_unsupported()
225    }
226
227    /// Record the final accepted/failed effect state for a reserved receipt.
228    async fn update_control_receipt_state(
229        &self,
230        _receipt_id: &str,
231        _state: &str,
232    ) -> SessionStoreResult<DurableControlReceipt> {
233        management_unsupported()
234    }
235
236    /// Wait for store-owned background-subagent operations whose caller futures may have ended.
237    ///
238    /// Implementations that detach non-cancellable database or network work must retain and drain
239    /// that work here. Cancellation-safe implementations must explicitly return success; the
240    /// default fails closed so a store cannot accidentally claim a complete shutdown guarantee.
241    async fn drain_background_subagent_operations(&self) -> SessionStoreResult<()> {
242        management_unsupported()
243    }
244
245    /// Idempotently persist one accepted durable background-subagent attempt.
246    async fn record_background_subagent_acceptance(
247        &self,
248        _record: BackgroundSubagentRecord,
249    ) -> SessionStoreResult<BackgroundSubagentRecord> {
250        management_unsupported()
251    }
252
253    /// Persist a monotonic non-terminal lifecycle transition or child-run correlation.
254    async fn update_background_subagent_execution(
255        &self,
256        _record: BackgroundSubagentRecord,
257    ) -> SessionStoreResult<BackgroundSubagentRecord> {
258        management_unsupported()
259    }
260
261    /// Extend an active background execution lease for its current fenced owner.
262    async fn heartbeat_background_subagent(
263        &self,
264        _attempt_id: &starweaver_core::SubagentAttemptId,
265        _host_instance_id: &str,
266        _fencing_generation: u64,
267        _lease_expires_at: chrono::DateTime<chrono::Utc>,
268    ) -> SessionStoreResult<BackgroundSubagentRecord> {
269        management_unsupported()
270    }
271
272    /// Atomically persist terminal evidence and its optional oversized-result artifact.
273    async fn commit_background_subagent_terminal(
274        &self,
275        commit: crate::BackgroundSubagentTerminalCommit,
276    ) -> SessionStoreResult<BackgroundSubagentRecord> {
277        if commit.artifact.is_some() {
278            return management_unsupported();
279        }
280        self.record_background_subagent_terminal(commit.record)
281            .await
282    }
283
284    /// Load one retained background-result artifact by stable reference.
285    async fn load_background_subagent_artifact(
286        &self,
287        _artifact_ref: &str,
288    ) -> SessionStoreResult<crate::BackgroundSubagentArtifact> {
289        management_unsupported()
290    }
291
292    /// Expire retained background-result content while preserving minimal audit evidence.
293    async fn expire_background_subagent_retention(
294        &self,
295        _namespace_id: &str,
296        _now: chrono::DateTime<chrono::Utc>,
297        _limit: usize,
298    ) -> SessionStoreResult<Vec<BackgroundSubagentRecord>> {
299        management_unsupported()
300    }
301
302    /// Idempotently persist immutable terminal outcome before delivery becomes claimable.
303    async fn record_background_subagent_terminal(
304        &self,
305        _record: BackgroundSubagentRecord,
306    ) -> SessionStoreResult<BackgroundSubagentRecord> {
307        management_unsupported()
308    }
309
310    /// Load one durable background attempt by globally unique attempt identity.
311    async fn load_background_subagent(
312        &self,
313        _attempt_id: &starweaver_core::SubagentAttemptId,
314    ) -> SessionStoreResult<BackgroundSubagentRecord> {
315        management_unsupported()
316    }
317
318    /// List bounded durable background attempts in one host namespace and optional session.
319    async fn list_background_subagents(
320        &self,
321        _namespace_id: &str,
322        _session_id: Option<&SessionId>,
323        _limit: usize,
324    ) -> SessionStoreResult<Vec<BackgroundSubagentRecord>> {
325        management_unsupported()
326    }
327
328    /// List terminal results still awaiting or holding logical delivery ownership.
329    async fn list_pending_background_subagents(
330        &self,
331        _namespace_id: &str,
332        _session_id: Option<&SessionId>,
333        _limit: usize,
334    ) -> SessionStoreResult<Vec<BackgroundSubagentRecord>> {
335        management_unsupported()
336    }
337
338    /// Atomically claim one terminal result, allowing exact-claim idempotent replay.
339    async fn claim_background_subagent_delivery(
340        &self,
341        _attempt_id: &starweaver_core::SubagentAttemptId,
342        _claim: DurableBackgroundSubagentDeliveryClaim,
343    ) -> SessionStoreResult<BackgroundSubagentRecord> {
344        management_unsupported()
345    }
346
347    /// Acknowledge one matching claim as logically delivered.
348    async fn acknowledge_background_subagent_delivery(
349        &self,
350        _attempt_id: &starweaver_core::SubagentAttemptId,
351        _claim_id: &str,
352    ) -> SessionStoreResult<BackgroundSubagentRecord> {
353        management_unsupported()
354    }
355
356    /// Release one matching claim with a durable retry or consumer-termination disposition.
357    async fn release_background_subagent_delivery(
358        &self,
359        _attempt_id: &starweaver_core::SubagentAttemptId,
360        _claim_id: &str,
361        _release: DurableBackgroundSubagentDeliveryRelease,
362    ) -> SessionStoreResult<BackgroundSubagentRecord> {
363        management_unsupported()
364    }
365
366    /// Atomically admit a continuation run and consume its background result exactly once.
367    async fn acquire_background_subagent_continuation(
368        &self,
369        _request: AcquireBackgroundSubagentContinuation,
370    ) -> SessionStoreResult<BackgroundSubagentContinuationReceipt> {
371        management_unsupported()
372    }
373
374    /// Classify lost in-process executions and reclaim expired delivery claims after restart.
375    async fn reconcile_background_subagents(
376        &self,
377        _namespace_id: &str,
378        _now: chrono::DateTime<chrono::Utc>,
379    ) -> SessionStoreResult<Vec<BackgroundSubagentRecord>> {
380        management_unsupported()
381    }
382
383    /// Save a session record.
384    async fn save_session(&self, session: SessionRecord) -> SessionStoreResult<()>;
385
386    /// Load a session record.
387    async fn load_session(&self, session_id: &SessionId) -> SessionStoreResult<SessionRecord>;
388
389    /// List sessions by optional filter.
390    async fn list_sessions(&self, filter: SessionFilter) -> SessionStoreResult<Vec<SessionRecord>>;
391
392    /// Update session status.
393    async fn update_session_status(
394        &self,
395        session_id: &SessionId,
396        status: SessionStatus,
397    ) -> SessionStoreResult<()>;
398
399    /// Save a context state snapshot for a session.
400    async fn save_context_state(
401        &self,
402        session_id: &SessionId,
403        state: ResumableState,
404    ) -> SessionStoreResult<()>;
405
406    /// Save an environment state reference for a session.
407    async fn save_environment_state(
408        &self,
409        session_id: &SessionId,
410        environment_state: EnvironmentStateRef,
411    ) -> SessionStoreResult<()>;
412
413    /// Append or replace a run record.
414    ///
415    /// A zero `sequence_no` requests atomic session-local allocation. Replacing an existing run
416    /// must preserve its assigned sequence; an explicit attempt to change that sequence fails.
417    async fn append_run(&self, run: RunRecord) -> SessionStoreResult<()>;
418
419    /// Load a run record.
420    async fn load_run(
421        &self,
422        session_id: &SessionId,
423        run_id: &RunId,
424    ) -> SessionStoreResult<RunRecord>;
425
426    /// List runs for a session.
427    async fn list_runs(&self, session_id: &SessionId) -> SessionStoreResult<Vec<RunRecord>>;
428
429    /// Update run status and optional output preview.
430    async fn update_run_status(
431        &self,
432        session_id: &SessionId,
433        run_id: &RunId,
434        status: RunStatus,
435        output_preview: Option<String>,
436    ) -> SessionStoreResult<()>;
437
438    /// Append a full runtime checkpoint.
439    async fn append_checkpoint(
440        &self,
441        session_id: &SessionId,
442        checkpoint: AgentCheckpoint,
443    ) -> SessionStoreResult<()>;
444
445    /// Load checkpoints for a run in insertion order.
446    async fn load_checkpoints(
447        &self,
448        session_id: &SessionId,
449        run_id: &RunId,
450    ) -> SessionStoreResult<Vec<AgentCheckpoint>>;
451
452    /// Load the latest checkpoint for a run.
453    async fn latest_checkpoint(
454        &self,
455        session_id: &SessionId,
456        run_id: &RunId,
457    ) -> SessionStoreResult<Option<AgentCheckpoint>> {
458        let checkpoints = self.load_checkpoints(session_id, run_id).await?;
459        Ok(checkpoints.into_iter().last())
460    }
461
462    /// Append runtime stream records used as resume evidence.
463    async fn append_stream_records(
464        &self,
465        session_id: &SessionId,
466        run_id: &RunId,
467        records: Vec<AgentStreamRecord>,
468    ) -> SessionStoreResult<()>;
469
470    /// Replay runtime stream records for a run.
471    async fn replay_stream_records(
472        &self,
473        session_id: &SessionId,
474        run_id: &RunId,
475    ) -> SessionStoreResult<Vec<AgentStreamRecord>>;
476
477    /// Replay runtime stream records after a sequence cursor.
478    async fn replay_stream_records_after(
479        &self,
480        session_id: &SessionId,
481        run_id: &RunId,
482        after_sequence: Option<usize>,
483    ) -> SessionStoreResult<Vec<AgentStreamRecord>> {
484        let records = self.replay_stream_records(session_id, run_id).await?;
485        Ok(records
486            .into_iter()
487            .filter(|record| after_sequence.is_none_or(|cursor| record.sequence > cursor))
488            .collect())
489    }
490
491    /// Store a stream cursor reference for a run and session.
492    async fn save_stream_cursor(
493        &self,
494        session_id: &SessionId,
495        run_id: &RunId,
496        cursor: StreamCursorRef,
497    ) -> SessionStoreResult<()>;
498
499    /// Append an approval record.
500    async fn append_approval(&self, approval: ApprovalRecord) -> SessionStoreResult<()>;
501
502    /// Load approval records for a run.
503    async fn load_approvals(
504        &self,
505        session_id: &SessionId,
506        run_id: &RunId,
507    ) -> SessionStoreResult<Vec<ApprovalRecord>>;
508
509    /// Append a deferred tool record.
510    async fn append_deferred_tool(&self, record: DeferredToolRecord) -> SessionStoreResult<()>;
511
512    /// Load deferred tool records for a run.
513    async fn load_deferred_tools(
514        &self,
515        session_id: &SessionId,
516        run_id: &RunId,
517    ) -> SessionStoreResult<Vec<DeferredToolRecord>>;
518
519    /// Load a resume snapshot from session, checkpoint, and stream evidence.
520    async fn resume_snapshot(
521        &self,
522        _session_id: &SessionId,
523        _run_id: &RunId,
524    ) -> SessionStoreResult<SessionResumeSnapshot> {
525        Err(SessionStoreError::Failed(
526            "session store does not support per-run resume snapshots".to_string(),
527        ))
528    }
529
530    /// Return compact run trace projection.
531    async fn compact_run_trace(
532        &self,
533        session_id: &SessionId,
534        run_id: &RunId,
535    ) -> SessionStoreResult<CompactRunTrace>;
536
537    /// Return compact session trace projection.
538    async fn compact_session_trace(
539        &self,
540        session_id: &SessionId,
541    ) -> SessionStoreResult<CompactSessionTrace>;
542}