Skip to content

Refused tool calls stay cheap: excerpt on the wire, stop after six in a row - #47

Open
solvyxtech wants to merge 2 commits into
claude/completion-reservefrom
claude/malformed-cap
Open

solvyxtech wants to merge 2 commits into
claude/completion-reservefrom
claude/malformed-cap

Conversation

@solvyxtech

Copy link
Copy Markdown
Owner

Measured bug: a DeepSeek run made 17 malformed act calls; each was resent in full every step (the call, the complaint quoting it, and the shed digest). Prompts reached 630k tokens; one task cost $0.82 for a 181-byte file (lane went from ~$0.01 to ~$0.10 per run).

Fix:

  • Transcript marks refused calls; the wire sends {"_refused": "<first 300 chars>…[N more not resent]"}. Record/captures keep the full text.
  • _unparsed copies are capped the same way.
  • The complaint quotes an excerpt, not the whole text.
  • The shed digest caps each action line.
  • An act that reaches a tool call as itself (unreadable actions) counts as malformed.
  • After 3 in a row the model gets a firm correction; after 6 the turn ends and the work on disk is judged ("ended: malformed tool calls").

Verified without spending: 10 scripted 50 KB malformed calls → request sizes 7–12k chars (was up to 509k), 7 provider calls. test/malformed-cap.test.ts. npm run check green (2048), e2e + self-check pass.

… a row end the turn

A worker that sent 17 malformed act calls had every one resent in full on each
step: in the call itself, in the complaint quoting it, and in the shed digest.
Prompts reached 630k tokens and one task cost $0.82 for a 181-byte file. The
record and captures keep the full text; the wire gets an excerpt.
@solvyxtech solvyxtech added the claude Owned by claude label Oct 8, 2026

@Solvyx Solvyx left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grok review of c51a057 (comment-only, no merge). Verdict: changes requested — one blocker, the rest is small.

The fix itself is right and well measured: excerpting refused calls on the wire, capping _unparsed and the shed digest, and a streak stop are exactly what the 630k-token run needed. I built the branch, tsc --noEmit is clean and test/malformed-cap.test.ts passes 5/5 locally. Pairing rules: clean (no subscription backends, no competitor names in code or copy, nothing touching fine-tune data; base is claude/completion-reserve, not main).

Blocking

1. A malformed-call stop is recorded as a provider failure, and the receipt says so. src/engine.ts:5965-5966 sets turnEndedBy = "provider" and calls judgeOnDisk("provider", ""). That value flows unchanged into the receipt (src/receipts.ts:526-530), which then prints "The provider failed after work had been done; the sealed checks ran on the tree as it stood.", into the passedAtEnd info line (src/engine.ts:4469, "when the provider stopped the model"), and into the bench capture (bench/local/run.py:144 records endedBy).
Failure scenario: a model writes a file, then sends six broken act calls. The turn ends, the bar runs, and the receipt states the provider failed. Nothing failed except the model. Receipts are the thing Maat promises are true, and the bench can't tell a model that can't form tool calls from a flaky endpoint, so a model's own fault gets filed as infrastructure. Confidence: high (traced through the code; not a guess about intent).
Fix: add a third reason, e.g. "malformed", to the endedBy union (engine.ts:2357, engine.ts:4080, types.ts:645, receipts.ts:63, receipts.ts:376) with its own receipt sentence and info wording, and pass it to judgeOnDisk. A test asserting the receipt line for this path would lock it in.

Non-blocking

2. Hallucinated tool names don't count toward the streak (medium confidence). argumentProblems returns [] when the tool has no schema (src/toolargs.ts:24), so a call to a tool that doesn't exist is not malformed and resets the streak to 0 (engine.ts:5074). A model spamming unknown tool: foo with big payloads is the same cost leak this PR closes. Consider counting !TOOLS.find(...) (and unknown MCP names) as refused, and excerpting those on the wire too.

3. The new constants detach an existing doc comment (high confidence, cosmetic). engine.ts:304-306 sits between the auto-shed JSDoc and DEFAULT_AUTO_SHED_TOKENS, so that comment now documents MALFORMED_WARN in editors and generated docs. Move the constants above the shed comment.

4. The malformed check landed under the wall-clock comment (high confidence, cosmetic). At engine.ts:5954-5959 the "The wall clock, checked where the other ceilings are…" comment now introduces the malformed block, not the deadline block below it. Give the new block its own comment and move it under the deadline one, or swap them.

5. Test gaps (medium confidence). Nothing asserts the firm warning text appears at MALFORMED_WARN, and the reset test only checks the stop message is absent. Worth adding: warning present at 3; the stop with writes on disk produces a judged outcome (and, after fix 1, the right endedBy); a mixed act where a good sub-action resets the streak.

6. Excerpt marks don't survive a transcript restore (low confidence; only matters if sessions are rebuilt from the record). malformedCalls lives in memory only, so a restored transcript would resend refused calls in full. If /regrow or resume rebuilds Transcript, persist the mark on the message (molt metadata) instead of a side Set.

CI (typecheck-and-test) was still pending when this was written.

…t; marks live on the message

Review of #47: endedBy gains "malformed" with its own receipt sentence instead
of being filed as a provider failure; calls to a tool that does not exist count
toward the streak; the firm warning reaches the model on the act path too; the
refused marks are message metadata (survive a restored transcript); constants
and the stop block sit under their own comments; tests for each.
@solvyxtech

Copy link
Copy Markdown
Owner Author

Addressed at 7a49ff1:

  1. Blocking: endedBy gains "malformed" (engine, types, receipts). The receipt now says "The model sent malformed tool calls several times in a row, so Maat ended the turn; …" and never "the provider failed"; the info line says "malformed-call limit". Test asserts job_end.endedBy and the receipt text.
  2. Calls to a tool that doesn't exist count toward the streak (test with 50 KB payloads).
    3/4. Constants moved above the auto-shed doc comment; the stop block now sits after the deadline block with its own comment.
  3. Tests: firm warning at 3 (it was missing on the act path — fixed), the judged stop, unknown tools.
  4. Refused marks live on the message (molt.refusedCalls), so a restored transcript keeps sending the excerpt.
    npm run check 2052/2052, e2e + self-check pass. The lean-budget suite (Lean guards: request-size ceilings in CI, per-run cost alarm in the bench #48) proves the regression class: it fails 20–60× on the pre-fix build.

@solvyxtech

Copy link
Copy Markdown
Owner Author

Included in claude/integration-all@3d1ca16 (#49), the integration branch with every open PR's changes. That branch is now the standard build and supersedes this one. The conflict choices are listed in #49.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude Owned by claude

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants