# Tool Dispatch

How one round of tool calls is checked, run, and recorded: the three passes, deferred schemas, which tools run concurrently, and how repeated calls are deduplicated.

## Three-pass tool concurrency

`toolCall.go` splits each round's tool calls into three serial passes; only Pass 2 fans out:

| Pass | Mode | Work |
|---|---|---|
| 1 — pre-flight | Serial | Unwrap `run_tool` into the named tool; refuse a tool called directly that is not in the loaded set (with a hint to fetch its schema with `find_tools` and call `run_tool`); duplicate-call dedupe (`tool\|args` hash); return the schema, and ask for a re-invoke, when a tool is called before its schema was fetched; existence check, so a name with no registered handler never runs (since v1.2.0); confirm gate; JSON-schema validation |
| 2 — execute | Concurrent for `IsConcurrent`-tagged tools, at most 5 at a time (`MAX_CONCURRENT_TOOLS`, v1.0.20) with the rest queued; serial otherwise | `tools.Execute` |
| 3 — commit | Serial | Land `sessionData.Tools` and `ToolHistories`, update the dedupe map, write the result cache, emit `EventToolResult` |

Since v1.2.0 tool schemas are deferred. For every provider except `claude-code`, the request's tools array holds only `find_tools` and `run_tool` (plus client tools); every other tool is listed by name in the turn context, its schema is fetched with `find_tools`, and it is called as `run_tool(name, args)`. `claude-code` gets every tool except `run_tool`, but all of them except `find_tools` and client tools start unmarked, so the first call to each returns its schema instead of running. The per-tool `AlwaysLoad` flag was removed; use `find_tools` to load a schema. Removed in v1.2.0.

Concurrent-tagged built-in tools (18): `read_files`, `find_files`, `find_tools`, `file_history`, `chat_history`, `error_history`, `fetch_page`, `search_web`, `http_request`, `download_file`, `test_tool`, `calculate`, `subagents`, `list_chatbot`, `send_to_chatbot`, `reasoning_guide`, `html_template`, `write_todo` (since v1.0.26; `find_note` left the set when it was removed in v1.0.23). `generate_audio` and `generate_image` do not carry the flag. `api_*` / `script_*` / `ext_*` tools run concurrently only when their own definition declares it. `edit_file`, `run_command`, interactive tools, and MCP tools always run serially — the parallel `run_command_readonly` tool added in v1.0.23 was removed in v1.0.25.

Dedupe is per run, not per session: a repeated `tool|args` pair returns the earlier result instead of executing again, and `edit_file` invalidates the `read_files` entries covering the paths it touched. A separate 30-minute result cache in ToriiDB (`db_0`) covers `fetch_page`, `search_web`, and `http_request` `GET` calls only.
