Skip to content

feat(core): add search({ excludeSession }) and derive missing session titles - #17

Open
Cyb3rN8 wants to merge 1 commit into
tommy0103:mainfrom
Cyb3rN8:feat/exclude-session-and-derived-titles
Open

feat(core): add search({ excludeSession }) and derive missing session titles#17
Cyb3rN8 wants to merge 1 commit into
tommy0103:mainfrom
Cyb3rN8:feat/exclude-session-and-derived-titles

Conversation

@Cyb3rN8

@Cyb3rN8 Cyb3rN8 commented Jul 30, 2026

Copy link
Copy Markdown

Fixes the two behavioural items from #16. Documentation-only items from that issue are not included here.

search(text, { excludeSession })

Searching history from inside a session reliably hits that session's own prompt, because the query terms came from it. Filtering afterwards in the query script does not recover the loss: limit is applied in SQL, so the self-hits have already displaced evidence from other sessions. excludeSession accepts one session ID or an array and filters before limit.

Derived session titles

overview() and sessions() are advertised as a navigation map, but on a current index most rows come back with title: null — 792 of 821 Claude sessions on the index I measured. Both title sources are usually absent now: ai-title transcript rows have become rare (most recent one in a 264-transcript project directory: 2026-07-21), and history.jsonl entries no longer carry a title field at all, so the historyTitles path in providers/claude.ts cannot populate. Neither is an indexing failure — every title that exists is picked up.

overview(), sessions(), and search() now fall back to the first 80 characters of the opening user message, skipping <command-…> envelopes so the label describes the task instead of naming the slash command. A session with no user text keeps title: null rather than getting an invented one.

Implemented as a SQL expression in the query layer (titleExpr()), for two reasons: it covers sessions indexed before this change without a rebuild, and it leaves sessions.title meaning "a title the transcript actually provided". The tradeoff is that a derived title is not distinguishable from a stored one in the returned row — the docs call it a navigation label. Happy to switch to a separate field, or to compute it at index time, if you would rather have the provenance explicit.

Notes

  • skill-doc changes are limited to what documents the new behaviour: the search() opts list, an Exclude Yourself entry in the Retrieval Contract, the sessions() note about derived titles, and the api-reference.md rows. The broader documentation suggestions stay in First-pass retrieval wastes most of its budget: untitled sessions, self-hits, and known-empty memory recall #16.
  • Tests: two added to tests/query.test.mjs (SQL-level exclusion including the limit: 1 case, and the title fallback including the command-envelope skip and the null case). On this machine npm test goes from 207 passing to 209 with an unchanged set of 12 failures — all in the app/session suites, which need Electron deps that are not installed here.
  • Branched from current main, so this may touch api-reference.md near feat(core): index failed tool results for retrieval #15. Say the word if you want it rebased onto either of the open branches instead.

… titles

Two things make a first-pass retrieval noisier than it needs to be.

Self-hits: searching history from inside a session almost always returns that
session's own prompt, because the query terms were taken from it. A script can
filter the rows afterwards, but `limit` has already been spent on them, so real
evidence from other sessions falls off the end. `excludeSession` accepts one ID
or an array and filters in SQL, before `limit` applies.

Missing titles: `overview()` and `sessions()` are advertised as a navigation
map, but on a current index most rows come back with `title: null`. Both upstream
sources are usually absent now — recent Claude Code versions rarely write an
`ai-title` transcript row, and `history.jsonl` entries no longer carry a `title`
field at all (they are prompt history: display/pastedContents/timestamp/project/
sessionId). On the index I checked, 792 of 821 Claude sessions had no title, so
`overview({ limit: 6 })` is six unlabelled IDs.

`overview()`, `sessions()`, and `search()` now fall back to the first 80
characters of the opening user message, skipping `<command-…>` envelopes so the
label is the task rather than the slash command's name. Sessions with no user
text still return null rather than an invented title. This is computed in the
query layer, so it also covers sessions that were indexed before this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant