# The xAI agentic tool loop — server-side tools, client-side functions, state, `max_turns` **Status:** `DOCUMENTED` + `LIVE_VERIFIED` (2026-09-19, grok-4.3: Responses function_call → function_call_output via previous_response_id; server tools web_search / x_search / code_interpreter / mcp / file_search inside single requests; Chat Completions function round trip). **Sources:** https://docs.x.ai/developers/tools/overview · https://docs.x.ai/developers/tools/advanced-usage · https://docs.x.ai/developers/tools/tool-usage-details · https://docs.x.ai/developers/tools/streaming-and-sync · https://docs.x.ai/developers/tools/function-calling **Last verified:** 2026-09-19 ## 1. Who executes what (Responses API) | `output[]` item type | Executed by | You send back | |---|---|---| | `function_call` | you | `{type:"function_call_output", call_id, output}` | | `shell_call` (tool `shell`, env local) | you | `shell_call_output {call_id, output:[{stdout, stderr, outcome}]}` | | `web_search_call`, `custom_tool_call` (x_search sub-tools), `code_interpreter_call`, `file_search_call`, `mcp_call`, `image_generation_call`, `tool_search_call/output` | xAI (server side, inside the same request) | nothing | Stop when a response contains no client-side calls (normally it ends with a `message`). There is **no approval step** for MCP (`require_approval` ignored). ## 2. Loop with `previous_response_id` (verified) ```python resp = client.responses.create(model="grok-4.3", input="…", tools=tools) while True: outs = [{"type":"function_call_output","call_id":i.call_id,"output":run(i)} for i in resp.output if i.type == "function_call"] if not outs: break resp = client.responses.create(model="grok-4.3", tools=tools, input=outs, previous_response_id=resp.id) ``` Server keeps reasoning, server-tool calls and their outputs. Follow-ups may use different tools/params. ## 3. Loop with encrypted state (ZDR / `store:false`) Request `include:["reasoning.encrypted_content"]`, keep an `input_list`, `input_list.extend(resp.output)` then append your `function_call_output` items and resend everything (`store:false` allowed). Verified: replayed reasoning + message items → 200. ## 4. Turns, limits, billing - `max_turns` bounds assistant/server-tool turns **within one request**; a client-side call ends the request, and the follow-up request gets a fresh budget. Default = server cap. Recommended 1–2 quick, 3–5 balanced, 10+ deep. - One turn may run several tools in parallel; `parallel_tool_calls:false` limits to one call. - Billing: only **successful** server-tool executions count (`usage.server_side_tool_usage_details.*_calls`, `num_server_side_tools_used`) — failed attempts (e.g. `file_search_call status:"failed"`) are free; tokens for every intermediate step are billed (`input_tokens` is cumulative across steps, heavily cached: 7 467 input / 704 cached on a one-search request). Prices: web_search $5/1k, x_search $5/1k (→ per-post/profile pricing from 2026-09-21), code_interpreter $5/1k, file_search $2.50/1k, attachment search $10/1k, MCP tokens only. - Rejected-before-generation policy violations on Responses cost $0.05. ## 5. Observability - Streaming recommended: watch `response.output_item.added` for tool items, `response.reasoning_summary_text.delta` for thinking, `response.function_call_arguments.done` for client calls. - Citations: `output_text.annotations[]` (`url_citation`) + inline `[[N]](url)` (disable with `include:["no_inline_citations"]`); collections citations `collections://…`. - xai-sdk: `get_tool_call_type(tool_call)` → `client_side_tool | web_search_tool | x_search_tool | code_execution_tool | collections_search_tool | mcp_tool`; `response.server_side_tool_usage` map (e.g. `{'SERVER_SIDE_TOOL_X_SEARCH': 3}`), `response.tool_calls` (all attempts incl. failed). ## 6. Chat Completions variant Only client functions: `tools:[{type:"function", function:{…}}]` → `choices[0].message.tool_calls` (`finish_reason:"tool_calls"`) → append the assistant message + `{"role":"tool","tool_call_id":id,"content":…}` → resend full history (stateless). Server-side tools are not available there (422 / 410). Shared runnable loop: `examples/xai/tools/function-calling/` (py/ts/sh) and `examples/xai/responses/tool_loop.py`.