# 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}")
```
