---
name: run_claude_tool_loop
kind: function
lang: py
domain: core
version: "1.0.0"
purity: impure
signature: "def run_claude_tool_loop(messages: list, tools: list, dispatch: dict, model: str = \"claude-opus-4-8\", system: str = \"\", max_tokens: int = 4096, max_iters: int = 8, on_text=None) -> dict"
description: "Bucle agentico de tool-use sobre la API de Anthropic. Llama stream_anthropic_messages en loop: si el modelo responde con tool_use, despacha cada tool via dispatch{name: callable}, anade el tool_result, y continua. Termina cuando stop_reason != tool_use o se alcanza max_iters. Devuelve {messages, final_text, stop_reason, iterations}."
tags: [claude-direct, anthropic, tools, agent, llm, tool-loop, streaming]
uses_functions: [stream_anthropic_messages_py_core, load_claude_oauth_token_py_core]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [json, typing.Callable]
params:
  - name: messages
    desc: "conversacion en formato Anthropic [{role, content}]. Se modifica in-place anadiendo mensajes assistant y tool_result"
  - name: tools
    desc: "lista de tool definitions Anthropic [{name, description, input_schema}]"
  - name: dispatch
    desc: "mapa {tool_name: callable(input_dict) -> result}. El callable puede retornar cualquier valor serializable o lanzar excepcion"
  - name: model
    desc: "ID del modelo Anthropic. Default: claude-opus-4-8"
  - name: system
    desc: "system prompt opcional"
  - name: max_tokens
    desc: "maximo de tokens por llamada API. Default 4096"
  - name: max_iters
    desc: "maximo de iteraciones del loop (proteccion contra loops infinitos). Default 8"
  - name: on_text
    desc: "callback opcional llamado con cada text delta: on_text(delta: str). Util para streaming en tiempo real"
output: "dict con keys: messages (lista actualizada), final_text (texto del ultimo turno assistant), stop_reason (end_turn|tool_use|max_tokens|max_iters|error), iterations (int)"
tested: true
tests:
  - "tool_use seguido de end_turn ejecuta dispatch y termina"
  - "tool dispatch que falla devuelve is_error True en tool_result"
  - "tool desconocido devuelve is_error True"
  - "on_text callback recibe deltas de texto"
  - "max_iters limita el numero de iteraciones"
test_file_path: "python/functions/core/run_claude_tool_loop_test.py"
file_path: "python/functions/core/run_claude_tool_loop.py"
---

## Ejemplo

```python
import sys
sys.path.insert(0, "python/functions/core")
from run_claude_tool_loop import run_claude_tool_loop

# Definir una tool propia
tools = [
    {
        "name": "add",
        "description": "Suma dos numeros enteros y devuelve el resultado.",
        "input_schema": {
            "type": "object",
            "properties": {
                "a": {"type": "number", "description": "primer sumando"},
                "b": {"type": "number", "description": "segundo sumando"},
            },
            "required": ["a", "b"],
        },
    }
]

# Implementacion de la tool (la provee el usuario)
dispatch = {
    "add": lambda inp: inp["a"] + inp["b"],
}

messages = [{"role": "user", "content": "cuanto es 17 + 25?"}]

result = run_claude_tool_loop(
    messages=messages,
    tools=tools,
    dispatch=dispatch,
    model="claude-haiku-4-5-20251001",
    on_text=lambda d: print(d, end="", flush=True),
)

print(f"\n[stop={result['stop_reason']} iters={result['iterations']}]")
# Claude llama a add(17, 25) -> 42, luego responde "El resultado es 42."
```

## Cuando usarla

Cuando quieras que Claude ejecute herramientas Python propias en un bucle
agentico: calculos, consultas a BD, llamadas a APIs externas, lectura de
archivos, etc. El loop gestiona automaticamente la negociacion
tool_use/tool_result con la API. Usa `on_text` para mostrar la respuesta
en tiempo real mientras el modelo piensa entre tool calls.

## Gotchas

- **messages se modifica in-place**: los mensajes assistant y tool_result se
  anaden a la lista pasada. Pasa una copia si necesitas preservar el estado
  original: `run_claude_tool_loop(list(messages), ...)`.
- **dispatch debe cubrir todos los tools declarados**: si el modelo invoca
  una tool que no esta en dispatch, el tool_result contendra `is_error: True`.
  Declara exactamente los tools que dispatch puede manejar.
- **Excepciones en dispatch son capturadas**: si tu callable lanza una
  excepcion, se convierte en tool_result con `is_error: True` y el mensaje
  del error. El loop continua — el modelo puede corregir y reintentar.
- **max_iters como seguro**: sin este limite, un modelo que siempre pide
  tools podria iterar indefinidamente. Default 8 es conservador; subelo
  para agentes complejos con muchos pasos.
- **final_text es solo el ultimo turno**: si el modelo genero texto en
  iteraciones anteriores (raro pero posible), solo se devuelve el del
  ultimo turno. El historial completo esta en `messages`.
