fix(history): stop personality marker from swallowing the next real prompt - #74350
Open
JoaoMarcos44 wants to merge 3 commits into
Open
JoaoMarcos44 wants to merge 3 commits into
JoaoMarcos44 wants to merge 3 commits into
Conversation
…ed real prompts
The personality-switch marker was appended as an in-memory-only role=user
message identified purely by a "[System:" text prefix, unlike the
model-switch marker which is durably persisted via SessionDB.append_message.
When alternation-repair merged the marker with the next real user turn
(both role=user), the merged blob still started with "[System:" and the
display projection dropped the whole row, silently swallowing the real
prompt.
Persist the marker durably and identify it via display_kind="personality_switch"
plus display_metadata={"marker_text": ...}, mirroring the model-switch
marker's pattern. Both survive alternation-repair's merge (which only
rewrites `content`), so the hide-projection can now strip just the marker's
own span from a merged row instead of dropping the whole thing.
Closes NousResearch#74315
teknium1
reviewed
Jul 30, 2026
teknium1
left a comment
Collaborator
There was a problem hiding this comment.
Thanks for tracing the marker/alternation/display interaction; the current-main premise is valid: tui_gateway/server.py:5556-5558 creates the consecutive user marker, agent/agent_runtime_helpers.py:681-709 merges it, and tui_gateway/server.py:6615-6617 hides the merged [System: row.
Problems
- The new
display_kind="personality_switch"attui_gateway/server.py:5516survives that merge because repair rewrites onlycontent(agent/agent_runtime_helpers.py:692-709). The merged row is then excluded from every current real-user predicate:/undoattui_gateway/methods_session.py:2307,/retryattui_gateway/methods_tools.py:721, and rewind ordinal selection attui_gateway/methods_prompt.py:168. The projection suppresses the display kind, but the live session history does not.
Suggested changes
- Preserve a distinct merged-marker discriminator while making the merged row count as a real user turn for those paths, and add regression coverage for undo, retry, and rewind after the merge.
Automated hermes-sweeper review.
…y/rewind teknium's review on NousResearch#74350: repair_message_sequence's consecutive-user merge only rewrites content, so a real prompt merged onto a personality/model-switch marker tail still carries the marker's display_kind. undo, retry, and rewind ordinal selection all treat role=user with no display_kind as the definition of a real turn, so they silently skipped this visible prompt. Pass 2 of repair_message_sequence now stamps merged_real_turn=True on a bookkeeping-marker row when a distinct real turn gets merged onto it. The four role=user and not display_kind predicates in tui_gateway/server.py (session.undo, prompt.submit rewind ordinal, retry, rollback.restore) now also accept merged_real_turn rows as real. Added regression coverage for undo, retry, and rewind after the merge.
Contributor
Author
Done |
Resolves conflicts from main's split of tui_gateway/server.py into methods_session.py/methods_prompt.py/methods_tools.py: took main's side for the moved RPC handlers (session.undo, prompt.submit, command.dispatch, rollback.*) and reapplied the merged_real_turn predicate fix (this branch's last commit) to its new locations: - methods_session.py: session.undo - methods_prompt.py: prompt.submit rewind-ordinal selection - methods_tools.py: /retry and rollback.restore tests/tui_gateway/test_protocol.py: main dropped the old command.dispatch retry/steer/skill/bundle test block wholesale (no replacement found elsewhere); kept only this branch's new regression test (test_command_dispatch_retry_resends_prompt_merged_onto_marker), which still exercises the live command.dispatch/retry codepath. _apply_personality_to_session and _bookkeeping_marker_span (the original PR fix) did not conflict — main hadn't touched that code.
This was referenced Aug 5, 2026
refactor(tui): extract pet payload into tui_gateway/pet_payload (server.py god-file slice R3)
#79261
Open
Closed
Closed
13 tasks
This branch has not been deployed
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.
Summary
SessionDB.append_message) and identified via structureddisplay_kind="personality_switch"+display_metadata={"marker_text": ...}, mirroring the existing model-switch marker pattern instead of relying on a"[System:"text-prefix convention._history_to_messages) now strips only the marker's own span from a row that alternation-repair merged with a real user turn, instead of dropping the whole merged row.Root Cause
_apply_personality_to_sessionintui_gateway/server.pyappended the personality-pivot marker as a plain{"role": "user", "content": marker}dict, mutating onlysession["history"]in memory — never durably persisted viaSessionDB.append_message()(unlike the model-switch marker, see_append_model_switch_marker).Because the marker was
role="user"and the next real prompt is alsorole="user", alternation-repair (agent/agent_runtime_helpers.py::repair_message_sequence) merges the two consecutiveusermessages, concatenating the real prompt onto the marker'scontent(marker_text + "\n\n" + real_text).The display projection's hide check (
_is_display_hidden_marker) then hid the row wholesale because the merged content still started with the literal string"[System:"— with no structured way to tell "this is a bare marker" apart from "this is a marker with a real prompt merged onto its tail". The real prompt the user typed disappeared from every client transcript after reload/resume.Fix
_apply_personality_to_sessionnow callsSessionDB.append_message(..., display_kind="personality_switch", display_metadata={"marker_text": marker}), following the exact pattern already used by_append_model_switch_marker(same_ensure_session_db_row/_session_dbfallback, wrapped the same way).display_kind="personality_switch"anddisplay_metadata={"marker_text": ...}. Alternation-repair's merge only rewritescontent—display_kind/display_metadatasurvive the merge untouched, so both the merge and the projection can recognize the marker unambiguously._bookkeeping_marker_span()helper intui_gateway/server.pycompares a row'scontentagainst its recordeddisplay_metadata["marker_text"]. If they're equal, the row is a bare marker and is hidden entirely (unchanged behavior). Ifcontentstarts withmarker_text + "\n\n", only that prefix is stripped — the remainder (the real user prompt) is kept and rendered as an ordinary message, with the marker'sdisplay_kind/display_metadatasuppressed since the visible content is no longer the marker.marker_text(legacy rows persisted before this fix, or the model-switch marker which doesn't yet carrydisplay_metadata) fall back to the previous"[System:"text-prefix hiding — no behavior change for those paths.Diff is scoped to the personality-marker path (
tui_gateway/server.py);agent_runtime_helpers.py's alternation-repair merge logic is unchanged (confirmed it already leavesdisplay_kind/display_metadatauntouched on merge).Test Plan
test_personality_marker_survives_alternation_repair_merge(tests/test_tui_gateway_server.py) — reproduces the exact chain from the issue: personality marker inserted with structured metadata → merged with the next real user turn viarepair_message_sequence→ projected via_history_to_messages→ asserts the real prompt text is present and visible, and that no literal"[System:"text leaks into the projected transcript.test_history_to_messages_hides_bare_personality_marker_without_merge— confirms a marker with nothing merged onto it is still hidden entirely, matching the model-switch marker's existing contract.pytest tests/test_tui_gateway_server.py -k "personality_marker or hides_gateway_system_markers or hides_bare_personality_marker or config_set_personality"— 5 passed.tests/test_tui_gateway_server.pysuite (493 passed, 2 pre-existing unrelated failures —test_persist_model_switch_preserves_sibling_model_keys/test_persist_model_switch_clears_stale_base_url— confirmed failing identically on unmodifiedmain, unrelated to this change).tests/run_agent/test_message_sequence_repair.py(43 passed),tests/gateway/test_session.py+tests/cli/test_personality_none.py(164 passed),tests/hermes_state/test_restore_alternation_repair.py+tests/agent/test_synthetic_turn_display_kind.py(7 passed) — no regressions.Closes #74315
Infographic