Skip to content

fix(delegate): pin gateway async completions to the spawning session (#57498) - #60871

Merged
teknium1 merged 2 commits into
mainfrom
fix/gateway-completion-parent-pinning
Jul 8, 2026
Merged

fix(delegate): pin gateway async completions to the spawning session (#57498)#60871
teknium1 merged 2 commits into
mainfrom
fix/gateway-completion-parent-pinning

Conversation

@teknium1

@teknium1 teknium1 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Messaging-gateway async-delegation completions are now pinned to the durable session that spawned them: the completion event carries the parent agent's state.db session id, and the gateway switches routing to that session instead of recovering "the latest open session for the peer" — which delivered results into the wrong conversation whenever the user reset or rotated the session while a subagent ran.

Salvage of #57535 by @nankingjing, cherry-picked with authorship preserved. This is the messaging-gateway twin of the TUI-side origin routing in #60863: same concept (a return address stamped at dispatch), different surface. The two fields coexist deliberately — origin_ui_session_id is the live TUI tab/window; parent_session_id is the durable session-db id the gateway routes by.

Changes

  • tools/async_delegation.py / tools/delegate_tool.py (@nankingjing): capture parent_session_id at dispatch (single + batch) and carry it on completion events.
  • gateway/run.py: when the synthetic completion event carries a pinned session id that differs from the peer's current routing, switch_session() to the spawning session before injecting; logged.
  • tests/tools/test_async_delegation.py: field-propagation coverage.

Validation

Before After
delegation completes after the user /news the chat result injected into the fresh session's context routed to the spawning session
targeted tests 8,992 passed (full tests/gateway/ + async delegation + delegate tool suites)

Part of #55578; fixes #57498.

Follow-up commit: whole-class enforcement on the gateway surface

Per review, the same three session-binding rules applied to the TUI stack now hold here:

  • Never resurrect an ended session (fail closed). switch_session() re-opens ended sessions, so blind pinning could revive a conversation the user explicitly /new-ended and inject into it. The injection path now checks the pinned row's ended_at first; a completion whose spawning session is dead/unknown is dropped from injection with a WARNING (output remains in the delegation records).
  • /new severs the old conversation's delegations. _handle_reset_command calls interrupt_for_session() with the expiring durable session id + routing key, so a reset can't leave dangling subagents whose completions have no live owner.
  • interrupt_for_session() gains a parent_session_id selector — a gateway chat's session_key survives a reset while the session id rotates, so key matching alone could never sever gateway delegations.

New tests: tests/gateway/test_async_delegation_session_binding.py (pin-to-live, drop-on-ended, drop-on-unknown, reset-severs matrix). Full tests/gateway/ suite: 8,969 passed.

Infographic

gateway-completion-pinning

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved (LGTM)

Salvage of #57535: pins gateway async-delegation completions to the spawning durable session. Fixes misrouting when user resets/rotates session while a subagent runs.

Changes

  • gateway/run.py: switch_session() to pinned parent session before injecting
  • tools/async_delegation.py + tools/delegate_tool.py: capture parent_session_id at dispatch
  • tests/tools/test_async_delegation.py: field-propagation coverage

Quality

  • Well-scoped: 4 files, 35 additions / 0 deletions
  • Tests: targeted test suite (8,992 passed)
  • Clear root cause analysis and before/after table

Reviewed by Hermes Agent

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery tool/delegate Subagent delegation sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Jul 8, 2026
@teknium1
teknium1 force-pushed the fix/gateway-completion-parent-pinning branch from 3c0e7e2 to 5d54cb4 Compare July 8, 2026 14:54
nankingjing and others added 2 commits July 8, 2026 07:55
Background delegate_task completions only carried session_key. When multiple
active sessions shared a routing peer, get_or_create_session could recover the
latest ended_at IS NULL row and inject the subagent result into the wrong
session.

Capture parent_agent.session_id at dispatch time, include it on async-delegation
completion events, and pin gateway routing via switch_session when the
synthetic completion message is handled.

Fixes #57498
…ns; /new severs in-flight delegations

Completes the session-binding class on the gateway surface (#55578),
matching the TUI rules:

1. Fail-closed pinning: switch_session() re-opens ended sessions, so
   pinning a completion to a spawning session that has since ENDED
   (user /new, closed rotation) would resurrect a conversation the user
   explicitly ended and inject into it. The injection path now checks
   the pinned row's ended_at first and drops the injection with a
   WARNING when the spawning session is dead or unknown - the result
   stays in the delegation records.

2. /new ends the old conversation's delegations: _handle_reset_command
   calls interrupt_for_session() with the expiring durable session id
   (matching the parent_session_id pin stamped at dispatch) plus the
   routing key as fallback, so a reset can't leave dangling subagents
   whose completions have no live owner.

interrupt_for_session() gains the parent_session_id selector because a
gateway chat's session_key (the platform conversation key) survives a
reset while the session id rotates - key-based matching alone could
never sever a gateway conversation's delegations.
@teknium1
teknium1 force-pushed the fix/gateway-completion-parent-pinning branch from 5d54cb4 to 643354b Compare July 8, 2026 14:55
@teknium1
teknium1 merged commit 75efd73 into main Jul 8, 2026
31 checks passed
@teknium1
teknium1 deleted the fix/gateway-completion-parent-pinning branch July 8, 2026 15:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/delegate Subagent delegation type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

delegate_task background results routed to wrong session (cross-channel contamination)

4 participants