Skip to main content
An agent can now search its own past conversation transcripts — asking “what did we decide about the billing migration?” across every stored session, including older turns that have been compacted out of the active window. The user asks what was decided last week; session search recalls matching turns from prior stored conversations — even ones summarised out of the live context by the default retention="compact" policy. Search scans both archived (compacted) turns and the active window, so a detail summarised out of the live context is still recalled:

Quick Start

1

Add session_search to an agent

2

Call the three shapes directly

3

Scale to thousands of sessions

4

Recall a compacted (archived) detail


How It Works

Sessions live at ~/.praisonai/sessions/*.json — one JSON file per session. The default DefaultSessionStore search is a dependency-free substring/keyword scan with no setup. The index hop above appears only when a SqliteSessionStore is active; the default flow reads the JSON files directly. Both stores scan a single archived + active projection of each session: archived_messages (turns compacted out of the live window) are scanned ahead of the active messages, so a compacted conversation stays fully recallable rather than losing its raw turns. Every returned context message carries an archived: bool flag so the caller can see exactly where the compaction boundary falls instead of having archived turns silently blended into the current window.
A session-id that was never seen locally can be hydrated on demand from a configured mirror via SessionMirrorProtocol.load(session_id). See Session Mirror.
Picking the right shape: Which store should I use?

Configuration Options

session_search Parameters

Return Shapes

Returned when query is set:
The bookends key is present only when the session has more than BOOKEND_SIZE (2) user+assistant messages — mirroring SessionHit.as_dict().Each entry in messages includes index, role, content, timestamp, and an archived boolean. archived: true marks a turn recalled from archived_messages (compacted out of the active window); archived: false marks a turn still in the live window.

Scoring

Discovery mode scores hits using keyword matching: Ties are broken by recency (updated_at descending). Scores are heuristic — treat them as relative rankings, not probabilities. Snippets are centred on the first match and trimmed to ~120 characters with … ellipses.

Recall Across Compacted History

Under the default retention="compact" policy, older raw turns are moved into archived_messages and replaced in the active window by a single synthetic summary. Those archived turns stay searchable: search() and window() both scan an archived + active projection, with archived turns scanned first so a hit anchors in context exactly like an active-turn hit.
  • archived: True on a returned context message marks a turn recalled from archived_messages. The compaction boundary stays visible to the caller rather than being silently blended into the current window.
  • window() shares the coordinate space. An anchor_index from a discovery hit — including one landing on an archived turn — resolves to the exact same message when you scroll around it.
  • Both built-in stores use one projection. DefaultSessionStore, SqliteSessionStore, and the exported SqliteTranscriptStore share the same archived + active projection, so results cannot drift between backends.
Before this change, cross-session recall indexed only a session’s active messages; compacted raw turns were dropped from the index on the next write and became permanently unsearchable. session_search now spans archived + active turns.

SQLite index upgrade (INDEX_CONTENT_VERSION → 2)

SqliteSessionStore bumps its INDEX_CONTENT_VERSION to 2 because _flatten now indexes archived_messages alongside the active messages. On the first startup after upgrade, the store detects the stale version and rebuilds every session’s FTS row once, so archived-only queries work immediately rather than only after each session’s next write.
  • No user action required. Startup performs a bounded one-time backfill, then persists the new version so subsequent startups fall back to the cheap “skip already-indexed sessions” path.
  • New session_index_meta table. An internal key/value table (content_version key) tracks the persisted index-content version. Operators upgrading an existing store should expect this table to appear in the SQLite index file.
See Session Store → SqliteSessionStore for the full index schema.

Anchored, Demoted, Deduped Results

Every discovery hit — from either store — carries anchored context, demotes noisy automated runs, and collapses continuations of the same conversation.
  • Bookends: {"opening": [...], "closing": [...]} — the first and last 2 user+assistant messages of the session, so the agent sees goal → match → resolution in one call.
  • Automated demotion: sessions tagged as automated/scheduled or whose message rate exceeds ~60 msg/hour have their score multiplied by 0.25.
  • Lineage dedup: reset/compacted continuations sharing lineage_id / root_session_id / thread_id collapse to the single best-scoring hit.

Automated-session heuristics

Demotion multiplies the session’s total score by 0.25 (constant AUTOMATED_DEMOTION).

Bookends

Bookends return the first / last 2 user+assistant messages (BOOKEND_SIZE = 2). They are omitted when the session has ≤ 2 conversational messages, to avoid duplicating messages already in the context window.

Lineage keys

parent_session_id is intentionally not a lineage key — it points at an immediate parent, so sibling sessions forked from one parent stay distinct instead of suppressing each other.

Common Patterns

Gateway assistant recalling a past decision

”What was I working on?” at the start of a new session

Programmatic access via the store directly

The same engine (SqliteSessionStore / FTS5+bm25) is reachable from the terminal — not just from an agent tool:
Results are ranked by score, deduped by session_id across the project and global stores, and printed as a table (or JSON with --json). See Session Search for the full command reference.

Best Practices

Use window=3 to window=5 for discovery. Widen only when you need more surrounding context after finding a hit — use scroll mode for that.
Scores are heuristic keyword counts, not probability-calibrated relevance scores. A score of 4.0 beats 2.0 but says nothing absolute. Always let the agent reason about the returned snippets.
The default search is per-store, not per-user. In multi-user deployments, prefix session_id values with a user identifier (e.g. user_42_session_xyz) and search within those sessions explicitly.
For long-lived gateway bots with thousands of sessions, swap in SqliteSessionStore — a stdlib sqlite3 + FTS5 index turns recall into a bounded MATCH ... ORDER BY bm25 lookup instead of a full-directory scan. It transparently falls back to the substring scan if sqlite3 or FTS5 is unavailable, so nothing breaks. See Session Store.
Not to be confused with SqliteTranscriptStore. SqliteSessionStore (this accordion) is an FTS5-backed recall index for cross-session search. SqliteTranscriptStore is a separate class that replaces the per-session JSON files under the gateway’s session.persist path with one WAL SQLite row per session. See SQLite Transcript Store.

Advanced: SearchableSessionStoreProtocol

The default store implements SearchableSessionStoreProtocol, a separate runtime_checkable protocol that adds search to any session store backend.

Protocol Methods

SessionHit Fields

SessionSummary Fields

SessionStoreProtocol (the core persistence contract) is unchanged and backward compatible. SearchableSessionStoreProtocol is an additive, separately runtime-checkable protocol.

Session chat history is written to and read from the configured SessionStore first, then falls back to Memory for backward compatibility. Both restore paths now agree with the save path — if a resumed session comes back empty, check that the same store is configured on both save and restore.

Bot Default Tools

Where session_search fits in the opt-in tool list for bots

Session Store

How sessions are persisted in ~/.praisonai/sessions/

Memory

Distilled long-term memory (different from raw session transcripts)

Knowledge

RAG over documents (also different from session recall)