Search
“Which session discussed the retry logic?”. Search answers that across every Claude Code, opencode, Cursor, Antigravity, Codex, Copilot, and Grok transcript on the machine, plus every captured context payload.
The index
Section titled “The index”A SQLite FTS5 index at ~/.config/threadle/search.db, built with Node’s built-in node:sqlite:
| Table | Purpose |
|---|---|
fts | FTS5 virtual table: text indexed, with unindexed provider, session_id, doc_type, role, ts, extra |
indexed | (provider, session_id) → stamp. What has already been ingested |
Indexed text is the text and thinking parts of each message, joined per message and capped at 32,000 characters. Messages with no text are skipped. Payloads are indexed as a single document each, same cap, carrying their kind and source session in extra.
If the runtime has no node:sqlite, threadle logs search index unavailable once and search simply returns nothing. Nothing else breaks.
Incremental reindex
Section titled “Incremental reindex”Refresh runs before every query, so results are never stale:
| Document | Stamp | Reindexed when |
|---|---|---|
| Session | The session’s updatedAt | Stamp differs from the last indexed value |
| Payload | The payload file’s mtime | mtime differs |
A changed session has its messages deleted and reinserted inside a transaction, so a failure rolls back rather than leaving half a session in the index. Sessions use the full transcript where a provider keeps one, so abandoned branches stay findable.
The index is disposable: Settings → internals can clear it, and the next search rebuilds it.
Querying
Section titled “Querying”Each whitespace-separated token is quoted and given a prefix wildcard, then joined. retry log becomes "retry"* "log"*, i.e. All tokens must match, each as a prefix. Results come back ORDER BY rank with a SQLite snippet() of up to 14 tokens, ellipsised, with matches wrapped in ⟪ ⟫ and re-rendered as highlights in the UI.
curl 'http://127.0.0.1:4570/api/search?q=retry+backoff&provider=opencode&role=assistant'| Param | Values | Default |
|---|---|---|
q | Query string. Empty returns no results | |
limit | 1–100 | 40 |
provider | claude-code · opencode · cursor · antigravity · codex · payload | all |
doctype | message · payload | all |
role | user · assistant | all |
Unknown filter values are ignored rather than erroring.
The Search view
Section titled “The Search view”Three chip rows map onto those parameters:
| UI | Sends |
|---|---|
| service. All / claude / opencode / cursor / antigravity / codex / payloads | provider |
| type. All / transcript / context | doctype=message / doctype=payload |
| role. All / input / output | role=user / role=assistant |
Typing is debounced by 300 ms. Each hit shows a provider dot (context payloads use the context colour), the session title. Resolved live from discovery. A role badge, the payload kind, and the relative time, above the highlighted snippet.
Click-through
Section titled “Click-through”Clicking a hit expands it in place with two tabs:
- ≡ transcript. Jumps to the matched message (highlighted), loading a window around it. Foldable thinking, tool calls, and results, and a copy button per message
- info. The session info panel: tokens, cache, costs, context pressure, tools, files, downloads
Plus ⌗ blueprint and → workflow. A payload hit resolves to its source session, so searching your saved context still lands you in the conversation that produced it (without a message jump).
Related
Section titled “Related”- Sessions. Where transcripts come from
- Context library. The payload half of the index
- Lineage. Uses the same index to filter its canvas