Environment: Autolith 0.45.0 release binary (x86_64-openbsd); OpenAI-compatible provider (Synthetic, api.synthetic.new), model syn:large:text.
Summary: When the model emits a function_call whose arguments string is not valid JSON, Autolith persists the PROVIDER-ITEM verbatim. Every subsequent request replays it, and the backend hard-rejects with a non-retryable 400 - Assistant tool call function.arguments must be valid JSON. The conversation is then permanently unusable: every new user message (including automated heartbeats) dies instantly with no assistant output, and kill+resume does NOT help because the poison is on disk.
Observed in production after the model glitched mid-generation while producing a deeply-nested-escaping shell command: it spliced two JSON objects into one arguments string, and also hallucinated the tool name (runt_5_shell__run; real tool is run). Autolith correctly recorded an unknown-tool error output for the call, but the invalid arguments stayed in history.
Evidence (sanitized):
Persisted item (one line of the segment sexp, Lisp-escaped wire JSON):
{"type":"function_call","call_id":"call_581...","name":"runt_5_shell__run","arguments":"{\"command\": \"D=/home/...; grep -ob '\"role\":\"user\"' $D/000000000000{\"command\": \"D=/home/...\"}","namespace":"shell"} - invalid JSON at char 115 (a second {"command": ... literal spliced in mid-string).
Following PROVIDER records:
(:FAILURE (:MESSAGE "... 400 Assistant tool call function.arguments must be valid JSON." :STATUS 400 :RETRYABLE-P NIL))
for the next request AND for every later user message / heartbeat turn.
Root-cause confirmation: we rewrote only that one record's arguments to valid JSON in the segment file (backup taken, sessions restarted) and the conversation immediately recovered - 6/6 subsequent provider requests succeeded, including tool calls. The single malformed item was the poison.
Repro: any OpenAI-compatible provider that validates tool-call arguments. Get the model to emit malformed arguments (heavy quote-escaping in shell commands triggers it), or hand-insert such a function_call item into a conversation segment. Next request -> 400, forever; resuming the same conversation reproduces.
Suggested fixes:
- Validate
arguments parses as JSON at ingest; if not, sanitize on persist and feed a synthetic tool-result error ("arguments were not valid JSON") so the turn continues and history stays replayable.
- On a 400 matching this message, auto-heal (scan replayed history for function_call items with unparseable arguments, repair/quarantine, retry) - or at minimum offer a
/repair command. Today the only recovery is hand-editing sexp files.
- Consider validating the tool name against the registry at ingest as well.
- Minor: surface the last turn error to integrations - our XMPP bridge could only show "(no text output)", which hid a total conversation loss for hours
Environment: Autolith 0.45.0 release binary (x86_64-openbsd); OpenAI-compatible provider (Synthetic, api.synthetic.new), model
syn:large:text.Summary: When the model emits a
function_callwhoseargumentsstring is not valid JSON, Autolith persists the PROVIDER-ITEM verbatim. Every subsequent request replays it, and the backend hard-rejects with a non-retryable400 - Assistant tool call function.arguments must be valid JSON. The conversation is then permanently unusable: every new user message (including automated heartbeats) dies instantly with no assistant output, and kill+resume does NOT help because the poison is on disk.Observed in production after the model glitched mid-generation while producing a deeply-nested-escaping shell command: it spliced two JSON objects into one
argumentsstring, and also hallucinated the tool name (runt_5_shell__run; real tool isrun). Autolith correctly recorded an unknown-tool error output for the call, but the invalidargumentsstayed in history.Evidence (sanitized):
Persisted item (one line of the segment sexp, Lisp-escaped wire JSON):
{"type":"function_call","call_id":"call_581...","name":"runt_5_shell__run","arguments":"{\"command\": \"D=/home/...; grep -ob '\"role\":\"user\"' $D/000000000000{\"command\": \"D=/home/...\"}","namespace":"shell"}- invalid JSON at char 115 (a second{"command": ...literal spliced in mid-string).Following PROVIDER records:
(:FAILURE (:MESSAGE "... 400 Assistant tool call function.arguments must be valid JSON." :STATUS 400 :RETRYABLE-P NIL))for the next request AND for every later user message / heartbeat turn.
Root-cause confirmation: we rewrote only that one record's
argumentsto valid JSON in the segment file (backup taken, sessions restarted) and the conversation immediately recovered - 6/6 subsequent provider requests succeeded, including tool calls. The single malformed item was the poison.Repro: any OpenAI-compatible provider that validates tool-call arguments. Get the model to emit malformed
arguments(heavy quote-escaping in shell commands triggers it), or hand-insert such afunction_callitem into a conversation segment. Next request -> 400, forever; resuming the same conversation reproduces.Suggested fixes:
argumentsparses as JSON at ingest; if not, sanitize on persist and feed a synthetic tool-result error ("arguments were not valid JSON") so the turn continues and history stays replayable./repaircommand. Today the only recovery is hand-editing sexp files.