Skip to content

fix(desktop): preserve full transcripts for running sessions - #67879

Closed
Kinkoolino-Hermes wants to merge 4 commits into
NousResearch:mainfrom
Kinkoolino-Hermes:fix/desktop-running-transcript-reconcile
Closed

fix(desktop): preserve full transcripts for running sessions#67879
Kinkoolino-Hermes wants to merge 4 commits into
NousResearch:mainfrom
Kinkoolino-Hermes:fix/desktop-running-transcript-reconcile

Conversation

@Kinkoolino-Hermes

@Kinkoolino-Hermes Kinkoolino-Hermes commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Keeps the complete persisted Desktop transcript visible when a warm session is reopened during a running, context-compressed turn.

session.activate.messages is the agent's runtime context projection, so it may intentionally omit older durable conversation rows. The warm-cache path previously applied the fuller REST transcript only while idle. During running: true, reopening the session therefore replaced the visible conversation with the compressed runtime projection.

This change always treats a matching REST transcript as the display authority, then projects the live inflight/queued tail onto it. A three-way, id-based overlay also preserves stream deltas and local submissions that arrive while activation is waiting for REST.

The dedupe is deliberately boundary- and structure-aware rather than global text matching: REST suppresses inflight.user only when the matching user row is the open persisted tail, while local rows are considered activation-owned only when they form the expected synthetic user/assistant-stream pair. Ambiguous rows are retained so an accepted prompt is never dropped. Intentionally repeated prompts remain distinct.

Related Issue

Fixes #67871

Related, but not duplicate:

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • apps/desktop/src/app/session/hooks/use-session-actions/index.ts
    • apply the matching persisted transcript while a warm session is running, not only while idle;
    • keep the matching REST transcript as the display authority on cold session.resume with a compressed live projection;
    • reconcile the live activation projection back onto that complete transcript;
    • overlay cache rows changed during the REST wait by stable local message id;
    • overlay runtime-cache rows changed before a cold resume becomes the active view;
    • avoid converting the full persisted transcript more than once in this path.
  • apps/desktop/src/app/session/hooks/use-session-actions/utils.ts
    • add conservative, boundary-aware inflight.user deduplication;
    • preserve the live prompt when histories have no safe common anchor;
    • remove only structurally matched synthetic activation rows;
    • overlay only cache rows that changed relative to the pre-activation baseline.
    • replace the activation assistant placeholder with the real time-based stream id instead of rendering two pending assistant bubbles.
  • Regression tests cover:
    • compressed runtime history versus a fuller persisted transcript while running: true;
    • partial assistant output remains visible;
    • an already-persisted in-flight user row appearing exactly once;
    • intentionally repeated prompts remaining separate;
    • a compressed cache's represented optimistic row not being replayed;
    • unmatched and text-identical local race prompts remaining intact;
    • assistant deltas and local submits arriving during a delayed REST request;
    • an answered historical repeat not suppressing a new identical in-flight prompt;
    • unchanged cache rows never overriding a newer authoritative REST row.
    • a full renderer restart retaining older persisted rows alongside inflight and queued.
    • a real stream id differing from the activation projection still producing exactly one pending assistant bubble.
    • a cold-resume runtime delta arriving while REST is pending remaining in the final stream bubble.

How to Test

  1. Run the focused regression suite:
    cd apps/desktop
    npx vitest run --project ui \
      src/app/session/hooks/use-session-actions.test.tsx \
      src/app/session/hooks/use-session-actions/utils.test.ts
  2. Run the full Desktop gate:
    npm run check
  3. Manual behavior:
    • open a long session whose runtime history has been compacted;
    • start a new turn, switch away, then switch back while it is running;
    • verify older persisted messages remain visible, partial assistant output remains live, and the current user row appears once.

Verified locally

  • Focused UI tests: 66 passed
  • Desktop TypeScript typecheck
  • Scoped ESLint and Prettier checks
  • Complete final-head Desktop gate (npm run check): exit 0, including the full Vitest matrix, Desktop platform/package matrix, and production build

Safety / Regression Notes

  • No session data, persistence schema, gateway payload, configuration, or backend behavior changes.
  • The warm cache is still painted before network reconciliation, so this does not delay the first visible session switch.
  • REST failure still falls back to the existing runtime/cache projection.
  • Persisted data is accepted only when its session id matches the activated stored session, preserving the existing cross-wiring guard.
  • The same persisted-authority rule applies to warm activation and cold resume; watch-window resumes without REST prefetch keep their existing path.
  • Concurrent cache updates are reconciled by local message id against the captured baseline; unchanged stale cache rows cannot replace newer REST rows.
  • A changed real assistant-stream-* row replaces the one baseline-new activation placeholder; it is not appended as a second assistant bubble.
  • Cold resume captures a per-runtime cache baseline when the RPC resolves and overlays later cache changes before committing the view.
  • The reconciliation is linear over the converted transcript and uses no global content dedupe.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass — N/A: Desktop-only TypeScript change; the complete Desktop gate is listed above
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Linux/jsdom renderer tests, Electron platform tests, and production build

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — N/A: no user-facing API or configuration change
  • I've updated cli-config.yaml.example if I added/changed config keys — N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — renderer-only behavior with Electron platform suite coverage
  • I've updated tool descriptions/schemas if I changed tool behavior — N/A

Screenshots / Logs

No screenshot is needed: the bug and fix are covered by a deterministic warm-session activation test using divergent runtime and persisted histories. The original live transcript contains private conversation data and is not included.

@Kinkoolino-Hermes
Kinkoolino-Hermes marked this pull request as ready for review July 20, 2026 05:24
@Kinkoolino-Hermes
Kinkoolino-Hermes marked this pull request as draft July 20, 2026 05:26
@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 20, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related to #67871, #63298, and #50159: this Desktop display-layer repair restores persisted history during a running compressed session and deduplicates the live tail; the linked work addresses the report, queued-prompt identity, and stale completed-turn mechanisms respectively.

@Kinkoolino-Hermes
Kinkoolino-Hermes marked this pull request as ready for review July 20, 2026 06:22
@Kinkoolino-Hermes
Kinkoolino-Hermes force-pushed the fix/desktop-running-transcript-reconcile branch from 7a43e6d to 1577328 Compare July 25, 2026 16:01
@Kinkoolino-Hermes

Copy link
Copy Markdown
Contributor Author

Refreshed this branch onto current main (07e97d2f5) and resolved the Desktop activation overlap deliberately rather than taking either side wholesale:

  • preserved upstream's deferred REST-prefetch paint and crash-survivable in-flight journal recovery;
  • preserved this PR's full persisted-transcript authority plus live inflight/queued projection reconciliation;
  • preserved runtime-cache changes that arrive while cold resume is waiting;
  • retained the pre-recovery transcript fail-latch.

Verification on the refreshed head:

  • focused session-action suites: 87 passed;
  • full Desktop UI gate: 2,210 passed, 1 skipped;
  • Electron/platform gate: 725 passed, 2 skipped;
  • full Desktop build and unpacked packaging: exit 0.

New head: 1577328409205f8256c00ae719a52d31d4d4c625.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for covering both warm activation and cold resume; the current-main premise is real: apps/desktop/src/app/session/hooks/use-session-actions/index.ts:742-747 only applies the persisted transcript while idle, and :921-934 bypasses it when a live projection exists.

Problems

  • apps/desktop/src/app/session/hooks/use-session-actions/utils.ts:479-484 clears inflight.user after finding the persisted row. That also removes the signal appendLiveSessionProjection uses at current main utils.ts:485-494 to insert an assistant boundary before queued.user. With a persisted in-flight user, queued prompt, empty assistant text, and streaming: false, this produces adjacent user rows rather than preserving the two-turn boundary. The new tests cover the queued case only with an assistant payload and streaming: true.

Suggested changes

  • Keep the source in-flight-user signal while suppressing only its duplicate bubble, and add the no-delta queued-turn regression.
  • Integrate manually over current main's post-base reconciliation work, notably 2109a1875's redirect-correction support.

Automated hermes-sweeper review.

...projection,
inflight: {
...projection.inflight,
user: undefined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearing inflight.user also removes the (inflightUser && queuedUser) signal that appendLiveSessionProjection uses to retain an empty assistant boundary before a queued prompt. Keep dedupe state separate from the source user, and cover the persisted-user + queued-user + no-assistant-delta case.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/sessions Session lifecycle, resume, persistence, history labels Jul 30, 2026
@Kinkoolino-Hermes
Kinkoolino-Hermes force-pushed the fix/desktop-running-transcript-reconcile branch from 1577328 to d1cd0c1 Compare July 30, 2026 17:26
@Kinkoolino-Hermes

Copy link
Copy Markdown
Contributor Author

Addressed the queued no-delta turn-boundary finding and refreshed the branch onto current main (8defb9fd6).

  • Kept the source inflight.user signal after persisted-tail validation while suppressing only its duplicate rendered bubble.
  • This preserves the empty assistant boundary before queued.user when the assistant payload is empty and streaming: false.
  • Added the missing regression through the real dedupe → projection path.
  • Retained current-main redirect-correction reconciliation from 2109a1875.

Local verification on head d1cd0c1fc9fcb08cf4f00d88d4b92105e35b768b:

  • regression test: RED before the fix, GREEN after it
  • focused session-action utilities: 52 passed
  • Desktop UI: 3,139 passed
  • Electron: 867 passed, 2 skipped
  • Desktop typecheck, focused ESLint, Prettier, and git diff --check: passed
  • full Desktop build/package gate (test:desktop:all): passed

@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Four PRs are listed in this complex. #67879 directly changes the REST-versus-compressed-runtime reconciliation behind #67871, while #68176, #70720, and #71121 address the adjacent image-attachment persistence and restoration failures in #68092/#70772.

Related pull requests

Duplicates

#68176 and #70720 substantially overlap on attachment preservation and were both superseded by #71121; none is a duplicate of #67879’s compressed-running-transcript fix.

Suggested consolidation

Keep #67879 open with a salvage path: preserve its full-transcript authority, live/concurrent overlay, boundary-aware dedupe, and queued no-delta regression, then obtain contributor re-review of the change that addresses teknium1’s keep_open finding. Keep #68176 and #70720 closed as superseded by #71121; #71121 is already integrated for the separate attachment issues and should not be treated as resolving #67871.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I67871(["issue #67871 (open)"])
    P67879["PR #67879 (open)"]
    P67879 -->|best fix| I67871
    class I67871 open
    class P67879 open
    class P67879 best
    class P67879 target
    click I67871 "https://github.com/NousResearch/hermes-agent/issues/67871"
    click P67879 "https://github.com/NousResearch/hermes-agent/pull/67879"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 4 pull requests and 3 issues in this complex. Each diff was read against this issue; Assessment working set: 146 kB of PR diffs, 32 kB of issue/PR text, 12 kB of discussion (16 comments), 11 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

@Kinkoolino-Hermes

Kinkoolino-Hermes commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@teknium1 The queued no-delta turn-boundary finding from your review has been addressed on the current head by retaining the source inflight.user signal while suppressing only its duplicate rendered bubble. The requested persisted-user → empty-assistant → queued-user regression now passes through the real dedupe and projection path, and the current CI is green. Could you take another look when convenient?

— 🤖 Bot comment · LLM: gpt-5.6-sol · not written by a human

@alt-glitch alt-glitch added P2 Medium — degraded but workaround exists area/compression Context compression and continuation sessions and removed P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Aug 4, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thank you for this fix! It was salvaged into #86595 (cherry-picked onto current main with your authorship preserved in the commit history) and is now merged. Closing this PR since the work has landed.

@teknium1 teknium1 closed this Aug 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/compression Context compression and continuation sessions area/sessions Session lifecycle, resume, persistence, history comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Running compressed Desktop sessions replace the complete transcript

4 participants