Skip to main content

SessionBackend

Trait SessionBackend 

pub trait SessionBackend: Send + Sync {
Show 34 methods // Required methods fn load(&self, session_key: &str) -> Vec<ChatMessage>; fn append( &self, session_key: &str, message: &ChatMessage, ) -> Result<(), Error>; fn remove_last(&self, session_key: &str) -> Result<bool, Error>; fn list_sessions(&self) -> Vec<String>; // Provided methods fn try_load(&self, session_key: &str) -> Result<Vec<ChatMessage>, Error> { ... } fn load_with_timestamps(&self, session_key: &str) -> Vec<TimestampedMessage> { ... } fn rewrite_messages( &self, session_key: &str, messages: &[ChatMessage], ) -> Result<(), Error> { ... } fn update_last( &self, session_key: &str, message: &ChatMessage, ) -> Result<bool, Error> { ... } fn list_sessions_with_metadata(&self) -> Vec<SessionMetadata> { ... } fn compact(&self, _session_key: &str) -> Result<(), Error> { ... } fn cleanup_stale(&self, _ttl_hours: u32) -> Result<usize, Error> { ... } fn search(&self, _query: &SessionQuery) -> Vec<SessionMetadata> { ... } fn clear_messages(&self, session_key: &str) -> Result<usize, Error> { ... } fn delete_session(&self, _session_key: &str) -> Result<bool, Error> { ... } fn clear_agent_attribution( &self, _agent_alias: &str, ) -> Result<usize, Error> { ... } fn rename_agent_attribution( &self, _from: &str, _to: &str, ) -> Result<usize, Error> { ... } fn count_agent_attribution( &self, _agent_alias: &str, ) -> Result<usize, Error> { ... } fn session_exists(&self, session_key: &str) -> bool { ... } fn set_session_name( &self, _session_key: &str, _name: &str, ) -> Result<(), Error> { ... } fn get_session_name( &self, _session_key: &str, ) -> Result<Option<String>, Error> { ... } fn set_session_agent_alias( &self, _session_key: &str, _agent_alias: &str, ) -> Result<(), Error> { ... } fn get_session_agent_alias( &self, _session_key: &str, ) -> Result<Option<String>, Error> { ... } fn set_session_trim_breadcrumb( &self, _session_key: &str, _present: bool, ) -> Result<(), Error> { ... } fn get_session_trim_breadcrumb( &self, _session_key: &str, ) -> Result<Option<bool>, Error> { ... } fn replace_conversation_state( &self, session_key: &str, messages: &[ChatMessage], breadcrumb_present: bool, ) -> Result<(), Error> { ... } fn replace_conversation_state_if_exists( &self, session_key: &str, messages: &[ChatMessage], breadcrumb_present: bool, ) -> Result<bool, Error> { ... } fn set_session_context( &self, _session_key: &str, _context: SessionContext<'_>, ) -> Result<(), Error> { ... } fn get_session_metadata(&self, session_key: &str) -> Option<SessionMetadata> { ... } fn set_session_principal( &self, _session_key: &str, _principal_id: &str, ) -> Result<(), Error> { ... } fn delete_session_owned( &self, _session_key: &str, _owner_principal_id: &str, ) -> Result<bool, Error> { ... } fn set_session_state( &self, _session_key: &str, _state: &str, _turn_id: Option<&str>, ) -> Result<(), Error> { ... } fn get_session_state( &self, _session_key: &str, ) -> Result<Option<SessionState>, Error> { ... } fn list_running_sessions(&self) -> Vec<SessionMetadata> { ... } fn list_stuck_sessions(&self, _threshold_secs: u64) -> Vec<SessionMetadata> { ... }
}
Expand description

Trait for session persistence backends. Implementations must be Send + Sync for sharing across async tasks.

Required Methods§

Source

fn load(&self, session_key: &str) -> Vec<ChatMessage>

Load all messages for a session. Returns empty vec if session doesn’t exist.

Source

fn append(&self, session_key: &str, message: &ChatMessage) -> Result<(), Error>

Append a single message to a session.

Source

fn remove_last(&self, session_key: &str) -> Result<bool, Error>

Remove the last message from a session. Returns true if a message was removed.

Source

fn list_sessions(&self) -> Vec<String>

List all session keys.

Provided Methods§

Source

fn try_load(&self, session_key: &str) -> Result<Vec<ChatMessage>, Error>

Like load, but distinguishes an unreadable transcript from a genuinely missing/empty session. Ok(vec) is the verified durable transcript (empty means no session), Err means the transcript could not be read or parsed and must not be treated as empty. The default impl delegates to load and never errors; backends that can detect read failures (JSONL) override this so callers can fail closed instead of seeding a new-message-only cache over an existing transcript.

Source

fn load_with_timestamps(&self, session_key: &str) -> Vec<TimestampedMessage>

Same as load, but each row carries its persisted created_at when the backend has one. Default impl falls back to load without timestamps so non-SQLite backends keep working.

Source

fn rewrite_messages( &self, session_key: &str, messages: &[ChatMessage], ) -> Result<(), Error>

Replace a session’s entire durable transcript with messages. Used to keep the canonical stored history in sync with a caller-owned buffer that trimmed or rewrote turns in place (e.g. history-budget enforcement), rather than appending on top of a now-stale transcript. The default clears the session (clear_messages) and re-appends every message through the required append primitive, so every backend gets real replacement semantics — a backend that never overrides this does not silently drop the caller’s authoritative trimmed history. It is O(n) calls and not atomic; backends with a native batch/transactional rewrite (JSONL, SQLite) override this.

Source

fn update_last( &self, session_key: &str, message: &ChatMessage, ) -> Result<bool, Error>

Source

fn list_sessions_with_metadata(&self) -> Vec<SessionMetadata>

List sessions with metadata.

Source

fn compact(&self, _session_key: &str) -> Result<(), Error>

Compact a session file (remove duplicates/corruption). No-op by default.

Source

fn cleanup_stale(&self, _ttl_hours: u32) -> Result<usize, Error>

Remove sessions that haven’t been active within the given TTL hours.

Source

fn search(&self, _query: &SessionQuery) -> Vec<SessionMetadata>

Search sessions by keyword. Default returns empty (backends with FTS override).

Source

fn clear_messages(&self, session_key: &str) -> Result<usize, Error>

Source

fn delete_session(&self, _session_key: &str) -> Result<bool, Error>

Delete all messages for a session. Returns true if the session existed.

Source

fn clear_agent_attribution(&self, _agent_alias: &str) -> Result<usize, Error>

Source

fn rename_agent_attribution( &self, _from: &str, _to: &str, ) -> Result<usize, Error>

Source

fn count_agent_attribution(&self, _agent_alias: &str) -> Result<usize, Error>

Source

fn session_exists(&self, session_key: &str) -> bool

Source

fn set_session_name(&self, _session_key: &str, _name: &str) -> Result<(), Error>

Set or update the human-readable name for a session.

Source

fn get_session_name(&self, _session_key: &str) -> Result<Option<String>, Error>

Get the human-readable name for a session (if set).

Source

fn set_session_agent_alias( &self, _session_key: &str, _agent_alias: &str, ) -> Result<(), Error>

Record the agent alias that owns a session. Called on WebSocket handshake when the alias is known. No-op for backends that don’t track per-agent attribution.

Source

fn get_session_agent_alias( &self, _session_key: &str, ) -> Result<Option<String>, Error>

Get the agent alias associated with a session, if recorded.

Source

fn set_session_trim_breadcrumb( &self, _session_key: &str, _present: bool, ) -> Result<(), Error>

Record whether this session’s persisted transcript currently starts with the synthetic trim breadcrumb, as one canonical fact alongside the transcript itself. Callers must not infer this from message text on restore: a genuine first user turn that happens to equal the localized breadcrumb string must keep its turn-boundary role, and a crumb written under another locale must stay classified as synthetic. No-op for backends that don’t track it (the caller falls back to treating the session as having no breadcrumb).

Source

fn get_session_trim_breadcrumb( &self, _session_key: &str, ) -> Result<Option<bool>, Error>

Get the recorded breadcrumb provenance for a session. None means the backend doesn’t track it or no session-level state exists yet (callers should assume false, not attempt text inference).

Source

fn replace_conversation_state( &self, session_key: &str, messages: &[ChatMessage], breadcrumb_present: bool, ) -> Result<(), Error>

Replace a session’s durable transcript and its breadcrumb provenance with the agent’s authoritative post-turn state. Callers that own a trimmed in-memory history must use this instead of appending only the turn’s delta. The default implementation performs two separate writes (rewrite_messages then set_session_trim_breadcrumb); they are serialized under the caller’s lock (channel per-sender lock or the store’s mutation guard) so concurrent turns cannot interleave, but the pair is not crash-atomic. A crash between the two writes can leave transcript and flag temporarily out of sync, recoverable on the next trim. Both built-in backends override this with a stronger guarantee: SqliteSessionBackend makes the pair atomic inside one transaction, and the JSONL SessionStore rolls the transcript back to its pre-replace content when the breadcrumb write fails, so an in-process failure converges back to the last known-good pair instead of leaving a new transcript paired with a stale flag (a process crash between the two file writes can still split them, since JSONL uses two files). Backends that can provide a transaction should follow SQLite’s example; ones limited to independent writes should follow JSONL’s.

Source

fn replace_conversation_state_if_exists( &self, session_key: &str, messages: &[ChatMessage], breadcrumb_present: bool, ) -> Result<bool, Error>

Replace the durable transcript and breadcrumb flag only if the session still exists. Returns Ok(true) when the replacement was written and Ok(false) when the session was already deleted, in which case nothing is written and, in particular, no fresh session row or file is recreated for the deleted key.

Post-turn persistence must use this instead of replace_conversation_state: a bare existence check followed by a separate replace is a check-then-act race — a DELETE /api/sessions/{id} (or any other deleter) committing between the two recreates durable state the user just wiped, since both the SQLite breadcrumb upsert and the JSONL rewrite recreate their rows/files unconditionally.

The default implementation is check-then-act and therefore only best-effort. Backends with a serializing primitive close the race by overriding it: SqliteSessionBackend performs the existence check and both writes inside one transaction under the connection mutex (which the deleter also holds), and the JSONL SessionStore holds its mutation guard across the existence probe and both file writes (which its deleter also holds).

Source

fn set_session_context( &self, _session_key: &str, _context: SessionContext<'_>, ) -> Result<(), Error>

Source

fn get_session_metadata(&self, session_key: &str) -> Option<SessionMetadata>

Source

fn set_session_principal( &self, _session_key: &str, _principal_id: &str, ) -> Result<(), Error>

Record the authenticated principal that owns a session (RFC 7141). Called at session/new when the dispatcher has a real (non-sentinel) principal bound. No-op for backends without the column.

Source

fn delete_session_owned( &self, _session_key: &str, _owner_principal_id: &str, ) -> Result<bool, Error>

Delete a session ONLY if owner_principal_id matches the stored owner: the ownership check and the destruction of the ownership row are one SQL statement (the RFC 7141 atomic storage predicate), so a concurrent re-stamp cannot race them apart. Backends without ownership support fail closed: Ok(false), nothing deleted.

Source

fn set_session_state( &self, _session_key: &str, _state: &str, _turn_id: Option<&str>, ) -> Result<(), Error>

Set the session state (e.g. “idle”, “running”, “error”). turn_id identifies the current turn (set when running, cleared on idle).

Source

fn get_session_state( &self, _session_key: &str, ) -> Result<Option<SessionState>, Error>

Get the current session state. Returns None if the backend doesn’t track state.

Source

fn list_running_sessions(&self) -> Vec<SessionMetadata>

List sessions currently in “running” state.

Source

fn list_stuck_sessions(&self, _threshold_secs: u64) -> Vec<SessionMetadata>

List sessions stuck in “running” state longer than threshold_secs.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§