# NousResearch/hermes-agent · Session compression lineage tracking and deduplication fixes

## Open now

- 4 issues, 0 PRs open on GitHub.
- #10374: Feature: lineage-aware /resume core for CLI and gateway
- #44913: Desktop hides compressed session lineage and can spin forever while response exists in gateway dashboard
- #48524: fix(sessions): delete entire compression lineages, not just the visible tip
- #50192: Desktop should treat compressed session lineages as one logical chat

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.

## TL;DR

- 18 member(s): 4 open issue(s), 4 closed issue(s), 0 open PR(s), 3 merged PR(s), 7 PR(s) closed without merging.
- A fix was merged to main in PR #43487. Verify it addresses your case.
- **Recommended:** read PR #43487 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `partially_resolved` · deciding PR(s): `pr-43487,pr-86595,pr-134061`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_PARTIAL`): 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 member 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.

## Open related issues

- #10374: Feature: lineage-aware /resume core for CLI and gateway
- #44913: Desktop hides compressed session lineage and can spin forever while response exists in gateway dashboard
- #48524: fix(sessions): delete entire compression lineages, not just the visible tip
- #50192: Desktop should treat compressed session lineages as one logical chat

## Closed / duplicate issues

- #43483: Desktop sidebar can show duplicate rows for the same auto-compressed session lineage
- #66663: \[gateway\] session.list RPC drops _lineage_root_id, breaking desktop session dedup
- #66664: \[gateway\] _lineage_root_id not set on uncompressed tips, breaking desktop lineage dedup
- #67871: \[Bug\]: Running compressed Desktop sessions replace the complete transcript

## Prior & competing PRs

- **#43487: fix(desktop): deduplicate sidebar rows by compression lineage**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - files: `apps/desktop/src/store/session.test.ts`, `apps/desktop/src/store/session.ts`
  - fix hunks:
  - `apps/desktop/src/store/session.test.ts`

    ```diff
    @@ -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`

    ```diff
    @@ -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)))
       )
     
    ```
- **#66728: fix(state): set _lineage_root_id on uncompressed sessions for desktop dedup (#66664)**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`; refs `#66664`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#86595: fix(desktop): salvage compaction transcript-visibility cluster — surface archived turns + preserve running-session transcripts (#67871)**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - 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 · fix tier `verified`
  - maintainer direction: category `merged`; implication `superseded`
  - 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`
- **#44069: fix(desktop): dedupe compressed session tips in sidebar merge**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#43487`, `#43487`, `#43483`
- **#45084: fix: Desktop hides compressed session lineage and can spin forever while response exists in gateway dashboard (#44913)**. closed without merge
  - maintainer direction: category `silent_close`; implication `active`
- **#48525: fix(sessions): delete entire compression lineages, not just the visible tip**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`; refs `7b26530dfe`, `aaf7f199d5`, `4f64356f5e`, `ecac27c0af`
- **#48634: fix(state): delete entire compression lineages (#48524)**. closed without merge
  - maintainer direction: category `duplicate_of`; implication `duplicate`; refs `#48525`, `#48524`, `#48525`, `#48525`
- **#66675: fix(gateway): preserve session lineage in list RPC**. closed without merge
  - maintainer direction: category `superseded_by_pr`; implication `superseded`
- **#67879: fix(desktop): preserve full transcripts for running sessions**. closed without merge
  - maintainer direction: category `merged_via`; implication `superseded`; refs `#86595`

## Fix status

- Merged to main in PR #43487 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-10-06`.
- Subsystem: `agent`
