Skip to content

perf(state): add messages(session_id, id) index for windowed reads (#76237 salvage) - #76877

Merged
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/76237-messages-index
Aug 2, 2026
Merged

perf(state): add messages(session_id, id) index for windowed reads (#76237 salvage)#76877
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/76237-messages-index

Conversation

@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Salvage of #76237 by @spfcraze — cherry-picked to preserve authorship. Clean as submitted; no follow-up needed.

Context — what this changes for users

session_search's scroll shape (get_messages_around) — used every time the agent or a user pages through past-conversation context — currently walks the session's whole message history to find the window. On long sessions (thousands of messages) that's a full scan per call. The new messages(session_id, id) index turns it into an index seek: 32-307x measured, and EXPLAIN-verified (346 VM steps → 2 on the probe workload).

Bonus coverage (verified by EXPLAIN probes): the rewind query (WHERE session_id=? AND id>=?) and last-user-message lookups (ORDER BY id DESC LIMIT 1) also pick up the new index.

Review & verification (full pipeline run)

  • 142/142 tests/test_hermes_state.py green, ruff clean.
  • Mutation check: reverting the schema makes the perf-contract test fail with the honest message ("854x100 VM steps — the session-history scan is back") and the parity test fail on the missing index; restored → green.
  • Schema placement verified: both columns are base-schema, so SCHEMA_SQL (not DEFERRED_INDEX_SQL) is correct — CREATE INDEX IF NOT EXISTS is idempotent for existing DBs.
  • Write cost honestly assessed: ~50 bytes/row (~5MB per 100K messages); inserts append at the right edge of each session's key range (monotonic id), so B-tree maintenance is cheap and sequential-ish.
  • Test quality: perf contract measured via SQLite progress-handler step counts (behavior contract, not EXPLAIN-text snapshot), threshold has >25x headroom; the DROP INDEX test runs on a function-scoped fixture (no cross-test pollution).

Closes #76237.

…ueries

Every ORDER BY id query on the messages table sorted or scanned the
whole session: get_messages_around's window seek, latest_message_row_id
(LIMIT 1), and get_messages' full-load ordering all paid O(session
history) per call — hot mid-turn via session_search and reactions.
messages.id is an original column (INTEGER PRIMARY KEY AUTOINCREMENT),
so the index lives in SCHEMA_SQL next to idx_messages_session — no
legacy-column migration hazard (the kanban lesson from NousResearch#28776 does not
apply).

Measured (real schema, one 20k-message session, median of 30):
get_messages_around 7.08 -> 0.22 ms (32x), latest_message_row_id
3.37 -> 0.011 ms (307x), get_messages full load 111.6 -> 98.6 ms
(1.13x — remaining cost is row deserialization, not the sort).
Window results byte-identical at probe points across the session.

Tests: VM-step pin (get_messages_around bounded work, calibrated
~12 vs ~855 handler calls, threshold 300 — fails without the index)
and window parity with/without the index. No EXPLAIN/plan text
(behavior contracts, AGENTS.md).
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) August 2, 2026 15:42
@kshitijk4poor
kshitijk4poor merged commit 8f91e24 into NousResearch:main Aug 2, 2026
35 checks passed
@kshitijk4poor
kshitijk4poor deleted the salvage/76237-messages-index branch August 5, 2026 07:10
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.

2 participants