Claude Corner: Pin Tool Choice for Stable Agent Loops

  • Force or forbid tools per turn with tool_choice (auto, any, tool, none).
  • Cap fan-out with disable_parallel_tool_use so each agent step stays deterministic.
  • Flip choice between the tool turn and the final answer turn so loops stop wandering.

Default tool use lets Claude decide whether to call a tool, which tool, and how many in one response. That flexibility is fine for chat. For coding agents and multi-step harnesses it shows up as skipped lookups, surprise parallel calls, or a prose answer when you needed a structured tool_use block.

Why it matters

Agent loops break when a turn returns text instead of a tool call, or returns three tool calls your runner was not ready to execute. The Messages API gives you an explicit control plane: tool_choice. Use {"type":"any"} when any tool must run, {"type":"tool","name":"..."} when one named tool must run, {"type":"none"} when tools must stay off, and leave {"type":"auto"} only when the model should decide. Put disable_parallel_tool_use: true inside that same object to force at most one call (auto) or exactly one call (any / tool). Pin the choice per phase of the loop, not once for the whole conversation.

How to pin tool choice

  1. 1. Keep your tool definitions stable across turns (same names and schemas).
  2. 2. On the action turn, set tool_choice to any or a named tool, and add disable_parallel_tool_use: true if your runner handles one call at a time.
  3. 3. After you append tool_result blocks, either keep auto for more work or switch to none for a prose wrap-up with no further calls.
  4. 4. Expect stop_reason: "tool_use" when a tool was forced. Parse tool_use blocks; do not wait for explanatory text first.
#!/usr/bin/env bash
set -euo pipefail
: "${ANTHROPIC_API_KEY:?Set ANTHROPIC_API_KEY in your environment}"

curl -sS https://api.anthropic.com/v1/messages \
  -H "content-type: application/json" \
  -H "x-api-key: ${ANTHROPIC_API_KEY}" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-opus-5",
    "max_tokens": 512,
    "tools": [
      {
        "name": "search_repo",
        "description": "Keyword search across the indexed tree before answering.",
        "input_schema": {
          "type": "object",
          "properties": { "query": { "type": "string" } },
          "required": ["query"]
        }
      },
      {
        "name": "read_file",
        "description": "Read a repo file by path after search returns candidates.",
        "input_schema": {
          "type": "object",
          "properties": { "path": { "type": "string" } },
          "required": ["path"]
        }
      }
    ],
    "tool_choice": { "type": "tool", "name": "search_repo", "disable_parallel_tool_use": true },
    "messages": [
      { "role": "user", "content": "Where is prompt caching documented in this repo?" }
    ]
  }'

Gotchas

  • Forced any / tool prefills the assistant turn. Claude will not emit natural-language preamble before the tool_use block, even if you ask for one.
  • Manual extended thinking (thinking: {"type":"enabled"}) rejects forced tool use. Use auto or none with that setting. Adaptive thinking on models such as Claude Opus 5 can still force tools.
  • Claude Fable 5.1 and Claude Mythos 5.1 return 400 for any / tool. Stay on auto (optionally with strict: true on tools) or use structured outputs when you need a fixed JSON shape.
  • disable_parallel_tool_use lives inside tool_choice, not as a top-level field. Set it on the request that produces tool_use blocks.
  • Changing tool_choice between cached turns can invalidate cached message blocks (tools and system can still hit). Keep choice stable when you care about cache hits, or accept a miss on phase changes.

Copy this paragraph into ChatGPT, Claude, Gemini, Grok, or whatever you use.

Sources: Define tools · Parallel tool use · Tool use overview

Leave a Comment