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.
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-a3bThe 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:
| Component | Size |
|---|---|
| IronClaw system prompt | 23,512 chars |
| 27 tool schemas | 38,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.
| Model | Context | Result |
|---|---|---|
qwen3.6-fast (our build) | 8,192 | text fallback |
qwen3.6:35b-a3b (stock) | 262,144 | **structured, works** |
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 prompt | Chars | Result |
|---|---|---|
| IronClaw's real prompt | 23,512 | text fallback |
| Neutral filler, same length | 23,512 | text fallback |
| IronClaw's prompt, first 25% | 5,878 | structured |
| IronClaw's prompt, last 25% | 5,878 | structured |
Nothing about the prompt's wording matters. Only whether the payload fits.
This is why it took so long to pin down. The same underlying failure presented three ways depending on which model was loaded:
| Model | What you see |
|---|---|
qwen2.5:14b | Talks about tool_search, never converges, the loop spins |
qwen3.6:35b-a3b | Emits tool_call(name="web-access.search", …) as plain text |
qwen3.8:27b | Ollama 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.
| # | Hypothesis | Verdict |
|---|---|---|
| 1 | Model too small, or wrong model | ✗ 14B and 35B both emit structured calls |
| 2 | The custom variants broke the chat template | ✗ template fine — but their **context limit** was the real cause, found later |
| 3 | Dots or hyphens in tool names | ✗ all four name forms parse |
| 4 | Streaming loses tool_calls deltas | ✗ IronClaw already sends stream=false |
| 5 | Malformed request payload | ✗ has_user=true, Ollama returns 200 |
| 6 | Too many tools | ✗ 1 to 41 tools all pass |
| 7 | The deferred tool_search meta-tool confuses the model | ✗ passes with it present |
| 8 | The prompt teaches a text call format | ✗ no such instruction in it |
| 9 | Schemas ship empty type, as in the Gemini bug | ✗ zero empty or null types |
| 10 | The 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.
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.
num_ctx, nothing warns you. A *tool definitions did not fit the context window* error would have saved a day of bisecting.web-access.search while the schema declares web-access__search.ironclaw status prints the wrong port.** It hardcodes the default rather than reading the configured port, so the login link 404s for anyone not on 3000.ironclaw extension activate does not exist**, but the docs tell you to run it.workspace root must not overlap default skill root /skills, which does not explain what to do instead.num_ctx to save memory silently breaks tool calling, and the symptom looks like a model bug or a broken integration.--keepalive and one model beats juggling several.