JOHNFORFAR/ workshops
CAMPUS CLAW · DAVAO

What broke, and why

Tool calling with a local model failed for a day and a half. The cause turned out to be a mistake we made ourselves. Here is the full record, including the ten hypotheses that were wrong, because each one looked plausible and cost real time.

**The answer: a context-window mistake, and it was ours.** Tool calling works on stock IronClaw with a local model. No patch, no shim, no fork.
Search the web for the latest stable Rust compiler version, then cite the source.

-> Rust 1.98.1, released 3 September 2026, with three source URLs.   70 s
   stock IronClaw v1.4.0 + stock qwen3.6:35b-a3b

The failure was caused by a num_ctx 8192 model variant we built earlier to reduce memory pressure. The real payload is larger than that window:

ComponentSize
IronClaw system prompt23,512 chars
27 tool schemas38,415 chars
**Total****~15,500 tokens**

At num_ctx 8192 the request is silently truncated. A model that cannot see its tool definitions describes the call in prose instead of emitting a structured tool_calls field, and IronClaw has nothing to execute.

ModelContextResult
qwen3.6-fast (our build)8,192text fallback
qwen3.6:35b-a3b (stock)262,144**structured, works**

It was length, not content

An earlier draft of this write-up blamed IronClaw's system prompt. That was wrong, and worth recording. Neutral filler of the same length fails identically, and either half of IronClaw's own prompt succeeds when it is short enough:

System promptCharsResult
IronClaw's real prompt23,512text fallback
Neutral filler, same length23,512text fallback
IronClaw's prompt, first 25%5,878structured
IronClaw's prompt, last 25%5,878structured

Nothing about the prompt's wording matters. Only whether the payload fits.

One cause, three different-looking bugs

This is why it took so long to pin down. The same underlying failure presented three ways depending on which model was loaded:

ModelWhat you see
qwen2.5:14bTalks about tool_search, never converges, the loop spins
qwen3.6:35b-a3bEmits tool_call(name="web-access.search", …) as plain text
qwen3.8:27bOllama returns 500: no user query found in messages

All three are the same failure — the model does not produce a structured tool_calls field, so IronClaw has nothing to execute.

Ten hypotheses that were wrong

#HypothesisVerdict
1Model too small, or wrong model✗ 14B and 35B both emit structured calls
2The custom variants broke the chat template✗ template fine — but their **context limit** was the real cause, found later
3Dots or hyphens in tool names✗ all four name forms parse
4Streaming loses tool_calls deltas✗ IronClaw already sends stream=false
5Malformed request payloadhas_user=true, Ollama returns 200
6Too many tools✗ 1 to 41 tools all pass
7The deferred tool_search meta-tool confuses the model✗ passes with it present
8The prompt teaches a text call format✗ no such instruction in it
9Schemas ship empty type, as in the Gemini bug✗ zero empty or null types
10The system prompt's *content* breaks it✗ same-length filler fails too

**Eleven — the payload exceeds num_ctx.** Correct. About 15,500 tokens against an 8,192 window we had set ourselves.

A real inconsistency that was not the cause

IronClaw's system prompt refers to web-access.search with a dot, while the tool schema declares web-access__search with a double underscore. The prompt is also internally inconsistent, using web-access__get_content a few lines away. Rewriting all 23 dotted names in the prompt to the schema form **did not fix anything** — worth reporting as a separate defect, but it is not what breaks tool calling.

What we would report upstream

The two lessons worth teaching

**1 — The context window must fit the payload.** An agent ships its entire tool catalogue on every single turn. Shrinking num_ctx to save memory silently breaks tool calling, and the symptom looks like a model bug or a broken integration.
**2 — Keep exactly one model resident.** Two loaded models exhausted memory and caused 50-second prefill spikes. Those were prompt-cache evictions under memory pressure, not compute. --keepalive and one model beats juggling several.

← Back to the workshop