feat(tui_gateway): INFO 'prompt accepted / turn finished' records on the Desktop/TUI turn path (#86647) - #86865
Merged
Merged
Conversation
…the Desktop/TUI turn path Part of #86647. During the #79278 persistent-mute investigation the decisive evidence was an absence: a Desktop request left no INFO record in agent.log OR gateway.log ("832 platform=webhook, 194 platform=telegram, 0 platform=desktop"), so the muted 13:15-13:19 window — 12 non-idempotent Qdrant snapshots, zero results returned — was structurally indistinguishable from a request that never arrived. The issue calls out fixing this observability gap as the first actionable step. This adds the two INFO records to _run_prompt_submit, the single choke point every Desktop/TUI turn passes through (user submits, queued prompts, auto-continue, goal follow-ups, watch upgrades): - "tui prompt accepted": emitted before the turn thread starts, carrying the UI session id, the gateway session_key, and the agent's live session_id — the id triple a rotation-mute trace needs, since compression rotates agent.session_id independently of the other two. No prompt content is logged (length only). - "tui turn finished": emitted in the turn's finally on every path (success, returned error, exception, interrupt), re-reading agent.session_id so a mid-turn compression rotation shows up as an accepted/finished pair with different agent ids. A missing finished record now positively identifies a turn thread that died before its finally. Placement follows @Adolanium's note on the issue: in _run_prompt_submit, logging sid + session_key + agent.session_id, NOT another platform= line in gateway.log (Desktop does not use the messaging gateway). tui_gateway is under COMPONENT_PREFIXES["gui"], so the records land in agent.log (root catch-all) and gui.log when running under the dashboard. Tests (tests/tui_gateway/test_prompt_accept_logging.py): accepted+finished pair on success with the full id triple and no prompt content leaked; mid-turn rotation visible as differing agent ids across the pair; finished record fires on the exception path and the returned-error path. Sabotage-verified: removing the accepted record fails the suite.
Contributor
૮ >ﻌ< ა ci reviewran on e1d8f8c — feat(tui_gateway): INFO 'prompt accepted / turn finished' re
|
This was referenced Aug 18, 2026
ruangraung
pushed a commit
to ruangraung/hermes-agent
that referenced
this pull request
Sep 2, 2026
Fixes NousResearch#89117 The whole of NousResearch#89117 is two log lines: tui_turn finished: ui_session=0dfcee58 status=error error_retained=True duration=0.9s A provider 4xx, a budget wall, a billing block and a crashed finalizer all produce exactly those characters, so an intermittent failure cannot be triaged from the one record that is guaranteed to exist. The bookend came from NousResearch#86865, which added it to trace compression rotations across NousResearch#86647 -- identities and a coarse status were the job, and content was deliberately excluded. What that leaves is a returned-error path (provider 4xx, budget, billing) which writes no other log line at all. The exception path at least prints `[gateway-turn] <Type>: <msg>` to stderr, so the failures that go unlogged are exactly the sub-second ones this issue is about. Both failure paths now stash a one-line cause, and the bookend appends it. The record keeps its shape when nothing failed: a successful turn gains no new fields. The cause is redacted with `redact_sensitive_text(force=True)` and capped at 240 characters with a visible ellipsis, because a 4xx body routinely quotes the request that produced it -- adding the cause without redacting it would write an Authorization header the user never chose to log. Redaction fails closed: if the redactor cannot run, the fragment reads `<unredactable>` rather than the raw message. Whitespace is collapsed so a multi-line provider body cannot split the record, which is the only property that makes it greppable for a bug like this one. 12 regression tests. Four mutations proven: disabling the helper fails 9, dropping redaction fails 2, dropping truncation fails 1, wiring only the exception path fails 4.
melon-xf
added a commit
to melon-xf/hermes-agent
that referenced
this pull request
Sep 3, 2026
Fixes NousResearch#89117 The whole of NousResearch#89117 is two log lines: tui_turn finished: ui_session=0dfcee58 status=error error_retained=True duration=0.9s A provider 4xx, a budget wall, a billing block and a crashed finalizer all produce exactly those characters, so an intermittent failure cannot be triaged from the one record that is guaranteed to exist. The bookend came from NousResearch#86865, which added it to trace compression rotations across NousResearch#86647 -- identities and a coarse status were the job, and content was deliberately excluded. What that leaves is a returned-error path (provider 4xx, budget, billing) which writes no other log line at all. The exception path at least prints `[gateway-turn] <Type>: <msg>` to stderr, so the failures that go unlogged are exactly the sub-second ones this issue is about. Both failure paths now stash a one-line cause, and the bookend appends it. The record keeps its shape when nothing failed: a successful turn gains no new fields. The cause is redacted with `redact_sensitive_text(force=True)` and capped at 240 characters with a visible ellipsis, because a 4xx body routinely quotes the request that produced it -- adding the cause without redacting it would write an Authorization header the user never chose to log. Redaction fails closed: if the redactor cannot run, the fragment reads `<unredactable>` rather than the raw message. Whitespace is collapsed so a multi-line provider body cannot split the record, which is the only property that makes it greppable for a bug like this one. 12 regression tests. Four mutations proven: disabling the helper fails 9, dropping redaction fails 2, dropping truncation fails 1, wiring only the exception path fails 4.
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
Part of #86647 — the persistent tool-result mute after compression rotation (split from #79278).
This PR ships the first actionable step the issue calls out: on the affected build a Desktop request left no INFO record in
agent.logorgateway.log(832 platform=webhook, 194 platform=telegram, 0 platform=desktop), so the muted 13:15–13:19 window — 12 non-idempotent Qdrant snapshots, zero results returned — was structurally indistinguishable from a request that never arrived. Until the next occurrence is measurable, the mute itself cannot be pinned.Investigation findings (why observability first)
#80916per-event compression-strip fix does not explain the 5-hour mute across four id rotations (…6d0e88 → …2c716f → …fc25ed → …1ce05c); @kshitijk4poor's analysis on [Bug]: context compression can drop an in-flight tool chain — side effect completes, result never reaches the agent, agent replays #79278 stands.agent/turn_context.py→recover_rotated_compression_session→_adopt_live_compression_child) already resolves via the canonicalget_compression_tipwalk on current main, and_run_prompt_submitre-syncssession_keypost-turn via_sync_session_key_after_compress. I could not reproduce a rotation-path drop on main from the preserved evidence alone — the log slice shows no turn start at all (last eventTurn ended … 13:02:34, snapshots begin 13:15:52), which is equally consistent with the executing process being a different backend (the reporter's second bind-looped gateway held six duplicated MCP stdio children) as with a dispatch drop in this process. Absence of Desktop logging is exactly what blocks the discrimination._run_prompt_submit(the single choke point every Desktop/TUI turn passes through — user submits, queued prompts, auto-continue, goal follow-ups), logs the UI sid +session_key+agent.session_id, and is not anotherplatform=line ingateway.log(Desktop does not use the messaging gateway).Changes
tui_gateway/server.py_run_prompt_submit— one INFOtui prompt acceptedrecord before the turn thread starts, carrying the full id triple a rotation-mute trace needs:ui_session(desktop tab),session_key(gateway routing),agent_session_id(rotated independently by compression). No prompt content is logged — length only.finally— one INFOtui turn finishedbookend on every path (success, returned error, exception, interrupt), re-readingagent.session_idso a mid-turn compression rotation shows up as an accepted/finished pair with different agent ids — that pair IS the rotation trace. A missing finished record now positively identifies a turn thread that died before itsfinally; a missing accepted record positively identifies a request that never reached this process. On the next occurrence those two signals discriminate the remaining hypotheses (foreign process vs. dispatch drop vs. result-append drop) in one grep.tui_gatewayis underCOMPONENT_PREFIXES["gui"], so the records land inagent.log(root catch-all — the file the reporter preserves) andgui.logunder the dashboard.Tests
tests/tui_gateway/test_prompt_accept_logging.py(new):chars=logged)agent_session_id=parent-sidin accepted vsagent_session_id=continuation-sidin finishedstatus=error error_retained=True) and the returned-error pathFull regression:
tests/tui_gateway/,tests/test_tui_gateway_server.py,tests/run_agent/test_compression_closed_adoption.py,tests/gateway/test_session.py→ 1112 passed. Sabotage-verified: renaming the accepted record fails the new suite.Infographic