Skip to content

fix(tui_gateway): candidate-inclusive display on warm/live + child-watch resume (#65919 fallout) - #69040

Merged
OutThisLife merged 2 commits into
mainfrom
bb/fix-verify-candidate-warm-payload
Jul 22, 2026
Merged

fix(tui_gateway): candidate-inclusive display on warm/live + child-watch resume (#65919 fallout)#69040
OutThisLife merged 2 commits into
mainfrom
bb/fix-verify-candidate-warm-payload

Conversation

@OutThisLife

@OutThisLife OutThisLife commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Problem

#65919 ("don't wipe messages before final message") made a durable, unconditional change: verification candidates (finish_reason=verification_required / verify_hook_continue) are persisted to state.db, but repair_message_sequence collapses them out of the in-memory model history. That leaves two projections of one session:

  • display lineage (verbatim, candidate present) — served by the eager session.resume path and REST /messages.
  • model history (candidate collapsed) — what two resume payloads were serving for the user-visible messages.

So switching to a still-live session (or opening a delegated-child watch window) dropped the substantive verification answer that a cold resume of the same session showed. The payloads disagreed about one session — the cross-session "substantive text vanishes on switch" class, and the direct sibling of the resume-duplication regression already fixed in #68149. The #65919 reviewer note called this out pre-merge: the config flag gates only the display/transport callback; the agent-core persistence + collapse ships unconditionally, so every consumer that mixes the two projections must reconcile them — two didn't.

Fix

Reconcile the projections in one place, and apply it to both offending payload builders.

  • _reconcile_display_with_live(db_display, in_memory) — pure, DI-testable. Candidate-inclusive DB display is the base; anchors on the last persisted row's (role, text); appends only the in-memory tail the DB doesn't yet cover; trusts the DB display when the tail can't be anchored (DB ahead / diverged).
  • _live_visible_history (warm/live reuse) reads the same get_messages_as_conversation(..., include_ancestors=True) the eager resume + REST paths use, reconciled with the fresh in-memory tail — so all surfaces agree by construction while a not-yet-flushed live turn still shows.
  • Child-watch (lazy) resume now builds its user-visible messages from the verbatim child-only display projection (repair_alternation=False) instead of the repaired history; the repaired history still feeds live replay. Falls back to the repaired history if the display read fails.

No schema change, no new config, no mid-conversation cache mutation, no desktop/TS change.

Testing

scripts/run_tests.sh tests/test_tui_gateway_server.py tests/tui_gateway/ tests/test_hermes_state.py1166 passed, 0 failed.

Coverage (incl. real-SessionDB E2E, not mocks):

  • cross-builder consistency: a persisted candidate is collapsed out of the model projection, kept in the display projection, and _live_visible_history equals the eager session.resume display projection (candidate present).
  • combined candidate + fully-flushed second turn both appear once.
  • lazy child-watch handler test: candidate survives in resp["result"]["messages"].
  • unit fallbacks: DB/session_key missing, DB empty (unflushed first turn), DB raising.
  • the pre-existing freshness guard test_session_resume_live_payload_uses_current_history_with_ancestors stays green — the reconciliation satisfies both invariants at once.

Scope

Fixes the confirmable backend fallout of #65919 across both remaining offending paths. The other confirmed regression (#68149) is already merged. #68231 (desktop live-render) does not reproduce on current mainapps/desktop/.../interim-sealing.test.tsx:100 ("preserves interim text that the final response does not include") already covers that scenario and is green; that report matches a pre-merge desktop build. This PR additionally guarantees the persisted substantive answer recovers consistently on any refresh/switch.

Relates to #65919, #68149. sweeper:risk-session-state.

#65919 persists verification candidates (finish_reason=verification_required
/ verify_hook_continue) to state.db but collapses them out of the in-memory
model history via repair_message_sequence. The eager session.resume + REST
paths read the verbatim display lineage (candidate present), but the
warm/live-reuse payload (_live_session_payload) built its user-visible
messages from the collapsed in-memory model history — so switching to a
still-live session dropped the substantive verification answer that a cold
resume of the SAME session showed. That divergence is the cross-session
"substantive text vanishes on switch" class, and the direct sibling of the
resume-duplication regression fixed in #68149.

Reconcile the persisted display lineage (candidate-inclusive, the same
get_messages_as_conversation(..., include_ancestors=True) read the eager
resume + REST paths use) with the fresh in-memory tail in
_live_visible_history, so all three surfaces agree by construction while a
not-yet-flushed live turn is still shown. Extracted
_reconcile_display_with_live as a pure, DI-testable function (anchors on the
last persisted row's (role, text); appends only the uncovered in-memory tail;
trusts the DB display when the tail can't be anchored).

Tests: unit coverage for candidate-inclusion, freshness, empty/raising-DB
fallback, and the combined candidate+fresh-tail case. The existing freshness
guard (test_session_resume_live_payload_uses_current_history_with_ancestors)
stays green.
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

running on 77855ce

CI timings

CI timings · View job

Wall time 7m54s vs 10m10s (-22.3%). 9 job(s) slower, 9 faster, 2 unchanged.

  • Build&Test Docker image / build (arm64, ubuntu-24.04-arm, linux/arm64, type=gha,scope=docker-arm64, type=gha,mode=max,scope...: -151.0s
  • Build&Test Docker image / build (amd64, ubuntu-latest, linux/amd64, type=gha,scope=docker-amd64, type=gha,mode=max,scope=do...: -99.0s
  • Python tests / e2e: +55.0s
  • Python tests / Run tests slice 5/8: -40.0s
  • Python tests / Run tests slice 4/8: -35.0s

… E2E

Complete the #65919 warm/live-payload fix across its sibling path and add
real-SessionDB cross-builder coverage.

- Child-watch (lazy) resume: the delegated-subagent watch window served
  _history_to_messages(repaired_history) for its user-visible messages, which
  collapses out persisted verification candidates just like the warm-payload
  path did. Build the visible messages from the verbatim child-only display
  projection (repair_alternation=False) while the repaired history still feeds
  live replay; fall back to the repaired history if the display read fails.

- E2E cross-builder consistency (real SessionDB, not mocks): a persisted
  verification candidate is collapsed out of the model projection but kept in
  the display projection, and _live_visible_history now equals the eager
  session.resume display projection (candidate present). Adds the combined
  candidate + fully-flushed-second-turn case and a lazy child-watch handler
  test that asserts the candidate survives in resp["result"]["messages"].
@OutThisLife OutThisLife changed the title fix(tui_gateway): serve candidate-inclusive display on warm/live resume (#65919 fallout) fix(tui_gateway): candidate-inclusive display on warm/live + child-watch resume (#65919 fallout) Jul 22, 2026
@OutThisLife
OutThisLife merged commit 8f51376 into main Jul 22, 2026
36 checks passed
@OutThisLife
OutThisLife deleted the bb/fix-verify-candidate-warm-payload branch July 22, 2026 02:16
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/tui Terminal UI (ui-tui/ + tui_gateway/) area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 22, 2026
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…-candidate-warm-payload

fix(tui_gateway): candidate-inclusive display on warm/live + child-watch resume (NousResearch#65919 fallout)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists 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