use async_trait::async_trait;
use starweaver_context::{AgentCheckpoint, ResumableState};
use starweaver_core::{RunId, SessionId};
use starweaver_stream::AgentStreamRecord;
use crate::{
AcquireBackgroundSubagentContinuation, AcquireRunAdmission,
BackgroundSubagentContinuationReceipt, BackgroundSubagentRecord,
DurableBackgroundSubagentDeliveryClaim, DurableBackgroundSubagentDeliveryRelease,
DurableControlReceipt, RunAdmissionLease, RunAdmissionReceipt, SessionContinuationFence,
UpdateManagedSession,
approval::{ApprovalRecord, DeferredToolRecord},
claim::HitlResumeClaim,
error::{SessionStoreError, SessionStoreResult},
evidence::RunEvidenceCommit,
publication::{PendingStreamPublication, StreamPublicationTarget},
records::{
EnvironmentStateRef, RunRecord, RunStatus, SessionRecord, SessionStatus, StreamCursorRef,
},
resume::SessionResumeSnapshot,
trace::{CompactRunTrace, CompactSessionTrace},
};
fn management_unsupported<T>() -> SessionStoreResult<T> {
Err(SessionStoreError::Failed(
"session store does not support agent session management".to_string(),
))
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct SessionFilter {
pub status: Option<SessionStatus>,
pub profile: Option<String>,
pub workspace: Option<String>,
pub limit: Option<usize>,
}
#[async_trait]
pub trait SessionStore: Send + Sync {
async fn commit_run_evidence(&self, commit: RunEvidenceCommit)
-> SessionStoreResult<RunRecord>;
async fn commit_checkpoint(
&self,
session_id: &SessionId,
checkpoint: AgentCheckpoint,
) -> SessionStoreResult<()>;
async fn claim_hitl_resume(&self, _claim: HitlResumeClaim) -> SessionStoreResult<()> {
Err(SessionStoreError::Failed(
"session store does not support exclusive HITL resume claims".to_string(),
))
}
async fn mark_hitl_resume_started(
&self,
_session_id: &SessionId,
_run_id: &RunId,
_claim_id: &str,
) -> SessionStoreResult<()> {
Err(SessionStoreError::Failed(
"session store does not support exclusive HITL resume claims".to_string(),
))
}
async fn release_hitl_resume_claim(
&self,
_session_id: &SessionId,
_run_id: &RunId,
_claim_id: &str,
) -> SessionStoreResult<()> {
Err(SessionStoreError::Failed(
"session store does not support exclusive HITL resume claims".to_string(),
))
}
async fn pending_stream_publications(
&self,
_session_id: &SessionId,
) -> SessionStoreResult<Vec<PendingStreamPublication>> {
Err(SessionStoreError::Failed(
"session store does not support transactional stream publication".to_string(),
))
}
async fn acknowledge_stream_publication(
&self,
_publication_id: &str,
_target: StreamPublicationTarget,
) -> SessionStoreResult<()> {
Err(SessionStoreError::Failed(
"session store does not support transactional stream publication".to_string(),
))
}
async fn create_session_idempotent(
&self,
_session: SessionRecord,
_idempotency_key: &str,
_command_fingerprint: &str,
) -> SessionStoreResult<SessionRecord> {
management_unsupported()
}
async fn update_managed_session(
&self,
_command: UpdateManagedSession,
_command_fingerprint: &str,
) -> SessionStoreResult<SessionRecord> {
management_unsupported()
}
async fn acquire_session_deletion_fence(
&self,
_session_id: &SessionId,
_expected_revision: u64,
_fence_id: &str,
_requested_by: &str,
_idempotency_key: &str,
_command_fingerprint: &str,
) -> SessionStoreResult<SessionRecord> {
management_unsupported()
}
async fn tombstone_session(
&self,
_session_id: &SessionId,
_fence_id: &str,
) -> SessionStoreResult<SessionRecord> {
management_unsupported()
}
async fn session_continuation_fence(
&self,
_namespace_id: &str,
_session_id: &SessionId,
) -> SessionStoreResult<SessionContinuationFence> {
management_unsupported()
}
async fn acquire_run_admission(
&self,
_request: AcquireRunAdmission,
) -> SessionStoreResult<RunAdmissionReceipt> {
management_unsupported()
}
async fn heartbeat_run_admission(
&self,
_lease: &RunAdmissionLease,
_lease_expires_at: chrono::DateTime<chrono::Utc>,
) -> SessionStoreResult<RunAdmissionLease> {
management_unsupported()
}
async fn release_run_admission(&self, _lease: &RunAdmissionLease) -> SessionStoreResult<()> {
management_unsupported()
}
async fn load_run_admission(
&self,
_target: &crate::ManagedRunTarget,
) -> SessionStoreResult<Option<RunAdmissionLease>> {
management_unsupported()
}
async fn reconcile_expired_run_admissions(
&self,
_namespace_id: &str,
_now: chrono::DateTime<chrono::Utc>,
) -> SessionStoreResult<Vec<crate::ManagedRunTarget>> {
management_unsupported()
}
async fn load_control_receipt(
&self,
_target: &crate::ManagedRunTarget,
_idempotency_key: &str,
) -> SessionStoreResult<Option<DurableControlReceipt>> {
management_unsupported()
}
async fn reserve_control_receipt(
&self,
_receipt: DurableControlReceipt,
) -> SessionStoreResult<DurableControlReceipt> {
management_unsupported()
}
async fn update_control_receipt_state(
&self,
_receipt_id: &str,
_state: &str,
) -> SessionStoreResult<DurableControlReceipt> {
management_unsupported()
}
async fn drain_background_subagent_operations(&self) -> SessionStoreResult<()> {
management_unsupported()
}
async fn record_background_subagent_acceptance(
&self,
_record: BackgroundSubagentRecord,
) -> SessionStoreResult<BackgroundSubagentRecord> {
management_unsupported()
}
async fn update_background_subagent_execution(
&self,
_record: BackgroundSubagentRecord,
) -> SessionStoreResult<BackgroundSubagentRecord> {
management_unsupported()
}
async fn heartbeat_background_subagent(
&self,
_attempt_id: &starweaver_core::SubagentAttemptId,
_host_instance_id: &str,
_fencing_generation: u64,
_lease_expires_at: chrono::DateTime<chrono::Utc>,
) -> SessionStoreResult<BackgroundSubagentRecord> {
management_unsupported()
}
async fn commit_background_subagent_terminal(
&self,
commit: crate::BackgroundSubagentTerminalCommit,
) -> SessionStoreResult<BackgroundSubagentRecord> {
if commit.artifact.is_some() {
return management_unsupported();
}
self.record_background_subagent_terminal(commit.record)
.await
}
async fn load_background_subagent_artifact(
&self,
_artifact_ref: &str,
) -> SessionStoreResult<crate::BackgroundSubagentArtifact> {
management_unsupported()
}
async fn expire_background_subagent_retention(
&self,
_namespace_id: &str,
_now: chrono::DateTime<chrono::Utc>,
_limit: usize,
) -> SessionStoreResult<Vec<BackgroundSubagentRecord>> {
management_unsupported()
}
async fn record_background_subagent_terminal(
&self,
_record: BackgroundSubagentRecord,
) -> SessionStoreResult<BackgroundSubagentRecord> {
management_unsupported()
}
async fn load_background_subagent(
&self,
_attempt_id: &starweaver_core::SubagentAttemptId,
) -> SessionStoreResult<BackgroundSubagentRecord> {
management_unsupported()
}
async fn list_background_subagents(
&self,
_namespace_id: &str,
_session_id: Option<&SessionId>,
_limit: usize,
) -> SessionStoreResult<Vec<BackgroundSubagentRecord>> {
management_unsupported()
}
async fn list_pending_background_subagents(
&self,
_namespace_id: &str,
_session_id: Option<&SessionId>,
_limit: usize,
) -> SessionStoreResult<Vec<BackgroundSubagentRecord>> {
management_unsupported()
}
async fn claim_background_subagent_delivery(
&self,
_attempt_id: &starweaver_core::SubagentAttemptId,
_claim: DurableBackgroundSubagentDeliveryClaim,
) -> SessionStoreResult<BackgroundSubagentRecord> {
management_unsupported()
}
async fn acknowledge_background_subagent_delivery(
&self,
_attempt_id: &starweaver_core::SubagentAttemptId,
_claim_id: &str,
) -> SessionStoreResult<BackgroundSubagentRecord> {
management_unsupported()
}
async fn release_background_subagent_delivery(
&self,
_attempt_id: &starweaver_core::SubagentAttemptId,
_claim_id: &str,
_release: DurableBackgroundSubagentDeliveryRelease,
) -> SessionStoreResult<BackgroundSubagentRecord> {
management_unsupported()
}
async fn acquire_background_subagent_continuation(
&self,
_request: AcquireBackgroundSubagentContinuation,
) -> SessionStoreResult<BackgroundSubagentContinuationReceipt> {
management_unsupported()
}
async fn reconcile_background_subagents(
&self,
_namespace_id: &str,
_now: chrono::DateTime<chrono::Utc>,
) -> SessionStoreResult<Vec<BackgroundSubagentRecord>> {
management_unsupported()
}
async fn save_session(&self, session: SessionRecord) -> SessionStoreResult<()>;
async fn load_session(&self, session_id: &SessionId) -> SessionStoreResult<SessionRecord>;
async fn list_sessions(&self, filter: SessionFilter) -> SessionStoreResult<Vec<SessionRecord>>;
async fn update_session_status(
&self,
session_id: &SessionId,
status: SessionStatus,
) -> SessionStoreResult<()>;
async fn save_context_state(
&self,
session_id: &SessionId,
state: ResumableState,
) -> SessionStoreResult<()>;
async fn save_environment_state(
&self,
session_id: &SessionId,
environment_state: EnvironmentStateRef,
) -> SessionStoreResult<()>;
async fn append_run(&self, run: RunRecord) -> SessionStoreResult<()>;
async fn load_run(
&self,
session_id: &SessionId,
run_id: &RunId,
) -> SessionStoreResult<RunRecord>;
async fn list_runs(&self, session_id: &SessionId) -> SessionStoreResult<Vec<RunRecord>>;
async fn update_run_status(
&self,
session_id: &SessionId,
run_id: &RunId,
status: RunStatus,
output_preview: Option<String>,
) -> SessionStoreResult<()>;
async fn append_checkpoint(
&self,
session_id: &SessionId,
checkpoint: AgentCheckpoint,
) -> SessionStoreResult<()>;
async fn load_checkpoints(
&self,
session_id: &SessionId,
run_id: &RunId,
) -> SessionStoreResult<Vec<AgentCheckpoint>>;
async fn latest_checkpoint(
&self,
session_id: &SessionId,
run_id: &RunId,
) -> SessionStoreResult<Option<AgentCheckpoint>> {
let checkpoints = self.load_checkpoints(session_id, run_id).await?;
Ok(checkpoints.into_iter().last())
}
async fn append_stream_records(
&self,
session_id: &SessionId,
run_id: &RunId,
records: Vec<AgentStreamRecord>,
) -> SessionStoreResult<()>;
async fn replay_stream_records(
&self,
session_id: &SessionId,
run_id: &RunId,
) -> SessionStoreResult<Vec<AgentStreamRecord>>;
async fn replay_stream_records_after(
&self,
session_id: &SessionId,
run_id: &RunId,
after_sequence: Option<usize>,
) -> SessionStoreResult<Vec<AgentStreamRecord>> {
let records = self.replay_stream_records(session_id, run_id).await?;
Ok(records
.into_iter()
.filter(|record| after_sequence.is_none_or(|cursor| record.sequence > cursor))
.collect())
}
async fn save_stream_cursor(
&self,
session_id: &SessionId,
run_id: &RunId,
cursor: StreamCursorRef,
) -> SessionStoreResult<()>;
async fn append_approval(&self, approval: ApprovalRecord) -> SessionStoreResult<()>;
async fn load_approvals(
&self,
session_id: &SessionId,
run_id: &RunId,
) -> SessionStoreResult<Vec<ApprovalRecord>>;
async fn append_deferred_tool(&self, record: DeferredToolRecord) -> SessionStoreResult<()>;
async fn load_deferred_tools(
&self,
session_id: &SessionId,
run_id: &RunId,
) -> SessionStoreResult<Vec<DeferredToolRecord>>;
async fn resume_snapshot(
&self,
_session_id: &SessionId,
_run_id: &RunId,
) -> SessionStoreResult<SessionResumeSnapshot> {
Err(SessionStoreError::Failed(
"session store does not support per-run resume snapshots".to_string(),
))
}
async fn compact_run_trace(
&self,
session_id: &SessionId,
run_id: &RunId,
) -> SessionStoreResult<CompactRunTrace>;
async fn compact_session_trace(
&self,
session_id: &SessionId,
) -> SessionStoreResult<CompactSessionTrace>;
}