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§
Sourcefn load(&self, session_key: &str) -> Vec<ChatMessage>
fn load(&self, session_key: &str) -> Vec<ChatMessage>
Load all messages for a session. Returns empty vec if session doesn’t exist.
Sourcefn append(&self, session_key: &str, message: &ChatMessage) -> Result<(), Error>
fn append(&self, session_key: &str, message: &ChatMessage) -> Result<(), Error>
Append a single message to a session.
Sourcefn remove_last(&self, session_key: &str) -> Result<bool, Error>
fn remove_last(&self, session_key: &str) -> Result<bool, Error>
Remove the last message from a session. Returns true if a message was removed.
Sourcefn list_sessions(&self) -> Vec<String>
fn list_sessions(&self) -> Vec<String>
List all session keys.
Provided Methods§
Sourcefn try_load(&self, session_key: &str) -> Result<Vec<ChatMessage>, Error>
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.
Sourcefn load_with_timestamps(&self, session_key: &str) -> Vec<TimestampedMessage>
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.
Sourcefn rewrite_messages(
&self,
session_key: &str,
messages: &[ChatMessage],
) -> Result<(), Error>
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.
fn update_last( &self, session_key: &str, message: &ChatMessage, ) -> Result<bool, Error>
Sourcefn list_sessions_with_metadata(&self) -> Vec<SessionMetadata>
fn list_sessions_with_metadata(&self) -> Vec<SessionMetadata>
List sessions with metadata.
Sourcefn compact(&self, _session_key: &str) -> Result<(), Error>
fn compact(&self, _session_key: &str) -> Result<(), Error>
Compact a session file (remove duplicates/corruption). No-op by default.
Sourcefn cleanup_stale(&self, _ttl_hours: u32) -> Result<usize, Error>
fn cleanup_stale(&self, _ttl_hours: u32) -> Result<usize, Error>
Remove sessions that haven’t been active within the given TTL hours.
Sourcefn search(&self, _query: &SessionQuery) -> Vec<SessionMetadata>
fn search(&self, _query: &SessionQuery) -> Vec<SessionMetadata>
Search sessions by keyword. Default returns empty (backends with FTS override).
fn clear_messages(&self, session_key: &str) -> Result<usize, Error>
Sourcefn delete_session(&self, _session_key: &str) -> Result<bool, Error>
fn delete_session(&self, _session_key: &str) -> Result<bool, Error>
Delete all messages for a session. Returns true if the session existed.
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
Sourcefn set_session_name(&self, _session_key: &str, _name: &str) -> Result<(), Error>
fn set_session_name(&self, _session_key: &str, _name: &str) -> Result<(), Error>
Set or update the human-readable name for a session.
Sourcefn get_session_name(&self, _session_key: &str) -> Result<Option<String>, Error>
fn get_session_name(&self, _session_key: &str) -> Result<Option<String>, Error>
Get the human-readable name for a session (if set).
Sourcefn set_session_agent_alias(
&self,
_session_key: &str,
_agent_alias: &str,
) -> Result<(), Error>
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.
Sourcefn get_session_agent_alias(
&self,
_session_key: &str,
) -> Result<Option<String>, Error>
fn get_session_agent_alias( &self, _session_key: &str, ) -> Result<Option<String>, Error>
Get the agent alias associated with a session, if recorded.
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).
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).
Sourcefn replace_conversation_state(
&self,
session_key: &str,
messages: &[ChatMessage],
breadcrumb_present: bool,
) -> Result<(), Error>
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.
Sourcefn replace_conversation_state_if_exists(
&self,
session_key: &str,
messages: &[ChatMessage],
breadcrumb_present: bool,
) -> Result<bool, Error>
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).
fn set_session_context( &self, _session_key: &str, _context: SessionContext<'_>, ) -> Result<(), Error>
fn get_session_metadata(&self, session_key: &str) -> Option<SessionMetadata>
Sourcefn set_session_principal(
&self,
_session_key: &str,
_principal_id: &str,
) -> Result<(), Error>
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.
Sourcefn delete_session_owned(
&self,
_session_key: &str,
_owner_principal_id: &str,
) -> Result<bool, Error>
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.
Sourcefn set_session_state(
&self,
_session_key: &str,
_state: &str,
_turn_id: Option<&str>,
) -> Result<(), Error>
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).
Sourcefn get_session_state(
&self,
_session_key: &str,
) -> Result<Option<SessionState>, Error>
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.
Sourcefn list_running_sessions(&self) -> Vec<SessionMetadata>
fn list_running_sessions(&self) -> Vec<SessionMetadata>
List sessions currently in “running” state.
Sourcefn list_stuck_sessions(&self, _threshold_secs: u64) -> Vec<SessionMetadata>
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".