Search
“Which session discussed the retry logic?” — Search answers that across every Claude Code, opencode, and Cursor 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 · 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 / 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 — the full transcript, loaded 200 messages at a time, with 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.
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