fix(cron): scope inline calls to reported transport

This commit is contained in:
kshitijk4poor
2026-07-11 10:54:22 +05:30
committed by kshitij
parent 47c91e4c34
commit 02063ece11
3 changed files with 31 additions and 16 deletions

View File

@@ -293,22 +293,18 @@ def _dispatch_nonstreaming_api_request(agent, api_kwargs: dict, *, make_client):
def should_use_direct_api_call(agent) -> bool:
"""True when the LLM call must run inline instead of on the interrupt worker.
"""Whether a cron OpenAI-wire request should skip the interrupt worker.
``interruptible_api_call`` / ``interruptible_streaming_api_call`` run every
request on a spawned daemon worker so the conversation loop can poll for an
interactive interrupt during the blocking HTTP round-trip. Cron jobs execute
their turn inside the gateway's *nested* thread pools (cron-scheduler →
parallel/sequential pool → per-job pool → ``run_conversation``); stacking the
interrupt worker on top of that wedges before the socket even opens on the
2nd+ call of a tool-using turn (#62151), while the identical job runs fine
via ``hermes cron tick`` (foreground, no nested gateway pools). Cron has no
interactive interrupt surface — its only stop signal is the scheduler's
inactivity watchdog, which fires from the outer thread — so running inline
removes the deadlock class without giving anything up. This predicate is the
single extension point for any future non-interactive, nested-pool context.
Issue #62151 is specific to OpenRouter's chat-completions path inside the
gateway cron thread stack. Keep native/Codex/Bedrock/MoA transports on their
established workers: their cancellation and client ownership differ, and
the report provides no evidence that those paths share the pre-HTTP wedge.
"""
return getattr(agent, "platform", None) == "cron"
return (
getattr(agent, "platform", None) == "cron"
and getattr(agent, "api_mode", None) == "chat_completions"
and getattr(agent, "provider", None) != "moa"
)
def direct_api_call(agent, api_kwargs: dict):

View File

@@ -40,12 +40,21 @@ def _make_agent(*, platform="cron"):
return agent
def test_should_use_direct_api_call_only_for_cron_platform():
def test_should_use_direct_api_call_only_for_cron_openai_wire():
assert should_use_direct_api_call(_make_agent(platform="cron")) is True
assert should_use_direct_api_call(_make_agent(platform="cli")) is False
assert should_use_direct_api_call(_make_agent(platform="telegram")) is False
assert should_use_direct_api_call(_make_agent(platform=None)) is False
for api_mode in ("codex_responses", "anthropic_messages", "bedrock_converse"):
agent = _make_agent(platform="cron")
agent.api_mode = api_mode
assert should_use_direct_api_call(agent) is False
moa = _make_agent(platform="cron")
moa.provider = "moa"
assert should_use_direct_api_call(moa) is False
def test_direct_api_call_runs_inline_and_closes_client():
agent = _make_agent()

View File

@@ -21,6 +21,7 @@ def _make_agent(*, platform="cron"):
agent = MagicMock()
agent.platform = platform
agent.api_mode = "chat_completions"
agent.provider = "openrouter"
agent._interrupt_requested = False
agent._touch_activity = MagicMock()
agent._create_request_openai_client = MagicMock()
@@ -28,12 +29,21 @@ def _make_agent(*, platform="cron"):
return agent
def test_should_use_direct_api_call_only_for_cron_platform():
def test_should_use_direct_api_call_only_for_cron_openai_wire():
assert should_use_direct_api_call(_make_agent(platform="cron")) is True
assert should_use_direct_api_call(_make_agent(platform="cli")) is False
assert should_use_direct_api_call(_make_agent(platform="telegram")) is False
assert should_use_direct_api_call(_make_agent(platform=None)) is False
for api_mode in ("codex_responses", "anthropic_messages", "bedrock_converse"):
agent = _make_agent(platform="cron")
agent.api_mode = api_mode
assert should_use_direct_api_call(agent) is False
moa = _make_agent(platform="cron")
moa.provider = "moa"
assert should_use_direct_api_call(moa) is False
def test_direct_api_call_runs_two_sequential_requests_on_same_thread():
"""Mirror the 2nd+ call failure mode: two back-to-back completions.create."""