diff --git a/docs/sessions.md b/docs/sessions.md index 39b30d5..43f6b42 100644 --- a/docs/sessions.md +++ b/docs/sessions.md @@ -96,7 +96,9 @@ The indexes are built on the next store init (`CREATE INDEX IF NOT EXISTS`, a few seconds on 150 MB) and session search drops to tens of milliseconds. -**Counts and match positions.** The sidebar needs a display count and a preview per session, and a search page also needs *where* the term appears. Both used to walk the heap: the message rows are wide (`content`, `thinking`), and the session index has no `is_display` column. `idx_session_messages_counts` covers `(session_id, sequence_number, is_display)`, so the counts and the position numbering are index-only scans once the table is vacuumed (prod, 1.3k sessions: ~500 ms → ~100 ms). The match query numbers positions over that projection and reads `content` only for the rows the trigram index returns; numbering rows together with their content took ~1.7 s for a common term across the largest sessions, against ~100 ms this way. +**Counts and match positions.** The sidebar needs a display count and a preview per session, and a search page also needs *where* the term appears. Both used to walk the heap: the message rows are wide (`content`, `thinking`), and the session index has no `is_display` column. `idx_session_messages_counts` covers `(session_id, sequence_number, is_display)`, so the counts and the position numbering are index-only scans once the table is vacuumed (prod, 1.3k sessions: ~500 ms → ~180 ms). The match query numbers positions over that projection and reads `content` only for the rows the trigram index returns; numbering rows together with their content took ~1.7 s for a common term across the largest sessions, against ~80–130 ms this way. + +**Building the list.** What is left of `GET /sessions` after the queries is Python. Two things kept it visible: `Session`'s private write baseline used `PrivateAttr(default_factory=dict)`, which pydantic re-inspects on every construction (16 µs per row against 0.6 µs for a plain default), and every head parsed `planning_logs`/`session_metadata` that no list view reads — up to ~95 kB of JSON per session on prod. Both are gone: a 1.3k-session list now spends ~20 ms building heads on top of the queries above (~190 ms warm in total). **Ownership:** Legacy sessions (`user_id IS NULL`) are accessible only to admins. New sessions created by authenticated users carry `user_id`. The `list_all()` method respects the `is_admin` flag to filter appropriately.