fix(api): serve full compression lineage in session messages endpoints (#79565) - #79785
thatssoheil wants to merge 2 commits into
Conversation
Related: #59661 addresses the same session-message endpoints with ancestor-inclusive reads. This PR uses the existing display-history projection to preserve replay-overlap handling, so these are competing approaches rather than duplicates. |
|
Thanks for the pointer. I checked #59661 — it's the same bug class (#51058: compression rotation hides the parent transcript) across the same three surfaces (api_server messages, web_routers/sessions.py REST, hermes_state lineage read). Noted, and I see it's being actively shepherded (rebased on current main, sweeper keep_open). The approaches differ in one material way worth flagging for the maintainers:
So they're genuinely competing approaches, not duplicates. Happy to close this PR if maintainers prefer #59661's simpler SQL-level read; if the dedup-consistency argument wins, this one is the safer merge. Either way, no hard feelings — flagging for the triage decision. |
d48fc6b to
a1d6fc2
Compare
…NousResearch#79565) Desktop session switching prefetches GET /api/sessions/{id}/messages. The endpoint resolved the id to the latest compression child and loaded only that child's rows, so a compressed conversation showed only the latest continuation segment while older dialogue stayed invisible (still intact in the parent sessions of state.db). Fix: resolve to the live tip (forward, resolve_resume_session_id), then serve the FULL lineage display history (backward, ancestors -> tip) via get_resume_conversations() - the same projection the CLI/TUI resume path uses, which already reconciles overlap copied into continuation children. The model-fed tip history is unchanged; only the display transcript is now complete. RED-GREEN: lineage test fails on old code (child-only rows), passes with fix. Uncompressed sessions unchanged.
NousResearch#79565) Both session-messages endpoints resolved to the latest compression child and loaded only that child's rows, so after a compression fork the desktop showed only the most recent segment - earlier dialogue silently vanished. - gateway/platforms/api_server.py _handle_session_messages: resolve to the tip (forward), then serve the FULL lineage display history via get_resume_conversations() (ancestors -> tip, replayed-user dedup). - hermes_cli/web_routers/sessions.py get_session_messages: page over the full lineage projection (get_messages_as_conversation include_ancestors) instead of get_messages(sid); pagination contract preserved. RED-GREEN: lineage tests fail on old code (child-only rows), pass with the fix, for both endpoints.
a1d6fc2 to
3d1d872
Compare
|
Following @friendfish's triage below (thank you - the live trace and the #59661 status check are exactly the state-of-record this thread needed): #59661 (opened 2026-07-06, a month before this issue) already implements the same fix class on the same endpoints, it is green and mergeable, and the remaining findings on it (@ehz0ah's API-fork walk + the rotation-clone residue) are tracked in that thread. I am closing this PR in its favor instead of keeping a parallel implementation open - see my post on #59661 for the two findings my branch carried that are worth folding in there (cross-segment rotation-clone dedupe for non-user roles, and branch gating on the REST path). Closing as duplicate; will delete the branch after this lands on the record. If #59661's shape changes in review, this thread's 5-segment lineage repro (326 raw rows -> 67 shown) remains the reference test case. |
|
Duplicate of #59661 (predates the issue; see close-comment above for the two findings worth folding in). |
Fix: serve full compression lineage in session messages endpoints (#79565)
What
The desktop session-switch view showed only the latest compression
segment of a long conversation — earlier segments appeared lost. Two
endpoints shared the same child-only fetch bug:
gateway/platforms/api_server.py_handle_session_messages—resolved to the latest compression child and loaded only that child's
rows via
get_messages.hermes_cli/web_routers/sessions.pyget_session_messages(thedashboard endpoint) — same child-only resolution.
Fix
Both endpoints now serve the full compression lineage:
api_server.py: resolve forward to the compression tip, then reuse theexisting, tested
get_resume_conversations(tip)display_history — whichalready walks the whole lineage root→tip and reconciles replayed-user
messages (the exact overlap handling the issue asks for).
sessions.py: useget_messages_as_conversation(include_ancestors=True)and page over the full lineage (limit/offset contract preserved).
No new lineage architecture — the projection already existed; this is a
surgical endpoint change.
_message_responseshape compatibility preserved.Tests
tests/gateway/test_session_messages_lineage.py(new): gateway endpointreturns the full root→tip lineage; uncompressed sessions unchanged.
tests/hermes_cli/test_web_server.py(extended): dashboard endpointreturns full lineage, paginates across segments, guard tests for
negative/oversized limits.
All fail on the old child-only code for the right reason (
'early question' in ['latest question', 'latest answer']), pass with the fix. 155 testsgreen across the three neighboring suites.
Prior art
This was previously attempted (#71857) with custom lineage projection and
caused duplicates; reusing
get_resume_conversationsdisplay_historyavoids re-introducing that class of bug.