Skip to content

fix(session): skip display_kind timeline rows in undo/retry turn targets - #72820

Closed
necoweb3 wants to merge 1 commit into
NousResearch:mainfrom
necoweb3:fix/undo-retry-skip-display-kind-timeline
Closed

fix(session): skip display_kind timeline rows in undo/retry turn targets#72820
necoweb3 wants to merge 1 commit into
NousResearch:mainfrom
necoweb3:fix/undo-retry-skip-display-kind-timeline

Conversation

@necoweb3

Copy link
Copy Markdown
Contributor

Summary

/undo and /retry resolve their target against every durable role == "user" row. Timeline bookkeeping rows — model_switch, async_delegation_complete, auto_continue, hidden — are stored that way, but no client counts them as user turns (desktop demotes them to system / drops them; the CLI already uses not m.get("display_kind") for resume turn counting).

When a session ends with a marker after the last real exchange:

  • /undo soft-deleted from the marker only, leaving the last real Q/A intact — the user thinks they undid the last turn and they did not.
  • /retry re-sent the opaque marker text and truncated only the marker instead of the failed exchange.

Same class as the prompt.submit ordinal skew (#72695): an index into the frontend's user-turn array treated as an index into the backend's full history. That PR fixed the destructive rewind ordinal; these paths still used the unfiltered definition of "user turn".

Problem

Producer (soft-delete target list)SessionDB.list_recent_user_messages:

WHERE session_id = ? AND role = 'user' AND active = 1

No display_kind exclusion. Every CLI / TUI / gateway /undo [N] and gateway.session.rewind_session call this list to pick the Nth-from-last user row, then rewind_to_message soft-deletes from that id forward.

Producer (in-memory retry/undo)tui_gateway /retry, session.undo, gateway slash /retry, CLI retry_last / undo_last walk history with only role == "user".

Consumer rule already agreed elsewhere:

  • hermes_cli/cli_agent_setup_mixin.pyrole == "user" and not m.get("display_kind")
  • hermes_cli/cli_commands_mixin.py — same
  • desktop toChatMessages demotes the known kinds to system / drops hidden

Default config. async_delegation_complete is emitted by the notification poller; _AUTO_CONTINUE_ENABLED_DEFAULT = True. Markers accumulate permanently in the session, so every subsequent /undo / /retry in that chat is misaligned until the next real user turn is newest again.

Unrecoverable for /retry on gateway: rewrite_transcript hard-replaces the transcript before re-sending the marker text. For TUI /undo, the soft-delete is durable — the wrong cut is persisted with active=0 on the wrong slice (or the right real turn is not rewound when only the marker is cut).

Fix

  1. SessionDB.list_recent_user_messages — add AND (display_kind IS NULL OR display_kind = ''). Fixes CLI/TUI/gateway /undo and rewind_session at the source.
  2. tui_gateway /retry and session.undo — walk with role == "user" and not display_kind; session.undo truncates from that index (same semantics as soft-delete).
  3. Gateway slash /retry — same predicate before rewrite_transcript.
  4. CLI retry_last / undo_last — same predicate on the in-memory walk (belt-and-suspenders with the list filter for the DB path).

Predicate matches the CLI siblings and #72695.

Scope

  • hermes_state.py — one SQL clause + docstring on list_recent_user_messages
  • tui_gateway/server.py/retry and session.undo walkers
  • gateway/slash_commands.py — gateway /retry
  • cli.pyretry_last / undo_last walkers
  • Tests for the TUI undo soft-delete path and /retry history walk

No desktop change required. No change to replace_messages / rewind_to_message themselves.

Related but distinct: #72695 (prompt.submit ordinal only). This PR does not touch that path.

Testing

  • test_undo_skips_display_kind_timeline_rows — history with a trailing async_delegation_complete marker; /undo prefills question 2 and soft-deletes from that real turn (marker + q2/a2 gone; q1/a1 remain). Fails on unpatched list_recent_user_messages.
  • test_command_dispatch_retry_skips_display_kind_timeline_rows/retry resends second question and truncates through the real last user turn (not the marker).
  • Existing undo/retry suite still green.
python -m pytest \
  tests/tui_gateway/test_undo_command.py \
  tests/tui_gateway/test_protocol.py::test_command_dispatch_retry_finds_last_user_message \
  tests/tui_gateway/test_protocol.py::test_command_dispatch_retry_skips_display_kind_timeline_rows \
  tests/tui_gateway/test_protocol.py::test_command_dispatch_retry_empty_history \
  tests/tui_gateway/test_protocol.py::test_command_dispatch_retry_handles_multipart_content \
  -o addopts= -q
15 passed

list_recent_user_messages and the in-memory /retry + session.undo walkers
treated every role=user row as a real user turn. Timeline bookkeeping
(model_switch, async_delegation_complete, auto_continue, hidden) is stored
that way, so /undo soft-deleted from a marker and /retry re-sent opaque
bookkeeping text. Exclude display_kind the same way CLI resume counting and
the prompt.submit ordinal path do.
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 27, 2026
@kshitijk4poor

Copy link
Copy Markdown
Collaborator

Merged via #73396. Your commits were cherry-picked with authorship preserved (rebase merge).

The fix was widened to two sibling sites found during Phase 2 review:

  • truncate_before_user_ordinal in prompt.submit (same ordinal-skew bug)
  • rollback.restore (same pop-loop pattern)

Both now use the same role == "user" and not display_kind predicate. Tests added for both.

Thanks for the clean fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) P1 High — major feature broken, no workaround 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.

3 participants