How the Reasonix History Catalog Powers Session Search: Architecture & Implementation

The Reasonix history catalog is a disposable SQLite FTS5 full-text index that projects searchable fields from every chat turn across all session files, enabling fast cross-session search with pagination and context extraction.

The esengine/DeepSeek-Reasonix project maintains every conversation turn—system prompts, user messages, assistant replies, and tool calls—in append-only JSONL session files. To make this chat history searchable without scanning raw logs, Reasonix implements a history catalog: a lightweight, rebuildable full-text search layer that sits between the on-disk session storage and the desktop UI's search interface.

What the History Catalog Is: A Projection, Not a Source of Truth

The catalog is intentionally disposable. It does not store complete messages; rather, it projects only the searchable fields from each turn—role, text content, and tool-call data—while preserving a reference back to the original session file and the turn's position within it. This design keeps the index small, fast to rebuild, and separate from the durable session store.

The core implementation lives in internal/historycatalog/catalog.go, which manages the SQLite FTS5 index, rebuild orchestration, and query execution.

Registering Catalog Roots at Startup

Before the catalog can index anything, Reasonix registers roots that define where session files live on disk. Each root is represented by historycatalog.Root as defined in internal/historycatalog/types.go:

  • Global sessions: The default session directory
  • Project-scoped sessions: Session files tied to specific workspaces
  • Archive: Older sessions moved to cold storage
roots := []historycatalog.Root{
    {Path: config.SessionDir(), Source: "global", Scope: "global"},
    {Path: config.ArchiveDir(), Source: "archive", Scope: "global", Archive: true},
}

The desktop/history_catalog.go file wraps this root construction and exposes it to the desktop layer. When Reasonix launches, these roots are passed to historycatalog.Rebuild to populate or refresh the index.

Rebuilding the Index: Background Processing with Status Updates

The historycatalog.Rebuild function—also in internal/historycatalog/catalog.go—walks every registered root, parses each JSONL session file, and inserts extracted messages into SQLite FTS5. This runs asynchronously and publishes historycatalog.Status updates that the UI displays as progress indicators.

For manual control, the CLI command reasonix catalog reindex history (implemented in internal/cli/catalogs_history.go) triggers the same rebuild pipeline on demand.

Searching the History Catalog: Filters, Pagination, and Relevance

The desktop UI initiates searches through catalog.Search with a historycatalog.SearchRequest. This request supports multiple filtering dimensions:

  • Full-text query: Arbitrary search terms against message content
  • Role filtering: Restrict to specific turn kinds (user, assistant, system, tool)
  • Tool name filtering: Find turns that invoked specific tools
req := historycatalog.SearchRequest{
    Query:    "authentication bug",
    Kinds:    []string{"user", "assistant"},
    ToolName: "",
    Limit:    20,
}
hits, err := catalog.Search(ctx, req)

Results return as []historycatalog.Candidate, each containing:

  • SessionPath: Location of the source JSONL file
  • MessageIndex: Position of the turn within that session
  • Relevance rank for result ordering

Cursor-Based Pagination

Large result sets stream through historycatalog.SearchCursor. The UI requests subsequent pages by passing the cursor's rank and session location, allowing efficient batch retrieval without re-scanning the entire index. This mechanism lives alongside the core search implementation in internal/historycatalog/catalog.go.

Context Extraction: Bridging Index Hits to Full Conversations

The catalog stores only minimal data. To show meaningful search results, Reasonix fetches surrounding turns from the original session file using desktop/history_search_collect.go. Given a Candidate, this code:

  1. Opens the session file at Candidate.SessionPath
  2. Parses the JSONL into an in-memory slice
  3. Extracts a window around Candidate.MessageIndex (typically ±3 turns)
cand := hits[0]
sessionPath := cand.SessionPath
msgIdx := cand.MessageIndex

sessionMsgs, _ := readSessionFile(sessionPath)
context := sessionMsgs[max(0, msgIdx-3) : min(len(sessionMsgs), msgIdx+3)]

The resulting []HistorySearchContextLine gives the UI a navigable snippet with full conversational context.

Lifecycle Management: Shared Instances and Observers

The internal/history/indexed.go file manages the shared catalog instance, coordinates observers for change notifications, and handles asynchronous updates when sessions are modified. This ensures that long-running Reasonix processes keep their search indexes reasonably fresh without blocking the main application loop.

Summary

  • The history catalog is a disposable SQLite FTS5 projection of searchable message fields, not a primary data store.
  • Roots (historycatalog.Root) define where session files live; historycatalog.Rebuild indexes them at startup or on demand.
  • Search (catalog.Search) supports full-text queries, role filters, and tool name filters, returning ranked Candidate results.
  • Pagination uses historycatalog.SearchCursor for efficient streaming of large result sets.
  • Context extraction in desktop/history_search_collect.go retrieves surrounding turns from source JSONL files to populate the UI.

Frequently Asked Questions

How is the history catalog different from the session files themselves?

The session files are the durable source of truth—append-only JSONL logs containing complete turn data. The history catalog is a disposable, searchable projection that extracts only fields needed for full-text search (role, text, tool data) plus location references. If the catalog is deleted, historycatalog.Rebuild regenerates it from the session files without data loss.

Can I trigger a catalog rebuild manually?

Yes. The CLI command reasonix catalog reindex history (in internal/cli/catalogs_history.go) initiates a rebuild on demand. The desktop application also rebuilds automatically at startup when roots are registered.

What search filters does the history catalog support?

The historycatalog.SearchRequest accepts three filter types: a full-text Query string against message content, a Kinds slice restricting to specific roles (user, assistant, system, tool), and a ToolName string to find specific tool invocations. These can be combined arbitrarily.

How does Reasonix handle pagination for large search results?

Results stream through historycatalog.SearchCursor. The client requests the next batch by passing the cursor's rank and session position, allowing the query to resume efficiently without re-executing the full search.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →