Skip to content

fix(agent): preserve chat context across a Quick/Reasoning switch - #5401

Merged
M3gA-Mind merged 2 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/5351-session-context-model-switch
Aug 5, 2026
Merged

M3gA-Mind merged 2 commits into
tinyhumansai:mainfrom
YellowSnnowmann:fix/5351-session-context-model-switch

Conversation

@YellowSnnowmann

@YellowSnnowmann YellowSnnowmann commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • The Quick/Reasoning composer toggle switches the active agent profile (default/reasoning), not a model dropdown. Switching it mid-thread no longer drops the in-progress conversation and plan.
  • normalise_state now pins built-in non-default, non-dedicated_memory profiles (reasoning/research/planner/review) to the shared memory + session_raw subtree — preventing and healing the mis-scoping on every load/save.
  • seed_resume_from_thread_transcript now resolves the thread's transcript across all profile dirs (newest-wins), so a thread's conversation follows the thread across any profile switch, including already-scoped installs.

Problem

Reported in #5351: switching quick↔reasoning mid-session drops the earlier plan and all prior context; the new tier behaves like a fresh session.

Root cause: store.rs::upsert auto-assigns a numeric memory_dir_suffix to any non-default profile with memory_dir_suffix.is_none() — it excludes only default, not the other built-ins. So editing + saving the built-in Reasoning profile once stamps it with "-1", routing it to a scoped session_raw-1/ + memory-1/ subtree. A mid-thread switch is a session-cache miss that rebuilds the agent from that scoped dir, which cannot see the conversation the previous profile wrote to the shared subtree. It is conditional (needs a prior profile edit) and partial (the plain-text prose fallback survived; tool-call/reasoning transcript fidelity and semantic recall did not).

Solution

  • normalise_state pin (store.rs). In the canonical normalization point (runs on every load and save, where default is already pinned), built-in non-default, non-dedicated_memory profiles are forced to memory_dir_suffix = None → shared subtree. This both prevents a fresh mis-assignment and heals an already-persisted stale suffix. Custom profiles and a user-set dedicated_memory are untouched. Non-destructive — no filesystem move/delete; orphaned memory-<n>/ dirs are left in place.
  • Cross-dir transcript resume (runtime.rs). seed_resume_from_thread_transcript now uses the cross-dir, newest-wins find_root_transcript_for_thread (scans the shared session_raw/ plus every session_raw-<id>/) instead of an own-dir-first lookup. A thread's conversation follows the thread across any profile switch and stays aligned with the transcript view + turn mirror, which already use this same resolver. Matching is keyed on thread_id, so it never bleeds an unrelated session across profiles.
  • Design decision. The pin routes the profile to the right dir for new threads; the cross-dir resume additionally rescues transcripts a profile wrote while scoped (pre-heal) and genuine dedicated-memory personalities. Together they restore both context vectors — the raw transcript (first turn after the switch) and shared-memory recall (later turns) — with no migration and no wire/API change.
  • The agent-name warm-resume path (try_load_session_transcript / find_latest_transcript_in_subdir) is deliberately left untouched; it keeps enforcing profile isolation for its (non-thread-keyed) use.

Submission Checklist

  • Tests added or updated (happy path + failure/edge): profile-scoped session resumes the shared-dir transcript; newest-wins across profile dirs (guards the own-dir-shadow regression); normalise_state heals a stale suffix while preserving a custom profile's suffix; upsert of the built-in Reasoning profile stays on the shared subtree.
  • Diff coverage ≥ 80% — not run locally (pnpm test:coverage/cargo-llvm-cov need a full build the local disk could not fit); the 4 tests exercise both changed code paths. Deferred to the CI coverage gate.
  • Coverage matrix updated — N/A: behaviour-only bugfix, no feature row added/removed/renamed.
  • All affected feature IDs listed under ## RelatedN/A: behaviour-only change.
  • No new external network dependencies introduced — none added; changes are pure in-process logic.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: internal profile-normalization + transcript-resume logic, no release-cut surface.
  • Linked issue closed via Closes #NNN in ## Related.

Impact

  • Platform: desktop + managed/cloud. The profile store and web-chat resume are kernel/always-compiled (no feature gate), so the fix ships in every build. No frontend change, no config schema change, no migration file.
  • Behavior change: the built-in reasoning/research/planner/review profiles now share the default memory + session_raw subtree — their shipped intent (dedicated_memory: false). This heals bug-created scoping; any bug-era memory-<n>/ data is orphaned non-destructively (files remain). A dedicated-memory personality active on a thread may now resume that thread's transcript written under another profile (read-only, same conversation; its writes still go to its scoped dir).
  • Known limitation: the heal recovers the conversation transcript but not semantic captures written into a bug-era memory-<n>/ during the scoped window. This does not affect the issue's scenario — a plan built under Quick (default) lives in shared memory and is recalled. A proper fix (embeddings/SQLite merge) is invasive and out of scope.
  • Performance: the cross-dir resolve parses root transcripts across profile dirs, but only on a session-cache miss (cold-boot / profile switch) — the same unit cost the transcript view already pays — and is a no-op superset for the common single-profile workspace.
  • Security: the scan is bounded to the per-user workspace_dir (users/<id>/workspace), so there is no cross-tenant transcript access.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

Commit & Branch

  • Branch: fix/5351-session-context-model-switch
  • Commit SHA: a37feb743

Validation Run

  • pnpm --filter openhuman-app format:checkN/A: no app/src (TypeScript) changes.
  • pnpm typecheckN/A: no TypeScript changes.
  • Focused tests: cargo test --lib — 4 new tests plus 51 in agent::profiles::store + agent::harness::session pass, 0 failed.
  • Rust fmt/check: cargo check --manifest-path Cargo.toml clean; cargo fmt --check clean on all 3 changed files.
  • Tauri fmt/check: N/A: app/src-tauri untouched; change is core-internal, public API unchanged.

Validation Blocked

  • command: pnpm test:coverage / pnpm rust:check (Tauri shell) / full cargo test link
  • error: local APFS container saturation during large Rust builds
  • impact: diff-coverage gate + Tauri shell check deferred to CI; core crate verified via cargo check + targeted tests

Behavior Changes

  • Intended behavior change: a mid-thread profile switch retains conversation context; built-in helper profiles share the default memory/session_raw subtree.
  • User-visible effect: the Quick↔Reasoning toggle no longer resets the conversation/plan.

Parity Contract

  • Legacy behavior preserved: custom-profile numeric-suffix assignment unchanged (memory_dir_suffix_auto_assigned_on_upsert passes); default-profile pinning unchanged; the agent-name warm-resume isolation path is untouched.
  • Guard/fallback/dispatch parity checks: cross-dir resume is keyed on thread_id (no cross-session bleed); dedicated_memory is honored; blank thread_id short-circuits to None.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this PR
  • Resolution (closed/superseded/updated): N/A

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved session resume by finding the newest matching transcript across shared and profile-specific locations.
    • Corrected built-in profile state handling by removing stale memory directory suffixes when dedicated memory is not used.
    • Preserved memory suffixes for profiles that require dedicated memory and for custom profiles.
  • Tests

    • Added coverage for transcript fallback and profile state normalization scenarios.

The Quick/Reasoning composer toggle switches the active agent profile
(default/reasoning), not a model dropdown. Editing + saving the built-in
Reasoning profile once made `store.rs::upsert` wrongly stamp it with a numeric
`memory_dir_suffix`, routing it to a scoped `session_raw-<n>/` + `memory-<n>/`
subtree. A mid-thread switch then rebuilt the agent from the scoped dir, so it
could not see the conversation the previous profile wrote to the shared subtree
— dropping the earlier plan and all prior context.

- `normalise_state` now pins built-in non-default, non-`dedicated_memory`
  profiles (reasoning/research/planner/review) to the shared memory + session_raw
  subtree. It runs on every load and save, so it both prevents a fresh
  mis-assignment and heals an already-persisted stale suffix. Custom profiles and
  a user-set `dedicated_memory` are untouched.
- `seed_resume_from_thread_transcript` now resolves the thread's transcript with
  the cross-dir, newest-wins `find_root_transcript_for_thread` (scans the shared
  `session_raw/` plus every `session_raw-<id>/`), so a thread's conversation
  follows the thread across any profile switch — including already-scoped
  installs — and stays aligned with the transcript view. Keyed on `thread_id`, so
  it never bleeds an unrelated session across profiles.

Known limitation: the heal recovers the conversation transcript but not semantic
captures written to a bug-era `memory-<n>/` during the scoped window. This does
not affect the issue's scenario (a plan built under Quick lives in shared memory
and is recalled); it is non-destructive (files remain).

Tests: profile-scoped resume finds the shared transcript; newest-wins across
profile dirs; normalise heals a stale suffix while preserving custom-profile
suffixes; upsert of the built-in Reasoning profile stays on the shared subtree.

Closes tinyhumansai#5351

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84c0c953-4797-4818-b1e1-2e355892d41c

📥 Commits

Reviewing files that changed from the base of the PR and between a37feb7 and fceedcb.

📒 Files selected for processing (1)
  • src/openhuman/agent/profiles/store.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/openhuman/agent/profiles/store.rs

📝 Walkthrough

Walkthrough

The change expands thread transcript resume across shared and profile-scoped directories. It also clears stale memory directory suffixes from built-in profiles without dedicated memory while preserving custom and dedicated-memory profile behavior.

Changes

Thread transcript resume

Layer / File(s) Summary
Cross-directory transcript resume
src/openhuman/agent/harness/session/runtime.rs, src/openhuman/agent/harness/session/tests.rs
Resume now selects the newest matching transcript across shared and profile-scoped directories. Regression tests cover shared fallback and newer sibling transcripts.

Built-in profile normalization

Layer / File(s) Summary
Built-in memory suffix normalization
src/openhuman/agent/profiles/store.rs
Normalization records built-in profile IDs, clears stale suffixes for built-in profiles without dedicated memory, and keeps custom profile suffixes. Tests cover persisted-state healing and save-reload behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • tinyhumansai/openhuman#5118: This PR extends the same transcript resume path across shared and profile-scoped session_raw directories.

Suggested labels: bug, agent

Poem

A rabbit found old threads to keep,
Through shared paths where context leapt.
The newest note now leads the way,
Old suffix dust is swept away.
Hop, hop, the session holds its thread.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes preserving chat context during Quick and Reasoning profile switches.
Linked Issues check ✅ Passed The changes address issue #5351 by preserving transcripts and shared context across profile switches, with regression tests for recovery and continuity.
Out of Scope Changes check ✅ Passed All code and test changes support profile-switch context preservation, transcript recovery, or stale suffix healing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Comment @coderabbitai help to get the list of available commands.

@YellowSnnowmann
YellowSnnowmann marked this pull request as ready for review August 5, 2026 08:22
@YellowSnnowmann
YellowSnnowmann requested a review from a team August 5, 2026 08:23

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot added agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Aug 5, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/agent/harness/session/runtime.rs`:
- Around line 456-462: Update the debug logging around
find_root_transcript_for_thread in the session resume flow to use the stable
[domain][session_resume] prefix instead of [web-channel]. Add matching prefixed
debug logs for the cross-directory lookup start, successful hit, and fallback
branches, retaining thread={thread_id} as the correlation field.

In `@src/openhuman/agent/profiles/store.rs`:
- Around line 478-493: Add a debug-level [domain] event immediately before the
assignment in the built-in shared-memory normalization branch guarded by
built_in_ids.contains(&profile.id) && !profile.dedicated_memory. Include
profile_id in the event fields, then retain the existing
profile.memory_dir_suffix = None behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 185b85fd-3c89-45eb-b683-fa663971664f

📥 Commits

Reviewing files that changed from the base of the PR and between d75b0a4 and a37feb7.

📒 Files selected for processing (3)
  • src/openhuman/agent/harness/session/runtime.rs
  • src/openhuman/agent/harness/session/tests.rs
  • src/openhuman/agent/profiles/store.rs

Comment thread src/openhuman/agent/harness/session/runtime.rs
Comment thread src/openhuman/agent/profiles/store.rs
Address CodeRabbit review on tinyhumansai#5401: the tinyhumansai#5351 heal branch mutated persisted
state (memory_dir_suffix) without a debug record. Add a [profiles] debug event
with profile_id + had_suffix before the pin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

YellowSnnowmann has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot removed the rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. label Aug 5, 2026

@M3gA-Mind M3gA-Mind left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed against upstream/main: CI green on every lane that applies to the changed areas, no unresolved review threads, and the diff does what the title and body claim. Checked correctness, blast radius on shared seams, that the behaviour change is pinned by a test rather than only asserted, and i18n/em-dash compliance on any new user-facing string. Nothing blocking found.

@M3gA-Mind
M3gA-Mind merged commit 0682302 into tinyhumansai:main Aug 5, 2026
24 of 28 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in Team Openhuman Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent Built-in agents, prompts, orchestration, and agent runtime in src/openhuman/agent/. bug

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

Session context lost when switching between quick and reasoning models mid-session

2 participants