# Actor Runtime — Tool-Calling Loop The **ToolCallingRuntime** provides the execution loop for tool-calling actors. It maps `ToolRegistry` specs to LLM-provider tool schemas, sends prompts to the LLM, routes tool call requests through `ToolCallRouter` / `ToolRunner`, and feeds results back to the LLM until a final response is produced or the iteration limit is reached. ## Architecture ``` ToolCallingRuntime |-- ToolRegistry (tool spec source) |-- ToolRunner (4-stage tool execution) |-- ToolCallRouter (optional; provider format translation) |-- LLMCaller (LLM invocation protocol) +-- ToolActorContext (sandbox, resources, history) ``` ## Loop Semantics ``` 1. Convert ToolRegistry specs -> provider tool schemas 2. Send prompt + tool schemas to LLM via LLMCaller 3. If LLM returns tool calls: a. Route each call through ToolCallRouter -> ToolRunner b. Capture metadata (tool name, inputs, outputs, duration, success) c. Thread sandbox root + resource bindings into tool inputs d. Feed tool results back to LLM e. Increment iteration counter; go to step 2 4. If LLM responds without tool calls: a. Return final response with complete tool call history 5. If max_iterations reached: a. Return partial result with terminated_by_limit=True ``` ## Safety: Max Iterations The `max_iterations` parameter (default **25**) prevents infinite loops when the LLM continuously requests tool calls without producing a final answer. When the limit is reached the runtime returns a `ToolCallRunResult` with `terminated_by_limit=True` and whatever content has been accumulated so far. ## Error Semantics | Error | Condition | Behavior | |---|---|---| | `ValueError` | Empty prompt | Raised immediately | | `TypeError` | Invalid registry/runner type | Raised at construction | | `ValueError` | `max_iterations < 1` | Raised at construction | | Tool not found | LLM calls a tool not in registry | `ToolCallRecord` with `success=False` | | Tool execution error | Handler raises | `ToolCallRecord` with `success=False`, `error` set | | Max iterations | Loop count exceeds limit | Returns `terminated_by_limit=True` | ## Tool Call Metadata Every tool call is recorded as a `ToolCallRecord` containing: | Field | Type | Description | |---|---|---| | `tool_name` | `str` | Name of the tool invoked | | `inputs` | `dict` | Arguments sent to the tool | | `output` | `dict` | Output returned by the tool | | `duration_ms` | `float` | Execution time in milliseconds | | `success` | `bool` | Whether the call succeeded | | `error` | `str | None` | Error message if failed | | `iteration` | `int` | Loop iteration when the call was made | ## Sandbox Threading When a `ToolActorContext` is provided with a `sandbox_root` and/or `resource_bindings`, these values are threaded into tool inputs as default keys (`sandbox_root`, `resource_bindings`) so that tools receive the execution environment context automatically. ## ToolActorContext The `ToolActorContext` carries: - **plan_id** — unique plan identifier - **phase** — current plan phase (e.g. `execute`, `strategize`) - **sandbox_root** — filesystem path to the sandbox directory - **automation_profile** — governing automation profile name - **resource_bindings** — slot-to-resource mappings from the plan - **project_resources** — additional project resource metadata - **tool_call_history** — running list of `ToolCallRecord` entries ## Usage Example ```python from cleveragents.tool import ( ToolCallingRuntime, ToolRegistry, ToolRunner, ToolActorContext, ) registry = ToolRegistry() runner = ToolRunner(registry) # register tools... runtime = ToolCallingRuntime( registry=registry, runner=runner, llm_caller=my_llm_caller, max_iterations=10, ) context = ToolActorContext( plan_id="plan-123", phase="execute", sandbox_root="/tmp/sandbox", ) result = runtime.run_tool_loop( prompt="Implement the feature described in the plan.", context=context, ) print(result.content) print(f"Tool calls: {len(result.tool_call_history)}") print(f"Iterations: {result.iterations}") ```