Skip to content

fix(gateway): declare webhook sessions stateless so background delegations run inline (#69145) - #69159

Open
PRATHAMESH75 wants to merge 1 commit into
NousResearch:mainfrom
PRATHAMESH75:fix/gateway-webhook-stateless-async-delivery
Open

fix(gateway): declare webhook sessions stateless so background delegations run inline (#69145)#69159
PRATHAMESH75 wants to merge 1 commit into
NousResearch:mainfrom
PRATHAMESH75:fix/gateway-webhook-stateless-async-delivery

Conversation

@PRATHAMESH75

Copy link
Copy Markdown
Contributor

Summary

Fixes #69145. A webhook delivery is one-shot: the delivery_id is baked into the session key and WebhookAdapter.on_processing_complete ends the per-delivery session the moment the run finishes (gateway/platforms/webhook.py), so the parent session can never receive a second turn.

When the parent agent calls delegate_task (forced background=True at top level), the subagent's result arrives tens of seconds later — after ended_at is set — and the completion re-injection is dropped by the #55578 fail-closed guard:

Async-delegation completion pinned to session <id>, which is ended —
dropping injection instead of resurrecting it (#55578 fail-closed;
result remains in the delegation records).

The subagent's work is fully persisted to the delegation records, but the parent is never woken, so it never summarizes or posts back. The user sees only the "waiting for the subagent's result" placeholder — the #53027 / #63142 symptom, now on the gateway/webhook surface.

Root cause

#66617 (merged Jul 18) fixed this for hermes -z one-shot and cron by declaring a stateless channel (async_delivery=False) so delegate_task falls back to synchronous inline execution. But it only touched hermes_cli/oneshot.py and cron/scheduler.py — the gateway webhook runner was not covered.

GatewayRunner._set_session_env already propagates each adapter's supports_async_delivery into the _SESSION_ASYNC_DELIVERY contextvar. APIServerAdapter sets it False for exactly this reason; WebhookAdapter inherited the base default True, so webhook sessions bound async_delivery=True and dispatched detached children whose completions could never be delivered.

Fix

Set supports_async_delivery = False on WebhookAdapter (one class attribute, mirroring APIServerAdapter). Top-level background delegations on a webhook session now run inline and return within the turn.

MSGraphWebhookAdapter is intentionally left unchanged: it is subscription-based (persistent Teams/Outlook conversations) and does not end sessions per-delivery, so it genuinely supports async delivery.

Tests

tests/gateway/test_async_delivery_capability.py:

  • test_webhook_false — locks the class-attribute invariant.
  • TestWebhookSessionBindsStateless — end-to-end wiring through GatewayRunner._set_session_env: a webhook session binds async_delivery_supported() == False, while a delivering adapter still binds True (the fix must not disable delivery for real channels).

Full tests/gateway/test_webhook_adapter.py, test_webhook_session_close.py, and test_async_delivery_capability.py pass (116 + 18). Preflight gates green vs upstream/main.

…tions run inline (NousResearch#69145)

A webhook delivery is one-shot: the delivery_id is baked into the session
key and on_processing_complete ends the per-delivery session the moment the
run finishes, so the parent session can never receive a second turn. When
the parent agent calls delegate_task (forced background=True at top level),
the subagent's result arrives ~tens of seconds later — after ended_at is
set — and the completion re-injection is dropped by the NousResearch#55578 fail-closed
guard. The subagent's work is fully persisted to the delegation records but
the parent is never woken, so it never summarizes or posts back; the user
sees only the 'waiting for the subagent's result' placeholder (the NousResearch#53027/
NousResearch#63142 symptom on the gateway/webhook surface).

NousResearch#66617 fixed this for hermes -z one-shot and cron by declaring a stateless
channel (async_delivery=False) so delegate_task falls back to synchronous
inline execution, but it only touched hermes_cli/oneshot.py and
cron/scheduler.py — the gateway webhook runner was not covered.

Set supports_async_delivery = False on WebhookAdapter (mirroring
APIServerAdapter). GatewayRunner._set_session_env already propagates the
adapter's flag into the _SESSION_ASYNC_DELIVERY contextvar, so top-level
background delegations now run inline and return within the turn.

MSGraphWebhookAdapter is intentionally left as-is: it is subscription-based
(persistent Teams/Outlook conversations) and does not end sessions
per-delivery, so it genuinely supports async delivery.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery tool/delegate Subagent delegation platform/webhook Webhook / API server 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 labels Jul 22, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused fix. The premise remains present on current main: WebhookAdapter inherits supports_async_delivery=True (gateway/platforms/webhook.py:177; base default at gateway/platforms/base.py:2687), and GatewayRunner._set_session_env propagates that value into the delegation capability (gateway/run.py:18013-18029).

Webhook delivery sessions are keyed by delivery_id (gateway/platforms/webhook.py:872-874) and ended at completion (gateway/platforms/webhook.py:936-958, 1008-1011). A top-level delegation is forced to background mode (run_agent.py:6914-6928), while a later completion for an ended non-compression parent is terminally dropped (gateway/run.py:18737-18740, 18797-18817). The proposed false capability flag correctly selects the existing in-turn fallback in tools/delegate_tool.py:3168-3217.

The added tests cover both the adapter capability and GatewayRunner context binding without changing prompt, tool-schema, or session-routing design. GitHub reports the PR mergeable against its base; the two added hunks are localized and should be mechanically salvageable.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform area/sessions Session lifecycle, resume, persistence, history labels Jul 30, 2026
@GottZ

GottZ commented Aug 3, 2026

Copy link
Copy Markdown

This was generated by AI during triage.

Summary

Two open PRs address #69145 by setting WebhookAdapter.supports_async_delivery=False, causing the existing gateway binding to select synchronous inline delegation for one-shot webhook sessions. Their current full diffs cover the same root cause and both add gateway-binding regression coverage with an async-capable control.

Related pull requests

Duplicates

#69159 and revised #69166 are functional duplicates: both set WebhookAdapter.supports_async_delivery=False and verify its propagation through GatewayRunner._set_session_env with a supported-delivery control.

Suggested consolidation

Keep #69159 open with a salvage path: preserve its focused webhook capability flag and gateway-binding regression tests, consistent with its recorded best-fix status and keep-open review. Close #69166 as a duplicate of #69159; despite its keep-open review, its revised full diff now implements the same scope and tests after removing the only formerly distinct MSGraph change.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    I69145(["issue #69145 (open)"])
    subgraph Dup69159 ["PRs duplicating each other"]
        P69159["PR #69159 (open)"]
        P69166["PR #69166 (open)"]
    end
    P69159 -->|best fix| I69145
    class I69145 open
    class P69159 open
    class P69166 open
    class P69159 best
    class P69159 target
    click I69145 "https://github.com/NousResearch/hermes-agent/issues/69145"
    click P69159 "https://github.com/NousResearch/hermes-agent/pull/69159"
    click P69166 "https://github.com/NousResearch/hermes-agent/pull/69166"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 2 pull requests and 1 issue in this complex. Each diff was read against this issue; Assessment working set: 10 kB of PR diffs, 11 kB of issue/PR text, 3 kB of discussion (4 comments), 4 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

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/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/webhook Webhook / API server sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform 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.

[Bug]: gateway/webhook one-shot sessions still strand background delegations — #66617's stateless-channel fix doesn't cover the gateway runner

4 participants