dba755dcec8f3d9495a5d80a8a6966854b2cd9bb
ROOT CAUSE (round 9; live-repro'd 5/5 this morning, both faces stub-proven by the
prompt-matrix gate). json_get is a first-substring-match scanner (strstr for
'"key":', el_runtime.c). bridge_save serialized the RAW messages array BEFORE the
tool_use_id scalar, so agentic_resume's json_get(blob, 'tool_use_id') returned the
FIRST '"tool_use_id":' occurrence inside the replayed conversation, not the saved
field. The resume guard then preferred that misread over the client's correct
call_id (its two branches both reduced to saved_use_id), attached the tool_result
to the wrong id, and Anthropic 400'd the resume ('unexpected tool_use_id found in
tool_result blocks'), surfaced as {"error":"llm unavailable"}.
ONE MISREAD, TWO FACES — whichever block owns the first tool_use_id in the array:
FACE 1 (search-then-bridge, the Key West killer): the first occurrence is the
first web_search_tool_result's srvtoolu_… id — every agentic turn that ran
server-side web_search and then bridged on a client tool died on approval,
deterministically (messages.2.content.0 … srvtoolu_…). The write itself had
already succeeded; only the resume died.
FACE 2 (multi-cycle missions): with no search, the first occurrence is ROUND 0's
tool_result block — so every LATER approve/resume cycle replayed the round-0
client id (stale-resume-id), killing multi-file missions after ~2 files.
And the shape that PASSES on round 8 confirms the mechanism: a single-cycle
bridge with no prior tool round has no 'tool_use_id' substring in its messages
at all (tool_use blocks carry 'id'), so the scan fell through to the blob's own
field and resumed correctly.
The server_tool_use ↔ web_search_tool_result pairs themselves replay intact — the
defect was a cross-field misread of the blob, the same first-match-scanner class
as BUG-6 (approve 'content' matched inside tool_input, 2026-07-17) and round 8's
citation-block fix.
THE FIX, the pattern not the spot:
1. bridge_save writes every json_safe'd scalar BEFORE both raw fields (an escaped
value cannot contain a bare '"key":' byte pattern, so first-match always lands
on the blob's own fields), and tools_raw (our fixed schema) before messages_raw
(arbitrary conversation), so the raw extractions cannot first-match into
model-controlled bytes either. Field order documented as load-bearing.
2. agentic_resume now honors the client's echoed call_id when present — the value
with clean provenance (minted from pend_tool_id, never blob-round-tripped) —
falling back to the saved id only when the client omits it. Each approve cycle
therefore binds to ITS OWN round's id (kills FACE 2 even against a blob written
by a pre-fix binary), and an omitted call_id still resumes on the saved id,
which the reordered blob now reads correctly.
Pattern sweep: the legacy synthetic blob (sessions.el handle_session_approve) embeds
only json_safe'd fields — no raw hazard, untouched. No other json_get read of any
container that embeds raw conversation JSON before the read field.
PROOF: prompt-matrix gate 24/32 RED on the round-8 brain (fails exactly the two
resume classes, named) -> 32/32 GREEN on this build; live-key Key West tracer
3/3 consecutive full round-trips (bridge -> approve-as-the-app -> real completion,
file on disk), plain-chat and weather-only controls PASS; unpatched round-8 brain
and a same-toolchain unpatched baseline build both still fail the identical
sequence with the identical srvtoolu 400 (the test discriminates, and the only
variable between failing and passing builds is this diff).
Refs neuron#109
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(engine): history keeps its provenance and its session — the false confession and the blank stare
fix(engine): history keeps its provenance and its session — the false confession and the blank stare
fix(engine): history keeps its provenance and its session — the false confession and the blank stare
Description
Neuron - the canonical CGI substrate. Real soul.el lives here.
11 MiB
Languages
Emacs Lisp
73.9%
Python
12.6%
Shell
11.9%
HTML
0.7%
Go Template
0.6%
Other
0.3%