Conversation
Contributor
fix(gateway): stop Desktop edit-resend from truncating the wrong turn The fail-closed posture (unreadable durable state → refuse ordinal-only truncation) is correct, and the new
|
teknium1
added a commit
that referenced
this pull request
Aug 15, 2026
…w ids by content Client half of #87059. The gateway now fails ordinal-only truncation closed for durable sessions (#87150), which turned the mis-aimed cut into a visible edit-resend error for any bubble without a bound rowId (edit after an interrupted turn, unstamped resume). Make the Desktop always produce a durable address or degrade safely: - runRewindSubmit: when a truncation request lacks a durable address, resolve the target's row id by exact content against session.history (which ships row_id per persisted row). Resolution is exact-or-nothing: a unique text match wins; ambiguity is accepted only when the target is provably the newest persisted turn (the edit-after-interrupt shape). Anything else degrades to a PLAIN resubmit — never a guessed cut. The client ordinal is dropped either way (its space can diverge from the gateway's — the #87059 root). - planReload/planRestore: degrade failed turns to a plain resubmit (extends the #86623 pattern to regenerate/restore) and carry the turn's persisted sourceText as the content key. - rebindSurvivorRowIds: iterate the same failed-turn-aware ordinal space as the truncate math. - session-tile-actions: reload goes through the shared runRewindSubmit primitive instead of a raw prompt.submit, so the tile surface gets the same discipline.
This was referenced Aug 15, 2026
This was referenced Aug 17, 2026
bobaba76
pushed a commit
to bobaba76/hermes-agent
that referenced
this pull request
Aug 27, 2026
…w ids by content Client half of NousResearch#87059. The gateway now fails ordinal-only truncation closed for durable sessions (NousResearch#87150), which turned the mis-aimed cut into a visible edit-resend error for any bubble without a bound rowId (edit after an interrupted turn, unstamped resume). Make the Desktop always produce a durable address or degrade safely: - runRewindSubmit: when a truncation request lacks a durable address, resolve the target's row id by exact content against session.history (which ships row_id per persisted row). Resolution is exact-or-nothing: a unique text match wins; ambiguity is accepted only when the target is provably the newest persisted turn (the edit-after-interrupt shape). Anything else degrades to a PLAIN resubmit — never a guessed cut. The client ordinal is dropped either way (its space can diverge from the gateway's — the NousResearch#87059 root). - planReload/planRestore: degrade failed turns to a plain resubmit (extends the NousResearch#86623 pattern to regenerate/restore) and carry the turn's persisted sourceText as the content key. - rebindSurvivorRowIds: iterate the same failed-turn-aware ordinal space as the truncate math. - session-tile-actions: reload goes through the shared runRewindSubmit primitive instead of a raw prompt.submit, so the tile surface gets the same discipline.
melon-xf
added a commit
to melon-xf/hermes-agent
that referenced
this pull request
Sep 3, 2026
…w ids by content Client half of NousResearch#87059. The gateway now fails ordinal-only truncation closed for durable sessions (NousResearch#87150), which turned the mis-aimed cut into a visible edit-resend error for any bubble without a bound rowId (edit after an interrupted turn, unstamped resume). Make the Desktop always produce a durable address or degrade safely: - runRewindSubmit: when a truncation request lacks a durable address, resolve the target's row id by exact content against session.history (which ships row_id per persisted row). Resolution is exact-or-nothing: a unique text match wins; ambiguity is accepted only when the target is provably the newest persisted turn (the edit-after-interrupt shape). Anything else degrades to a PLAIN resubmit — never a guessed cut. The client ordinal is dropped either way (its space can diverge from the gateway's — the NousResearch#87059 root). - planReload/planRestore: degrade failed turns to a plain resubmit (extends the NousResearch#86623 pattern to regenerate/restore) and carry the turn's persisted sourceText as the content key. - rebindSurvivorRowIds: iterate the same failed-turn-aware ordinal space as the truncate math. - session-tile-actions: reload goes through the shared runRewindSubmit primitive instead of a raw prompt.submit, so the tile surface gets the same discipline.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Desktop edit-resend can target the wrong durable user turn after a backend restart and silently remove a large part of the model context. This change makes ordinal-only rewinds fail closed whenever durable history exists or cannot be read, and preserves durable row IDs across every reload path involved in the incident.
Symptom
After Desktop reconnects with
session.resumeandomit_messages: true, editing and resending an interrupted turn can truncate at a stale server-side ordinal instead of the edited durable row. In the reported incident, the gateway cut 78 messages rather than 1 and the next request's prompt cache fell to 1%.Impact
Users can lose hours of active model context from the live conversation even though the durable rows remain recoverable in SQLite. Subsequent answers then build on a silently shortened transcript.
Bug Cause
Trigger:
tui_gateway/methods_prompt.py:504in the ordinal-onlyprompt.submittruncation branch.Causal chain:
omit_messages: true, then sends an edit-resend request that has a user ordinal but no durable row ID._row_idstamps, so the guard treated a durable transcript as ephemeral.Why it is wrong: Durability is a property of the persisted transcript, not of optional annotations on the current in-memory copy. Missing row stamps therefore cannot prove that ordinal-only destructive addressing is safe.
Working sibling / contrast: Row-ID-addressed rewinds already resolve the stable durable target and cross-check it against live history before truncating. The fixed ordinal-only path now consults the same profile-aware durable state and refuses unsafe downgrade addressing.
Ruled out: The failure was not caused by missing persistence. REAL_ENV verification resumed persisted rows from a fresh process and showed that they existed with stable row IDs; the bypass came from the unstamped live reload.
Fix
_row_idmetadata when restoring history through omit-messages resume,/undo, and the contended turn-lease reload.Related Issue
Closes #87059
Type of Change
Changes Made
tui_gateway/methods_prompt.py- derive truncation safety from durable state and reject unsafe ordinal-only rewinds.tui_gateway/methods_session.py- retain row IDs during deferred and eager omit-messages resume.tui_gateway/methods_tools.py- retain row IDs after/undoreloads.run_agent.py- retain row IDs when a contended turn lease reloads the latest transcript.tests/test_tui_gateway_server.py- cover durable fail-closed behavior, ephemeral compatibility, and resume metadata.tests/tui_gateway/test_undo_command.pyandtests/run_agent/test_cross_process_turn_lease.py- cover sibling reload boundaries.How to Test
omit_messages: true.truncate_before_user_ordinal; verify error 4004, zero persistence writes, and unchanged live and durable history.truncate_before_row_id; verify that exactly the selected durable turn is targeted.cd apps/desktop npx vitest run src/app/session/hooks/use-prompt-actions/rewind.test.ts src/app/session/hooks/use-prompt-actions/index.test.tsxChecklist
Code
fix(scope):,feat(scope):, etc.)Documentation & Housekeeping
cli-config.yaml.exampleupdate is N/A because no configuration keys changedCONTRIBUTING.mdandAGENTS.mdupdates are N/A because no architecture or workflow changedScreenshots / Logs
REAL_ENV verification on Windows 11 restarted the backend, resumed 7 persisted rows with row IDs, and confirmed that ordinal-only edit-resend returned 4004 with zero memory or database mutation. A row-ID-addressed edit cut exactly at the selected durable target.