Skip to content

feat(tui_gateway): INFO 'prompt accepted / turn finished' records on the Desktop/TUI turn path (#86647) - #86865

Merged
teknium1 merged 1 commit into
mainfrom
fix/86647-rotation-mute
Aug 15, 2026
Merged

teknium1 merged 1 commit into
mainfrom
fix/86647-rotation-mute

Conversation

@teknium1

Copy link
Copy Markdown
Collaborator

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.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. Until the next occurrence is measurable, the mute itself cannot be pinned.

Investigation findings (why observability first)

  • The #80916 per-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.
  • I audited the merged fix(compression): adopt live continuation tip at flush across multi-hop chains #86666 adopt-live-tip pattern against the Desktop/TUI turn path. The turn-start recovery preflight (agent/turn_context.pyrecover_rotated_compression_session_adopt_live_compression_child) already resolves via the canonical get_compression_tip walk on current main, and _run_prompt_submit re-syncs session_key post-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 event Turn 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.
  • Per @Adolanium's comment on the issue: the record goes in _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 another platform= line in gateway.log (Desktop does not use the messaging gateway).

Changes

  • tui_gateway/server.py _run_prompt_submit — one INFO tui prompt accepted record 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.
  • Turn finally — one INFO tui turn finished bookend 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 — that pair IS the rotation trace. A missing finished record now positively identifies a turn thread that died before its finally; 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_gateway is under COMPONENT_PREFIXES["gui"], so the records land in agent.log (root catch-all — the file the reporter preserves) and gui.log under the dashboard.

Tests

tests/tui_gateway/test_prompt_accept_logging.py (new):

  • accepted+finished pair on success, with the full id triple asserted and no prompt content leaked (secret-shaped prompt, only chars= logged)
  • mid-turn rotation visible: agent_session_id=parent-sid in accepted vs agent_session_id=continuation-sid in finished
  • finished record fires on the exception path (status=error error_retained=True) and the returned-error path

Full regression: tests/tui_gateway/, tests/test_tui_gateway_server.py, tests/run_agent/test_compression_closed_adoption.py, tests/gateway/test_session.py1112 passed. Sabotage-verified: renaming the accepted record fails the new suite.

Infographic

Desktop turns leave a trace

…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.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Aug 15, 2026
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on e1d8f8c — feat(tui_gateway): INFO 'prompt accepted / turn finished' re

⚠️ Warnings

OSV vulnerability scan · View job

5 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 6m14s vs 8m28s (-26.4%). 11 job(s) slower, 12 faster, 1 unchanged.

  • Python tests / Run tests slice 1/12: +40.0s
  • Python tests / Run tests slice 8/12: +34.0s
  • Python tests / Run tests slice 7/12: -31.0s
  • Python tests / Run tests slice 4/12: -27.0s
  • Python tests / Run tests slice 11/12: -26.0s

@teknium1
teknium1 merged commit be08335 into main Aug 15, 2026
47 checks passed
@teknium1
teknium1 deleted the fix/86647-rotation-mute branch August 15, 2026 09:45
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants