Skip to content

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.


A SQLite FTS5 index at ~/.config/threadle/search.db, built with Node’s built-in node:sqlite:

TablePurpose
ftsFTS5 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.


Refresh runs before every query, so results are never stale:

DocumentStampReindexed when
SessionThe session’s updatedAtStamp differs from the last indexed value
PayloadThe payload file’s mtimemtime 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.


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.

Terminal window
curl 'http://127.0.0.1:4570/api/search?q=retry+backoff&provider=opencode&role=assistant'
ParamValuesDefault
qQuery string — empty returns no results
limit1–10040
providerclaude-code · opencode · cursor · payloadall
doctypemessage · payloadall
roleuser · assistantall

Unknown filter values are ignored rather than erroring.


Three chip rows map onto those parameters:

UISends
service — all / claude / opencode / cursor / payloadsprovider
type — all / transcript / contextdoctype=message / doctype=payload
role — all / input / outputrole=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.


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.