Quick Start
1
Plug a mirror into DefaultSessionStore
~/.praisonai/sessions/user-42.json and the mirror both receive the turn.2
Continue on another machine
load(session_id) returns the full transcript, so a fresh install can pick up the conversation.How It Works
The store writes local disk first and returns; the record is then handed to a bounded queue drained by a daemon thread that callsmirror.append.
The failure modes never touch the local write:
Configuration Options
SessionMirrorProtocol methods
The runtime_checkable protocol lives at praisonaiagents.session.SessionMirrorProtocol.
DefaultSessionStore(mirror=…)
Writing a Mirror
A mirror is three tiny methods — theLoggingMirror from Quick Start is the whole story.
Concrete wrapper-side adapters (postgres/supabase/turso/sqlite) land in
praisonai.persistence.conversation.* alongside store-unification (#3645). Until then, implement the protocol directly against your backend.Local-First Guarantees
Local disk write always happens first
Local disk write always happens first
The mirror is fed after the message is persisted to the session file, off the lock. A mirror outage never reaches the caller as a failure.
A slow/broken mirror never blocks a turn
A slow/broken mirror never blocks a turn
Records are enqueued onto a bounded
queue.Queue (DEFAULT_MIRROR_QUEUE_SIZE = 1000) drained by a daemon thread (praisonai-session-mirror).Queue overflow is drop-to-log, not deadlock
Queue overflow is drop-to-log, not deadlock
On
Queue.Full, records are dropped with a warning pointing operators at the (future) session sync reconciler; the local file is unaffected.Transient failures retry with backoff
Transient failures retry with backoff
_SessionMirrorWriter._flush_one retries DEFAULT_MIRROR_MAX_RETRIES = 3 times with 50ms → 1s exponential backoff before logging and moving on.mirror=None is byte-for-byte the old store
mirror=None is byte-for-byte the old store
The writer is only instantiated when
mirror is not None — no queue, no thread, no import cost.Post-close appends are rejected
Post-close appends are rejected
After
close_mirror(), further enqueues drop-to-log rather than leaving records stranded with no consumer.Testing & Reconciliation
Useflush_mirror(timeout=…) to make mirroring deterministic in a test and close_mirror() to drain in a teardown.
The CLI-facing
session sync reconciler is not yet shipped (blocked on #3645). Until then, flush_mirror is the deterministic seam for reconciliation and tests.Backward Compatibility
mirror=None is the default — existing DefaultSessionStore(...) call sites are byte-for-byte unchanged, with no daemon thread spawned and no dependency added.
Best Practices
Keep append idempotent
Keep append idempotent
Records carry stable ids, so re-appending the same record is last-writer-wins. Don’t add merge logic — rely on the id.
Don't raise on transient failure
Don't raise on transient failure
The writer retries with backoff and drops-to-log on permanent failure. Let it — raising in
append just burns a retry.Never do heavy work in append
Never do heavy work in append
append runs on the single writer thread. Keep it a fast network/DB write so the queue drains and doesn’t back up.Always call close_mirror() on shutdown
Always call close_mirror() on shutdown
close_mirror() drains in-flight records before stopping the daemon writer. Skipping it can drop the last queued turns.Related
Session Store
The default JSON store the mirror wires into
Cross-Session Recall
Search past sessions — hydrate non-local ids from a mirror
Sessions & Remote Agents
Stateful conversations across restarts
Gateway Session Persistence
Persist gateway sessions across restarts

