Session compression lineage tracking and deduplication fixes
NousResearch/hermes-agent · cluster 10374 · agent
Data as of
4 open items. Fix merged in #43487. Check it covers your case.
The short answer
- Known?
- Yes. 18 related items grouped here, 4 issues already closed.
- Fix event
- A fix was merged to main in PR #43487. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PRs #43487, #86595, #134061. That is separate from the fix event. - Still open
- 4 issues and 0 PRs.
Next step: read #43487, then check whether your report is one of the 4 still open.
Read #43487Pick up a Lab taskThese issues and fixes center on how compressed session lineages are identified, listed, and displayed — covering bugs where desktop shows duplicate sidebar rows, hides compressed sessions, loses transcripts, and where gateway RPCs and state fail to propagate lineage root IDs correctly. Several fixes address deleting or preserving full lineages rather than only visible tips.
Open issues4
- Issue#10374Feature: lineage-aware /resume core for CLI and gateway
- Issue#44913Desktop hides compressed session lineage and can spin forever while response exists in gateway dashboard
- Issue#48524fix(sessions): delete entire compression lineages, not just the visible tip
- Issue#50192Desktop should treat compressed session lineages as one logical chat
Open PRs0
Fixes on record
-
#43487 fix(desktop): deduplicate sidebar rows by compression lineage
files:
apps/desktop/src/store/session.test.ts,apps/desktop/src/store/session.tsShow the diff
-
apps/desktop/src/store/session.test.ts@@ -133,13 +133,52 @@ describe('mergeSessionPage', () => { it('keeps a pinned session matched by its lineage root after compression', () => { // The pin is stored on the lineage-root id, but the loaded row surfaces // under its live compression tip. Matching on _lineage_root_id keeps it. - const previous = [session({ id: 'tip', _lineage_root_id: 'root' })] - const incoming = [session({ id: 'other' })] + const previous = [session({ id: 'tip', _lineage_root_id: 'root' })] as SessionInfo[] + const incoming = [session({ id: 'other' })] as SessionInfo[] const merged = mergeSessionPage(previous, incoming, ['root']) expect(merged.map(s => s.id)).toEqual(['tip', 'other']) }) + + it('evicts an old compression tip when the incoming page has the new tip from the same lineage', () => { + // Repro of #43483: after auto-compression rotates the tip (#4 → #5), + // the sidebar showed both the old tip and the new tip as separate rows. + // The old tip must be evicted because its lineage key matches the incoming + // new tip's lineage key. + const previous = [ + session({ id: 'tip-4', _lineage_root_id: 'root' }), + session({ id: 'other' }), + ] as SessionInfo[] + const incoming = [ + session({ id: 'tip-5', _lineage_root_id: 'root' }), + ] as SessionInfo[] + + // 'tip-4' is in the keep set (e.g. it was the active/working session), + // but should still be evicted because the incoming page carries the same + // lineage under a new tip id. + const merged = mergeSessionPage(previous, incoming, ['tip-4']) + + expect(merged.map(s => s.id)).toEqual(['tip-5']) + // The new tip comes from the server payload. + expect(merged.find(s => s.id === 'tip-5')?._lineage_root_id).toBe('root') + }) + + it('preserves an unrelated pinned session even when lineage dedup is active', () => { + // Regression guard: lineage dedup must not accidentally evict sessions + // from a different lineage that happen to be in the keep set. + const previous = [ + session({ id: 'a-old', _lineage_root_id: 'lineage-a' }), + session({ id: 'b', _lineage_root_id: 'lineage-b' }), + ] as SessionInfo[] + const incoming = [ + session({ id: 'a-new', _lineage_root_id: 'lineage-a' }), + ] as SessionInfo[] + + const merged = mergeSessionPage(previous, incoming, ['b']) + + expect(merged.map(s => s.id)).toEqual(['b', 'a-new']) + }) }) describe('workspaceCwdForNewSession', () => { -
apps/desktop/src/store/session.ts@@ -125,10 +125,18 @@ export function mergeSessionPage( } const incomingIds = new Set(incoming.map(session => session.id)) + // Deduplicate by compression lineage: when auto-compression rotates the tip + // id (old #4 → new #5), the incoming page carries the new tip but the + // previous list still holds the old one. Without lineage-level dedup both + // rows survive as separate sidebar entries (fixes #43483). + const incomingLineageKeys = new Set( + incoming.map(session => session._lineage_root_id ?? session.id) + ) const survivors = previous.filter( session => !incomingIds.has(session.id) && + !incomingLineageKeys.has(session._lineage_root_id ?? session.id) && (keep.has(session.id) || (session._lineage_root_id != null && keep.has(session._lineage_root_id))) )
-
-
#86595 fix(desktop): salvage compaction transcript-visibility cluster — surface archived turns + preserve running-session transcripts (#67871)
files:
apps/desktop/src/app/session/hooks/use-session-actions.test.tsx,apps/desktop/src/app/session/hooks/use-session-actions/index.ts,apps/desktop/src/app/session/hooks/use-session-actions/utils.test.ts,apps/desktop/src/app/session/hooks/use-session-actions/utils.ts,apps/desktop/src/hermes.test.ts,apps/desktop/src/hermes.ts,hermes_cli/web_routers/sessions.py,hermes_state.py,tests/hermes_cli/test_web_server.py,tests/hermes_state/test_get_messages_include_compacted.py -
#134061 fix(gateway): preserve session lineage in the session.list RPC
files:
apps/shared/src/gateway-contract.generated.ts,apps/shared/src/gateway-contract.openrpc.json,tests/tui_gateway/test_session_list_lineage_rpc.py,tui_gateway/contracts/sessions.py,tui_gateway/methods_session.py,tui_gateway/methods_session_branch.py
7 PRs closed without merging
From the close comments: duplicate 2 · superseded by another PR 2 · closed without comment 1 · merged via another change 1 · other 1
-
#44069 fix(desktop): dedupe compressed session tips in sidebar merge · duplicate →
#43487,#43483 - #45084 fix: Desktop hides compressed session lineage and can spin forever while response exists in gateway dashboard (#44913) · closed without comment
-
#48525 fix(sessions): delete entire compression lineages, not just the visible tip · superseded by another PR →
7b26530,aaf7f19,4f64356,ecac27c -
#48634 fix(state): delete entire compression lineages (#48524) · duplicate →
#48525,#48524 - #66675 fix(gateway): preserve session lineage in list RPC · superseded by another PR
Closed issues · 4
- Closed#43483Desktop sidebar can show duplicate rows for the same auto-compressed session lineage
- Closed#66663[gateway] session.list RPC drops _lineage_root_id, breaking desktop session dedup
- Closed#66664[gateway] _lineage_root_id not set on uncompressed tips, breaking desktop lineage dedup
- Closed#67871[Bug]: Running compressed Desktop sessions replace the complete transcript
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.08: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab