Files
freemo c6d831b5ff
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Failing after 16s
CI / quality (pull_request) Successful in 21s
CI / build (pull_request) Successful in 24s
CI / security (pull_request) Successful in 27s
CI / typecheck (pull_request) Successful in 40s
CI / coverage (pull_request) Has been skipped
CI / benchmark-regression (pull_request) Has been skipped
CI / integration_tests (pull_request) Successful in 2m26s
CI / unit_tests (pull_request) Successful in 5m40s
CI / docker (pull_request) Has been skipped
feat(actor): add tool-calling runtime for execution actors
2026-02-22 08:31:45 +00:00

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 ToolCallRecord entries

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