Skip to content

fix(agent): recover per-arg tool-call dialect leaks + short-circuit identical failing calls (#313) - #314

Merged
cboettig merged 1 commit into
mainfrom
fix/harness-dialect-scrub-313
Jul 14, 2026
Merged

fix(agent): recover per-arg tool-call dialect leaks + short-circuit identical failing calls (#313)#314
cboettig merged 1 commit into
mainfrom
fix/harness-dialect-scrub-313

Conversation

@cboettig

Copy link
Copy Markdown
Member

Closes #313.

What

Two model/tool-agnostic harness defenses, motivated by the live ca-30x30 z-ai/glm-5.2 failure this morning (2026-07-14, "what fraction of ca hardwood is protected?").

1. Per-argument dialect scrub (scrubArgDialectLeaks, wired into normalizeToolCallArguments)
GLM emitted a well-formed native add_hex_tile_layer call whose value_stats value was its XML arg dialect leaked in as a string:

"value_stats": "<arg_key>value_stats</arg_key> <arg_value>{...intact JSON...}</arg_value>"

The existing parseEmbeddedToolCalls recovery misses this: it only runs when there are zero native tool calls (agent.js:219), and here the outer arguments blob is valid JSON so JSON.parse succeeds. The scrub unwraps <arg_value>/<parameter …> markers out of string-valued args and re-parses the payload — the data is intact inside the wrapper, so this call now succeeds instead of failing. Runs inside normalizeToolCallArguments so both conversation history and execution see the recovered value.

2. Repeated-failure short-circuit (agent loop)
After the leak, GLM degraded to value_stats: "" and looped the byte-identical failing call ~15× until the blunt localOnlyStreak cap (=maxToolCalls, 15) stopped it. Now: when a round re-issues the identical call(s) that just failed and all fail again, the loop nudges once, then checkpoints — cutting ~15 wasted rounds to ~3.

Scope

Defense-in-depth per docs/design/tool-call-parsing.md. This is the client-side net; the root fix (normalize GLM-5.2's dialect at the proxy) is boettiger-lab/open-llm-proxy#85, and the architecture fix (stop transcribing value_stats through the LLM; fetch by hash) is #276 + boettiger-lab/mcp-data-server#316.

Tests

16 new tests in test/agent-tool-parse.test.js: scrubArgDialectLeaks (incl. the exact GLM leak, <parameter=> scalars, marker-free no-op), _isFailedResult, and three loop-level tests (leak recovered end-to-end so the tool executes with a real object; nudge-then-checkpoint on repeat; no short-circuit when the model recovers after the nudge). Full agent suite green (146 tests); no changes to browser-bound modules.

…dentical failing calls (#313)

Two model/tool-agnostic harness defenses, motivated by a live ca-30x30
z-ai/glm-5.2 failure (2026-07-14): GLM emitted a well-formed native
add_hex_tile_layer call whose value_stats *value* was wrapped in its XML
arg dialect (<arg_key>…</arg_key> <arg_value>{…}</arg_value>) — data intact
inside the wrapper — then looped the identical broken call ~15x until the
localOnlyStreak cap stopped it.

- scrubArgDialectLeaks: unwrap <arg_value>/<parameter …> markers leaked into
  string-valued native tool args, wired into normalizeToolCallArguments so
  both history and execution see the recovered value. The existing
  parseEmbeddedToolCalls net misses this — it only runs when there are zero
  native tool calls, and here the outer arguments JSON is valid.
- Repeated-failure short-circuit: when a round re-issues the byte-identical
  call(s) that just failed and all fail again, nudge once then checkpoint,
  instead of burning the full 15-round local-only cap.

Defense-in-depth; root fix is normalizing the dialect at the proxy
(open-llm-proxy#85), and the hex transcription surface itself is #276.

16 new tests in test/agent-tool-parse.test.js.
@github-actions

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 57.3% 5087 / 8877
🔵 Statements 57.27% 5087 / 8881
🔵 Functions 63.98% 183 / 286
🔵 Branches 83.77% 1131 / 1350
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
app/agent.js 96.42% 86.23% 89.36% 96.42% 87-89, 106-107, 132, 256-258, 265-266, 280-284, 361-366, 375-379, 414-415, 840-841, 912-913, 1091-1093
Generated in workflow #167 for commit 4d8d138 by the Vitest Coverage Report Action

@cboettig
cboettig merged commit c1b3de3 into main Jul 14, 2026
1 check passed
@cboettig
cboettig deleted the fix/harness-dialect-scrub-313 branch July 14, 2026 16:46
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.

Harness: recover per-argument tool-call dialect leaks + short-circuit identical failing calls

1 participant