4.1 KiB
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` |
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
ToolCallRecordentries
Usage Example
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}")