fix(delegation): route async results to their origin session across compression (#55578) - #60863
Merged
Merged
Conversation
This was referenced Jul 8, 2026
tonydwb
reviewed
Jul 8, 2026
tonydwb
left a comment
There was a problem hiding this comment.
Code Review Summary
Verdict: Approved (LGTM)
Salvage of #59767: async delegation completions now carry the commissioning UI session ID and compressed session keys are resolved to their continuation before treating an event as orphaned. Fixes results delivered to wrong tab when user switches sessions during delegation.
Changes
- gateway/session_context.py: new HERMES_UI_SESSION_ID context var
- tools/delegate_tool.py + tools/async_delegation.py: capture origin_ui_session_id at dispatch
- tui_gateway/server.py: ownership check prefers commissioning UI session; falls back to durable session_key; resolves compressed keys via resolve_resume_session_id
Quality
- Well-scoped: 6 files, 262 additions / 20 deletions
- Tests: 619 passed (tui_gateway + async delegation suites)
- Clear before/after table and root cause analysis
- Part of #55578 async-delegation session-split stack
Reviewed by Hermes Agent
Carry the live TUI session id with async delegation completion events and prefer the commissioning UI session when desktop pollers share the completion queue. Resolve compressed session keys to their continuation before treating events as orphaned, and capture the live parent agent session id for TUI/ACP dispatch.
…ion lifecycle Two invariants layered on the origin-routing commit (#55578): 1. Fail closed on orphaned async-delegation payloads. The poller's belongs-elsewhere check handles events owned by another LIVE session, but an event whose owner is gone previously fell through and was adopted by whichever poller saw it - injecting one chat's delegation output into another chat. Delegation completions are now injected only into a session that PROVABLY owns them (origin UI id, or session-key/lineage match via the compression chain); unowned payloads are dropped from injection with a WARNING (the subagent's output is already persisted in the delegation records, so nothing is lost). The shutdown drain applies the same rule. Non-delegation events keep the historical adopt-orphans behavior. 2. A session's in-flight async delegations end with the session. _finalize_session now calls interrupt_for_session(): delegations commissioned by the closing UI session are interrupted always; key-matched delegations only when the TUI owns the session lifecycle, so closing a viewer tab on a live gateway session never kills the gateway's own background work.
teknium1
force-pushed
the
fix/async-delegation-origin-routing
branch
from
July 8, 2026 13:48
fc577a7 to
24bf55f
Compare
This was referenced Jul 8, 2026
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
Async delegation completions now carry a precise return address — the live UI session that commissioned them — and compression-rotated session keys are resolved to their continuation before an event is treated as orphaned. This stops detached results from being claimed by whichever desktop poller wakes first (the core misrouting behind #55578).
Salvage of #59767 by @dschnurbusch, cherry-picked with authorship preserved (clean pick, +262/−20 across 6 files, includes tests).
Changes
gateway/session_context.py: newHERMES_UI_SESSION_IDcontext var — the live frontend tab/window id, deliberately separate from the durable session-db id.tools/delegate_tool.py/tools/async_delegation.py: captureorigin_ui_session_idat dispatch and carry it on completion events (single + batch).tui_gateway/server.py: ownership check prefers the commissioning UI session; if that window is gone, falls back to durable session_key routing; compressed keys are resolved throughresolve_resume_session_idso a pre-compression dispatch maps to the live continuation instead of becoming a free-for-all orphan — and a stale parent tab can't steal the continuation's result. Finalized sessions are excluded from ownership claims.tests/tools/test_async_delegation.pyandtests/test_tui_gateway_server.py.Supersedes #57586 (compression-chain half is included here) and the TUI-path portion of #35667.
Validation
Part of #55578.
Follow-up commit: fail-closed ownership + session-scoped lifecycle
Per review, two invariants added on top of the salvaged commit (24bf55f):
_finalize_sessionnow interrupts the closing session's in-flight async delegations via a newinterrupt_for_session()(per-session sibling ofinterrupt_all()): always by commissioning UI id; by durable session key only when the TUI owns the lifecycle — closing a viewer tab on a live gateway session never kills the gateway's own background work.10 new tests (
tests/tui_gateway/test_delegation_session_lifecycle.py); 661 targeted tests green.Infographic