Skip to main content

SqliteSessionBackend

Struct SqliteSessionBackend 

pub struct SqliteSessionBackend { /* private fields */ }
Expand description

SQLite-backed session store with FTS5 and WAL mode.

Implementations§

Source§

impl SqliteSessionBackend

Source

pub fn new(workspace_dir: &Path) -> Result<SqliteSessionBackend, Error>

Open or create the sessions database.

Source

pub fn migrate_from_jsonl(&self, workspace_dir: &Path) -> Result<usize, Error>

Migrate JSONL session files into SQLite. Renames migrated files to .jsonl.migrated.

Trait Implementations§

Source§

impl SessionBackend for SqliteSessionBackend

Source§

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

Replace the transcript and the breadcrumb flag in one transaction, so the pair can never commit as a partial write: a crash or error between the two rolls the whole transaction back rather than leaving a new transcript paired with a stale flag (or vice versa). The default SessionBackend::replace_conversation_state performs these as two separate committed operations; this override replaces it for SQLite where a single transaction can make the pair atomic.

Source§

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

Existence-guarded replacement in one transaction: when the metadata row is gone (a concurrent delete won), return Ok(false) without writing anything, so a completed turn can never recreate a session the user just deleted. The plain replace_conversation_state breadcrumb upsert would INSERT a fresh metadata row here. delete_session holds the same connection mutex, so the existence probe and both writes are atomic with respect to it. A genuine query failure (anything other than “no such row”) is still an error, not a silent skip: only a missing row means “deleted”.

Source§

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

Efficiently update the last message in-place (single UPDATE instead of DELETE + INSERT). Used for incremental persistence during streaming.

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 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 append(&self, session_key: &str, message: &ChatMessage) -> Result<(), Error>

Append a single message to a session.
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 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.
Source§

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

List sessions with metadata.
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 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 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 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 get_session_metadata(&self, session_key: &str) -> Option<SessionMetadata>

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.
Source§

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

Search sessions by keyword. Default returns empty (backends with FTS override).
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 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 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 set_session_context( &self, session_key: &str, context: SessionContext<'_>, ) -> Result<(), Error>

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 compact(&self, _session_key: &str) -> Result<(), Error>

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

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
§

impl<'a, T, E> AsTaggedExplicit<'a, E> for T
where T: 'a,

§

fn explicit(self, class: Class, tag: u32) -> TaggedParser<'a, Explicit, Self, E>

§

impl<'a, T, E> AsTaggedImplicit<'a, E> for T
where T: 'a,

§

fn implicit( self, class: Class, constructed: bool, tag: u32, ) -> TaggedParser<'a, Implicit, Self, E>

Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more