Skip to content

fix(engine): render local-history reruns as Responses-API input items (analysis-smoke EngineAgentExhaustedError) - #83

Open
inf-quantavius wants to merge 1 commit into
context-labs:mainfrom
inf-quantavius:quant/responses-input-render
Open

fix(engine): render local-history reruns as Responses-API input items (analysis-smoke EngineAgentExhaustedError)#83
inf-quantavius wants to merge 1 commit into
context-labs:mainfrom
inf-quantavius:quant/responses-input-render

Conversation

@inf-quantavius

Copy link
Copy Markdown
Contributor

What

The nightly [Inference] LLM Ops Analysis Smoke (dev) failed on 2026-07-28 (run 30326543437): both fixed-analysis runs (iar_55bgjz7omQ8Oq5BHy4toe task scope, iar_v2ydWpePBNlNqHerYtFXP agent scope) ended status=error stage=halo with:

HALO run failed: EngineAgentExhaustedError: agent root-... exhausted after 10 consecutive failures

Root cause

Dev ClickHouse (default.inferences, 03:40–04:00 UTC window) shows the underlying LLM failures: 29 gpt-5.5 requests to v1/responses rejected with

litellm.BadRequestError: OpenAIException - Missing required parameter: 'input[2].content'

Dissecting a failing request: input[2] is {"role":"assistant","tool_calls":[...]} with no content and input[3] is {"role":"tool","content":...} — chat-completions shapes inside a Responses-API input array.

Mechanism:

  • OpenAiAgentRunner.run rebuilds every attempt's input from local history: [m.model_dump(exclude_none=True) for m in agent_context.to_messages_array()] — chat-completions-shaped AgentMessages.
  • The SDK runs on the Responses API (OpenAIProvider default use_responses=True; all requests land on v1/responses). A healthy first attempt sends only [system, user] (valid), and mid-run turns use the SDK's own Responses items (function_call_output — visible in the 88 successful multi-turn requests in the same window).
  • But every rerun-from-local-history path — mid-stream failure recovery (INF-3504/INF-3308), refusal "Continue." retries, and the final_answer reprompt — re-renders the stored chat shape. Once history contains a tool turn, the assistant tool_calls message has no content → deterministic 400.
  • is_retriable_llm_error deliberately retries non-terminal 400s ("a clean rerun from local history fixes stale server-side state") — but here the local history itself renders invalid, so the rerun can never succeed. 10 identical failures → EngineAgentExhaustedError.

In the failing window all 29 failed requests carried chat-style tool_calls (and no function_call_output); all 88 multi-turn successes carried function_call_output (and no tool_calls). The failure is deterministic once any resend path triggers after a tool turn — which is probabilistic per run, hence the smoke passing 07-26/07-27 and failing 07-28.

Not a monorepo regression — none of the 110 monorepo commits since the last green run touch this path. Prod is affected too: the same error signature appears in prod default.inferences daily since at least 2026-07-17 (11–61 requests/day), i.e. real HALO/analysis runs intermittently dying the same way.

Fix

Add a render boundary, engine/agents/responses_input.py::to_responses_input, used by OpenAiAgentRunner.run in place of the raw model_dump:

  • system/user/assistant text messages → {"role", "content"} message items (None content → "", since message items require content)
  • assistant messages with tool_calls → optional text message item + one {"type": "function_call", "call_id", "name", "arguments"} item per call
  • role="tool" results → {"type": "function_call_output", "call_id", "output"} items

Order is preserved, so the existing context invariant from trim_incomplete_tool_turn (every call answered by its result) carries over to call/output pairing.

Tests

  • New tests/unit/agents/test_responses_input.py: tool-turn pair conversion, text+tool_calls assistant, None-content edges, compacted-summary passthrough.
  • Updated test_runner_retries_refusal_after_tool_result_without_replaying_tool_output to the Responses shapes.
  • Full unit suite: 490 passed; ruff check / ruff format clean.

Provenance

  • Requested by: CI Notifications (Slack U0B5KMSTCKX)
  • Source: Slack thread
  • Quant turn: 95a7ee0d

Rerunning from AgentContext (mid-stream recovery, refusal retries, the
final_answer reprompt) sent chat-completions-shaped messages — assistant
tool_calls arrays and role=tool results — inside a Responses-API input
array. The API rejects the content-less assistant item with 400
'Missing required parameter: input[N].content'; since non-terminal 400s
are deliberately retriable, the same invalid history was resent until
the circuit breaker tripped and the run died with
EngineAgentExhaustedError.

Convert at the render boundary: tool turns become function_call /
function_call_output items, text messages stay message items, and None
content degrades to an empty string.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant