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

4open issues
0open PRs
18items grouped
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.

These 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

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #43487 fix(desktop): deduplicate sidebar rows by compression lineage

    merged to main

    files: apps/desktop/src/store/session.test.ts, apps/desktop/src/store/session.ts

    Show 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)

    merged to main

    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

    merged to main

    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
Show 2 more
  • #66728 fix(state): set _lineage_root_id on uncompressed sessions for desktop dedup (#66664) · other → #66664
  • #67879 fix(desktop): preserve full transcripts for running sessions · merged via another change → #86595
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.00 means 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

At a glance

Subsystem
agent
Merged PRs
#43487, #86595, #134061
Closed issues
4
Attempts
7 PRs closed without merging
Fix link checked
6 Oct 2026

For your agent

The same facts, typed.