Skip to content

fix(desktop): preserve profile when restoring sessions - #63590

Closed
oliviaaaa7788 wants to merge 1 commit into
NousResearch:mainfrom
oliviaaaa7788:fix/desktop-profile-session-routing-pr
Closed

fix(desktop): preserve profile when restoring sessions#63590
oliviaaaa7788 wants to merge 1 commit into
NousResearch:mainfrom
oliviaaaa7788:fix/desktop-profile-session-routing-pr

Conversation

@oliviaaaa7788

@oliviaaaa7788 oliviaaaa7788 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • remember Desktop's last-open session per gateway profile instead of using one global localStorage key
  • create branch sessions in the parent stored session's profile instead of the currently active profile
  • pass the stored session's owning profile when recovering/resuming after stale runtime IDs, session-not-found, or timeout paths
  • preserve upstream Desktop source: 'desktop' resume/create tagging while adding profile routing

Why

Desktop could display or restore a stored session from one profile while routing follow-up actions through another profile. In a multi-profile setup this made sessions appear to jump profiles after profile switches or app restarts.

Test Plan

  • npm run test:ui --workspace apps/desktop -- src/store/session.test.ts src/app/session/hooks/use-session-actions.test.tsx src/app/session/hooks/use-prompt-actions/index.test.tsx — 84 tests passed
  • npm run typecheck --workspace apps/desktop
  • git diff HEAD^ --check
  • static added-line scan for hardcoded credentials / dangerous shell-eval patterns produced no matching lines

Scope / Safety

  • Desktop-only session/profile routing fix
  • No external product repository changes
  • No gateway config changes
  • No provider calls or production writes

@oliviaaaa7788
oliviaaaa7788 force-pushed the fix/desktop-profile-session-routing-pr branch from 1e917dd to 2d139b0 Compare July 13, 2026 05:18
@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) area/config Config system, migrations, profiles P3 Low — cosmetic, nice to have labels Jul 13, 2026
@oliviaaaa7788
oliviaaaa7788 force-pushed the fix/desktop-profile-session-routing-pr branch from 2d139b0 to 391c123 Compare July 13, 2026 05:49
Remember Desktop's last open session per gateway profile, create branches in the parent session's profile, and pass the stored session profile when recovering after a session-not-found response.

Tests: npm run test:ui --workspace apps/desktop -- src/store/session.test.ts src/app/session/hooks/use-session-actions.test.tsx src/app/session/hooks/use-prompt-actions/index.test.tsx; npm run typecheck --workspace apps/desktop
@oliviaaaa7788
oliviaaaa7788 force-pushed the fix/desktop-profile-session-routing-pr branch from 391c123 to f519cc9 Compare July 13, 2026 05:53

@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 tracing the profile-routing paths. The global remembered-session key remains a real defect on current main: apps/desktop/src/store/session.ts:26-29 and apps/desktop/src/app/desktop-controller.tsx:273-301 still share it across profiles.

Problems

  • The recovery helpers added in apps/desktop/src/app/session/hooks/use-prompt-actions/submit.ts:53-62 and index.ts:62-71 only inspect $sessions. That cache is paginated (use-session-list-actions.ts:161-183), so a selected cross-profile session outside the loaded window still resumes without profile. The backend then uses the launch-profile DB when profile is absent (tui_gateway/server.py:5596-5613).
  • The added tests seed $sessions, so they cover only this cache-hit case.

Suggested changes

  • Reuse or extract the profile-aware resolver in apps/desktop/src/app/session/hooks/use-session-actions/utils.ts:209-251, which probes uncached session IDs across profiles, and carry its resolved profile into each recovery resume.
  • Add cache-miss recovery coverage for the three updated resume paths.

This is an automated hermes-sweeper review.

return null
}

const stored = $sessions.get().find(session => session.id === storedSessionId || session._lineage_root_id === storedSessionId)

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.

This only searches the paginated $sessions cache. A selected cross-profile session can be absent from that cache, which leaves profile omitted and makes session.resume query the launch-profile DB. Please resolve uncached stored IDs through the existing cross-profile resolver (or a shared equivalent) before retrying.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/sessions Session lifecycle, resume, persistence, history area/profiles Multi-profile isolation, HERMES_HOME scoping labels Jul 16, 2026
@OutThisLife

Copy link
Copy Markdown
Collaborator

Superseded by #69861, which carries this profile-routing fix (credited to @oliviaaaa7788 via Co-authored-by).

Two things changed the shape: (1) the main resume path on current main already resolves the owning profile through resolveStoredSession (cache → active → cross-profile), so #69861 reuses that resolver instead of a separate cache-only lookup — which also closes the paginated-cache-miss gap @teknium1 flagged here; and (2) it applies the profile to the recovery session.resume calls (the paths that were actually forking a session into the launch-profile DB) with cache-hit and cross-profile cache-miss coverage. Thanks for tracing the routing paths.

OutThisLife added a commit that referenced this pull request Jul 23, 2026
…bleed, and [System:] bubble (#69861)

* fix(desktop): stop model-switch dup + route recovery resumes to the owning profile

Fixes two Desktop session-reconciliation symptoms from #67603.

Symptom 1 — duplicated user bubble after a model switch. The gateway
persists model-switch / personality notices as role=user `[System: …]`
rows (tui_gateway/server.py) so strict OpenAI-compatible providers don't
reject a non-leading system message (#48338). `preserveLocalPendingTurnMessages`
paired local optimistic rows with the stored transcript by user-role
ordinal, so a marker between two real user turns shifted every later
ordinal and the optimistic row was re-appended at the bottom. The single
trailing-marker case is already covered by the compression-era
`latestAuthoritativeUser` guard, but two switches around one turn (marker
before AND after the committed prompt) still duplicated it. Exclude
`[System:` bookkeeping markers from ordinal pairing on both sides.

Symptom 2 — a session appearing under two profiles. The main resume path
already resolves a session's owning profile via `resolveStoredSession`
(cache → active backend → cross-profile probe), but the recovery
`session.resume` calls (stale runtime id, session-not-found, wedged loop,
redirect) omitted `profile`, so the gateway fell back to the launch-profile
DB and forked the conversation into the wrong profile. Route every recovery
resume — and an uncached right-click branch — through the same resolver so
the profile is carried even for sessions outside the paginated sidebar
window (the cache-miss gap).

Tests: discriminating two-switch marker test (fails before, passes after);
cache-hit + cross-profile cache-miss coverage for the recovery resume and
for branching an uncached session.

Supersedes #68665 and #63590.
Closes #67603.

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>

* fix(desktop): scope the remembered session id per profile

A single global `hermes.desktop.lastSessionId` key remembered ONE session
across every profile, so a relaunch or cold start under profile B would try
to restore a session owned by profile A — reinforcing the impression that a
conversation had bled between profiles (#67603, second symptom).

Key the remembered id by the session's owning profile (resolved from the
session row's `profile`, falling back to the active gateway profile), read it
back for the active profile on restore, and clear an exhausted session under
its owner. The default profile keeps the original unsuffixed key so existing
installs' remembered session survives the upgrade.

Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>

* fix(gateway): hide [System:] bookkeeping markers from every transcript

Model-switch and personality notices are persisted as role=user `[System: …]`
rows so strict providers accept them mid-history, but they are model-facing
runtime metadata, not user turns. `_history_to_messages` — the single display
projection every client reads — passed them straight through, so on resume or
reload they rendered as a fake user bubble in the desktop, TUI, CLI, and web
transcripts.

Drop them in that projection. The raw marker stays in `session["history"]`
for the model, so nothing changes for inference; only the display loses a row
that never belonged to the user. This also removes the stored marker from the
payload the desktop reconciles against, killing the ordinal shift that
duplicated the optimistic prompt (#67603) at its source — the desktop-side
marker exclusion remains as a fallback for older backends.

Co-authored-by: Dolverin <Dolverin@users.noreply.github.com>

---------

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>
Co-authored-by: Dolverin <Dolverin@users.noreply.github.com>
pull Bot pushed a commit to licoba/hermes-agent that referenced this pull request Jul 23, 2026
…wning profile

Fixes two Desktop session-reconciliation symptoms from NousResearch#67603.

Symptom 1 — duplicated user bubble after a model switch. The gateway
persists model-switch / personality notices as role=user `[System: …]`
rows (tui_gateway/server.py) so strict OpenAI-compatible providers don't
reject a non-leading system message (NousResearch#48338). `preserveLocalPendingTurnMessages`
paired local optimistic rows with the stored transcript by user-role
ordinal, so a marker between two real user turns shifted every later
ordinal and the optimistic row was re-appended at the bottom. The single
trailing-marker case is already covered by the compression-era
`latestAuthoritativeUser` guard, but two switches around one turn (marker
before AND after the committed prompt) still duplicated it. Exclude
`[System:` bookkeeping markers from ordinal pairing on both sides.

Symptom 2 — a session appearing under two profiles. The main resume path
already resolves a session's owning profile via `resolveStoredSession`
(cache → active backend → cross-profile probe), but the recovery
`session.resume` calls (stale runtime id, session-not-found, wedged loop,
redirect) omitted `profile`, so the gateway fell back to the launch-profile
DB and forked the conversation into the wrong profile. Route every recovery
resume — and an uncached right-click branch — through the same resolver so
the profile is carried even for sessions outside the paginated sidebar
window (the cache-miss gap).

Tests: discriminating two-switch marker test (fails before, passes after);
cache-hit + cross-profile cache-miss coverage for the recovery resume and
for branching an uncached session.

Supersedes NousResearch#68665 and NousResearch#63590.
Closes NousResearch#67603.

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…wning profile

Fixes two Desktop session-reconciliation symptoms from NousResearch#67603.

Symptom 1 — duplicated user bubble after a model switch. The gateway
persists model-switch / personality notices as role=user `[System: …]`
rows (tui_gateway/server.py) so strict OpenAI-compatible providers don't
reject a non-leading system message (NousResearch#48338). `preserveLocalPendingTurnMessages`
paired local optimistic rows with the stored transcript by user-role
ordinal, so a marker between two real user turns shifted every later
ordinal and the optimistic row was re-appended at the bottom. The single
trailing-marker case is already covered by the compression-era
`latestAuthoritativeUser` guard, but two switches around one turn (marker
before AND after the committed prompt) still duplicated it. Exclude
`[System:` bookkeeping markers from ordinal pairing on both sides.

Symptom 2 — a session appearing under two profiles. The main resume path
already resolves a session's owning profile via `resolveStoredSession`
(cache → active backend → cross-profile probe), but the recovery
`session.resume` calls (stale runtime id, session-not-found, wedged loop,
redirect) omitted `profile`, so the gateway fell back to the launch-profile
DB and forked the conversation into the wrong profile. Route every recovery
resume — and an uncached right-click branch — through the same resolver so
the profile is carried even for sessions outside the paginated sidebar
window (the cache-miss gap).

Tests: discriminating two-switch marker test (fails before, passes after);
cache-hit + cross-profile cache-miss coverage for the recovery resume and
for branching an uncached session.

Supersedes NousResearch#68665 and NousResearch#63590.
Closes NousResearch#67603.

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…ile session bleed, and [System:] bubble (NousResearch#69861)

* fix(desktop): stop model-switch dup + route recovery resumes to the owning profile

Fixes two Desktop session-reconciliation symptoms from NousResearch#67603.

Symptom 1 — duplicated user bubble after a model switch. The gateway
persists model-switch / personality notices as role=user `[System: …]`
rows (tui_gateway/server.py) so strict OpenAI-compatible providers don't
reject a non-leading system message (NousResearch#48338). `preserveLocalPendingTurnMessages`
paired local optimistic rows with the stored transcript by user-role
ordinal, so a marker between two real user turns shifted every later
ordinal and the optimistic row was re-appended at the bottom. The single
trailing-marker case is already covered by the compression-era
`latestAuthoritativeUser` guard, but two switches around one turn (marker
before AND after the committed prompt) still duplicated it. Exclude
`[System:` bookkeeping markers from ordinal pairing on both sides.

Symptom 2 — a session appearing under two profiles. The main resume path
already resolves a session's owning profile via `resolveStoredSession`
(cache → active backend → cross-profile probe), but the recovery
`session.resume` calls (stale runtime id, session-not-found, wedged loop,
redirect) omitted `profile`, so the gateway fell back to the launch-profile
DB and forked the conversation into the wrong profile. Route every recovery
resume — and an uncached right-click branch — through the same resolver so
the profile is carried even for sessions outside the paginated sidebar
window (the cache-miss gap).

Tests: discriminating two-switch marker test (fails before, passes after);
cache-hit + cross-profile cache-miss coverage for the recovery resume and
for branching an uncached session.

Supersedes NousResearch#68665 and NousResearch#63590.
Closes NousResearch#67603.

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>

* fix(desktop): scope the remembered session id per profile

A single global `hermes.desktop.lastSessionId` key remembered ONE session
across every profile, so a relaunch or cold start under profile B would try
to restore a session owned by profile A — reinforcing the impression that a
conversation had bled between profiles (NousResearch#67603, second symptom).

Key the remembered id by the session's owning profile (resolved from the
session row's `profile`, falling back to the active gateway profile), read it
back for the active profile on restore, and clear an exhausted session under
its owner. The default profile keeps the original unsuffixed key so existing
installs' remembered session survives the upgrade.

Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>

* fix(gateway): hide [System:] bookkeeping markers from every transcript

Model-switch and personality notices are persisted as role=user `[System: …]`
rows so strict providers accept them mid-history, but they are model-facing
runtime metadata, not user turns. `_history_to_messages` — the single display
projection every client reads — passed them straight through, so on resume or
reload they rendered as a fake user bubble in the desktop, TUI, CLI, and web
transcripts.

Drop them in that projection. The raw marker stays in `session["history"]`
for the model, so nothing changes for inference; only the display loses a row
that never belonged to the user. This also removes the stored marker from the
payload the desktop reconciles against, killing the ordinal shift that
duplicated the optimistic prompt (NousResearch#67603) at its source — the desktop-side
marker exclusion remains as a fallback for older backends.

Co-authored-by: Dolverin <Dolverin@users.noreply.github.com>

---------

Co-authored-by: Dolverin <5910064+Dolverin@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <274182427+oliviaaaa7788@users.noreply.github.com>
Co-authored-by: oliviaaaa7788 <oliviaaaa7788@users.noreply.github.com>
Co-authored-by: Dolverin <Dolverin@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history comp/desktop Electron desktop app (apps/desktop/*) P3 Low — cosmetic, nice to have sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages 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.

4 participants