Skip to content

fix(oneshot): declare async delivery unsupported so background delegations run synchronously instead of orphaning - #65491

Closed
forgavio-max wants to merge 1 commit into
NousResearch:mainfrom
forgavio-max:fix/oneshot-async-delivery-orphan
Closed

fix(oneshot): declare async delivery unsupported so background delegations run synchronously instead of orphaning#65491
forgavio-max wants to merge 1 commit into
NousResearch:mainfrom
forgavio-max:fix/oneshot-async-delivery-orphan

Conversation

@forgavio-max

Copy link
Copy Markdown

What does this PR do?

Makes hermes -z (oneshot) declare itself unable to deliver async results, so delegate_task background=true batches automatically downgrade to synchronous execution instead of being orphaned.

The bug: a oneshot process exits right after printing its single response. A background delegation dispatched during that turn runs on a daemon thread in the same process, so it dies mid-flight: the async_delegations row is stuck at state: running forever, the work is lost, and the tool's own dispatch note ("its full result re-enters the conversation as a new message") is a promise the channel can never keep. Because top-level delegations are forced to background by the harness (_dispatch_delegate_task intentionally ignores the schema-level background param), the model cannot avoid this by leaving background unset — the orphan is structural in oneshot.

Reproduced live 4× before the fix (orphaned rows deleg_3725e69f, deleg_4d9013c4, deleg_9dd323d8, deleg_a9651f0a on our install); after the fix, the same prompt completes synchronously — subagents run, the consolidated report returns in the same response, zero orphans.

The fix reuses the existing capability gate rather than inventing a new mechanism: _SESSION_ASYNC_DELIVERY / async_delivery_supported() is exactly how the stateless API-server adapter already refuses this promise (issue #10760). This PR adds a standalone setter for callers that aren't full gateway adapters, and binds False in run_oneshot(). delegate_task's existing fallback then handles everything; terminal also correctly refuses notify_on_complete/watch_patterns watchers it could never fire.

Related Issue

Related to #53027, which reports the same event-loss class for cron sessions and proposes exactly this mechanism ("Return False from async_delivery_supported() so delegate_tool.py falls back to synchronous execution"). This PR covers the oneshot path, where the loss is deterministic; the same setter could be reused for the cron path if maintainers want to extend it.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • gateway/session_context.py — new set_async_delivery_supported(bool) standalone setter; async_delivery_supported() docstring updated.
  • hermes_cli/oneshot.pyrun_oneshot() binds the capability to False before the agent turn starts.
  • tools/delegate_tool.py — generalized the sync-fallback comment/note wording from "stateless HTTP API" to "stateless one-turn channel" (the note now accurately covers both the API-server and oneshot cases).
  • tests/hermes_cli/test_oneshot_async_delivery.py — 4 new tests: setter semantics, reset-to-default, and (via a patched _run_agent) that the oneshot turn actually observes async_delivery_supported() == False. Delegate suite passes unchanged.

…tions run synchronously

A `hermes -z` oneshot process exits right after printing its single
response. A delegate_task background=true dispatched during that turn runs
on a daemon thread in the SAME process, so it dies mid-flight and its
async_delegations row is orphaned at state=running forever - the model's
"result will re-enter the conversation" promise can never be kept.
Top-level delegations are forced background by the harness, so the model
cannot avoid this by leaving background unset.

Fix: bind the existing async-delivery capability gate (the one the
stateless API server adapter already uses) to False in run_oneshot() via a
new set_async_delivery_supported() helper. delegate_task then downgrades
background batches to synchronous execution with an explanatory note, and
terminal refuses notify_on_complete watchers it could never fire.

Reproduced live 4x before the fix (orphaned rows deleg_3725e69f,
deleg_4d9013c4, deleg_9dd323d8, deleg_a9651f0a); after the fix the same
prompt completes synchronously with zero orphans.

Related: NousResearch#53027 describes the same event-loss class for cron sessions and
proposes exactly this mechanism; this PR covers the oneshot path.

4 new tests in tests/hermes_cli/test_oneshot_async_delivery.py; delegate
suite passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard tool/delegate Subagent delegation sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 16, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Superseded by #66617 (merged). We went with @cgarwood82's #63866 as the base since it was submitted earliest and covers one-shot + cron + terminal watchers in one change; your one-shot approach (standalone setter + tests) matched it. Thanks @forgavio-max.

@teknium1 teknium1 closed this Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists 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.

3 participants