Direct mode still batch-aborts on raw malformed tool argument JSON #304

Closed
opened 2026-09-02 17:25:36 +00:00 by nsaspy · 0 comments
Owner

Reproducer

Auto-Dig live run 33433989280 on Prolog-RLM 6957c30c88f9548137a8ff9c3a0720f9bdadb210 reached a real OpenRouter native-direct turn with six parallel read-only mcp.brave.brave_web_search calls.

Five calls contained valid JSON argument objects. One provider call emitted this raw argument string with duplicate keys:

{"count":10,"country":"us","extra_snippets":true,"query":"Greenville NC police woman handcuffs Ring footage phone face unlock","result_filter":["web"],"safesearch":"moderate","search_lang":"en","spellcheck":true,"search_lang":"en","spellcheck":true,"summary":false,"text_decorations":false,"ui_lang":"en-us","units":"metric"}

normalize_arguments/2 correctly classifies that call as malformed_arguments, but normalize_response_calls/2 still invokes native_tool_calls_normalize/2 over the whole provider batch. The batch normalizer uses maplist(normalize_native_call, Inputs, Calls), so this parse-level per-call fault becomes after_call_normalization(error(...)) and aborts every valid sibling before classify_calls/4 from #313 can isolate it.

Observed top-level error:

phase=native_call
kind=malformed_arguments
message=provider native calls were rejected
cause.kind=malformed_arguments
cause.phase=normalize
cause.message=native tool arguments must be one ground JSON object

Expected

The #313 recoverability rule should cover malformed raw provider argument JSON too, not only malformed arguments that survive native-call normalization and fail later schema/context validation.

A malformed call with a recoverable malformed_arguments fault should produce a structured native_call_rejected observation for that original call ID/name while valid read-only siblings continue. Batch-fatal invariants must remain fail-closed: malformed/duplicate call envelope or IDs, duplicate IDs, unsupported call type, effect isolation, and budget violations must still abort before execution.

Regression test

Add a direct partial-batch test whose provider response contains two calls:

  1. valid read-only sibling
  2. well-formed call envelope (id, type=function, function.name) whose function.arguments is a JSON string containing duplicate keys

Assert the valid sibling executes exactly once, the malformed sibling gets a bounded malformed_arguments observation, and the loop can repair/finish on the next model turn.

Also add the reverse ordering case so isolation is order-independent.

## Reproducer Auto-Dig live run `33433989280` on Prolog-RLM `6957c30c88f9548137a8ff9c3a0720f9bdadb210` reached a real OpenRouter native-direct turn with six parallel read-only `mcp.brave.brave_web_search` calls. Five calls contained valid JSON argument objects. One provider call emitted this raw argument string with duplicate keys: ```json {"count":10,"country":"us","extra_snippets":true,"query":"Greenville NC police woman handcuffs Ring footage phone face unlock","result_filter":["web"],"safesearch":"moderate","search_lang":"en","spellcheck":true,"search_lang":"en","spellcheck":true,"summary":false,"text_decorations":false,"ui_lang":"en-us","units":"metric"} ``` `normalize_arguments/2` correctly classifies that call as `malformed_arguments`, but `normalize_response_calls/2` still invokes `native_tool_calls_normalize/2` over the whole provider batch. The batch normalizer uses `maplist(normalize_native_call, Inputs, Calls)`, so this parse-level per-call fault becomes `after_call_normalization(error(...))` and aborts every valid sibling before `classify_calls/4` from #313 can isolate it. Observed top-level error: ```text phase=native_call kind=malformed_arguments message=provider native calls were rejected cause.kind=malformed_arguments cause.phase=normalize cause.message=native tool arguments must be one ground JSON object ``` ## Expected The #313 recoverability rule should cover malformed **raw provider argument JSON** too, not only malformed arguments that survive native-call normalization and fail later schema/context validation. A malformed call with a recoverable `malformed_arguments` fault should produce a structured `native_call_rejected` observation for that original call ID/name while valid read-only siblings continue. Batch-fatal invariants must remain fail-closed: malformed/duplicate call envelope or IDs, duplicate IDs, unsupported call type, effect isolation, and budget violations must still abort before execution. ## Regression test Add a direct partial-batch test whose provider response contains two calls: 1. valid read-only sibling 2. well-formed call envelope (`id`, `type=function`, `function.name`) whose `function.arguments` is a JSON string containing duplicate keys Assert the valid sibling executes exactly once, the malformed sibling gets a bounded `malformed_arguments` observation, and the loop can repair/finish on the next model turn. Also add the reverse ordering case so isolation is order-independent.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nsaspy/prolog-rlm#304
No description provided.