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.
Picking the right shape:
Which store should I use?
Configuration Options
session_search Parameters
Return Shapes
- Discovery
- Scroll
- Browse
Returned when The
query is set: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 defaultretention="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: Trueon a returned context message marks a turn recalled fromarchived_messages. The compaction boundary stays visible to the caller rather than being silently blended into the current window.window()shares the coordinate space. Ananchor_indexfrom 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 exportedSqliteTranscriptStoreshare 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_metatable. An internal key/value table (content_versionkey) tracks the persisted index-content version. Operators upgrading an existing store should expect this table to appear in the SQLite index file.
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_idcollapse 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
CLI: praisonai session search
The same engine (SqliteSessionStore / FTS5+bm25) is reachable from the terminal — not just from an agent tool:
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
Keep window small for fast scans
Keep window small for fast scans
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.Treat scores as relative rankings
Treat scores as relative rankings
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.
Scope sessions per user in multi-user deployments
Scope sessions per user in multi-user deployments
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.Scale-out: swap in SqliteSessionStore
Scale-out: swap in SqliteSessionStore
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.Related
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)

