fix(background_process): do not consume the delivery budget on transient wake-up refusals

start_session_turn() answers 409 retryable=True while the Agent runtime is
stale, 409 process_wakeup_paused while wake-ups are paused, and 409 with
active_stream_id while the session is busy with another turn. The bridge
released the durable claim as a plain failure each time, so a burst of
refusals within seconds terminally dropped a completion whose origin session
was alive and waiting.

release_async_delegation_delivery(..., retryable=True) now forwards the
refund to an Agent core whose release_event_delivery() accepts the
`retryable` keyword (NousResearch/hermes-agent#120028), detected through
inspect.signature, and falls back to the historical plain release on an
older core. Hard rejections keep the bounded budget.
This commit is contained in:
Anthony Ruiz committed 2026-09-23 08:32:08 +00:00
1 parent b1774b45a9
commit 9b6851eaf1
3 files changed
+228 -3

No files matched your search

+36 -1
View File
@@ -1020,6 +1020,29 @@ def _record_async_delegation_accepted(
)
def _wakeup_refusal_is_transient(resp: dict | None, status: int) -> bool:
"""True when the target refused the wake-up for a reason it will recover from.
Such refusals are not delivery failures of the completion itself, so they
must not consume its bounded durable delivery budget. Otherwise a burst of
refusals (e.g. ``agent_runtime_stale`` answers while the Agent runtime is
being replaced) terminally drops a completion whose origin session is alive:
* any payload that explicitly says ``retryable: True`` (stale runtime);
* the paused-wakeup contract (``process_wakeup_paused``);
* a session busy with another turn (``active_stream_id`` on a 409).
"""
if not isinstance(resp, dict):
return False
if resp.get("retryable") is True:
return True
if status != 409:
return False
if resp.get("error") == "process_wakeup_paused":
return True
return bool(resp.get("active_stream_id"))
def _start_async_delegation_wakeup_turn(
session_id: str,
wakeup_prompt: str,
@@ -1059,7 +1082,11 @@ def _start_async_delegation_wakeup_turn(
)
return
release_async_delegation_delivery(evt, claim)
transient = _wakeup_refusal_is_transient(resp, status)
if transient:
release_async_delegation_delivery(evt, claim, retryable=True)
else:
release_async_delegation_delivery(evt, claim)
_retry_unclaimed_async_delegation_event(
process_registry, evt, keep_legacy_retrying=True
)
@@ -1068,6 +1095,14 @@ def _start_async_delegation_wakeup_turn(
"async delegation wakeup paused for session %s; delivery remains retryable",
session_id,
)
elif transient:
logger.info(
"async delegation wakeup refused transiently for session %s "
"(status=%s err=%r); attempt refunded, durable retry scheduled",
session_id,
status,
(resp or {}).get("error"),
)
else:
logger.debug(
"async delegation wakeup not accepted for session %s: "