fix(delegate): declare stateless channel in one-shot and cron so delegate_task returns results

run_agent._dispatch_delegate_task forces background=True for every top-level
delegation, and async_delivery_supported() returns True for any session that
never binds the capability. On runners that cannot receive a completion after
their turn ends, that combination silently discards every subagent result: the
model gets a dispatch handle, ends its turn, and reports 'waiting for results'.

Two such runners never bind the capability:

* hermes -z (one-shot) prints one final response and exits. It bypasses cli.py,
  so nothing drains process_registry.completion_queue (only the interactive
  process_loop and the gateway watchers do).

* cron run_job clears the HERMES_SESSION_* routing keys, so a completion event
  carries session_key="" — _enrich_async_delegation_routing cannot resolve it
  and _inject_watch_notification drops it ("no routing metadata"). By then
  run_job has already shipped the job's final response via _deliver_result;
  there is no turn left to re-enter. Worse, get_current_session_key() can fall
  back to the ambient os.environ HERMES_SESSION_KEY, so a cron subagent's output
  can be routed into an unrelated user chat rather than merely dropped.

Add declare_stateless_channel() and bind it in both runners, routing
delegate_task to its existing inline/synchronous path — the same fallback the
stateless HTTP adapter already relies on, and the fix suggested in #63142. The
helper binds only the capability: set_session_vars() would also latch
_session_context_engaged, which a pure single-process one-shot must not trigger.

Also correct two agent-facing strings that hardcoded 'stateless HTTP API' as the
only channel without async delivery (delegate_tool, terminal_tool); they now name
the actual condition.

Repro (before): hermes -z 'Use delegate_task to spawn a subagent that replies
BANANA. Report its reply.' -> "Waiting for the subagent's response...", exit 0,
no BANANA. After: BANANA is returned in-turn.

Fixes #53027
Fixes #63142
This commit is contained in:
Clifford Garwood 2026-07-13 12:18:15 -04:00 committed by Teknium
parent c48d53413a
commit 3d9be27895
6 changed files with 168 additions and 11 deletions

View file

@ -2832,10 +2832,11 @@ def delegate_task(
_sync_result = _execute_and_aggregate()
if isinstance(_sync_result, dict):
_sync_result["note"] = (
"background=true is not available on this endpoint (stateless "
"HTTP API — no channel to deliver a detached subagent result "
"after the turn ends), so the subagent(s) ran SYNCHRONOUSLY and "
"the result is included above."
"background=true is not available in this session — it cannot "
"receive a detached subagent result after the turn ends (a "
"one-shot runner such as `hermes -z` or a cron job, or a "
"stateless HTTP endpoint). The subagent(s) ran SYNCHRONOUSLY "
"and the result is included above."
)
return json.dumps(_sync_result, ensure_ascii=False)