PtcRunner.SubAgent.Loop.ToolNormalizer (PtcRunner v0.9.0)

Copy Markdown View Source

Tool preparation and wrapping for SubAgent execution.

This module normalizes tools from various formats into executable functions and wraps them with telemetry events for observability.

Tool Types

  • SubAgentTool - Wrapped child agents that inherit context and limits
  • Function/1 - Direct tool functions that receive args map
  • Other values - Passed through unchanged

Wrapping Behavior

Tool functions are wrapped to:

  1. Handle return value normalization ({:ok, value}, {:error, reason}, or raw values)
  2. Emit telemetry events on tool start/stop
  3. Inherit runtime context for nested SubAgents

Trace Propagation

When trace_context is present in state, child SubAgentTool executions receive a child trace context with:

  • New unique trace_id for the child's trace file
  • Parent's current span_id as parent_span_id
  • Incremented depth for visualization

Summary

Functions

Build a child trace context from the parent state.

Normalize tools map to convert SubAgentTool instances into executable functions.

Wrap the builtin llm-query tool.

Wrap an LLMTool in a function closure that executes an ephemeral single-shot SubAgent.

Wrap a regular tool function to handle various return formats.

Wrap a SubAgentTool in a function closure that executes the child agent.

Wrap a tool function with telemetry events.

Functions

build_child_trace_context(arg1)

@spec build_child_trace_context(map()) :: {map() | nil, String.t() | nil}

Build a child trace context from the parent state.

Returns {child_trace_context, child_trace_id} or {nil, nil} if tracing is not enabled.

The child trace context includes:

  • trace_id: New unique ID for this child's trace
  • parent_span_id: Current span ID from the parent (via Telemetry)
  • depth: Parent's depth + 1

normalize(tools, state, agent)

@spec normalize(map(), map(), PtcRunner.SubAgent.t()) :: map()

Normalize tools map to convert SubAgentTool instances into executable functions.

Each tool is wrapped with telemetry events and return value normalization.

Parameters

  • tools - Map of tool name to tool definition
  • state - Current loop state (for context inheritance)
  • agent - Parent agent (for telemetry metadata)

Returns

Map of tool names to wrapped executable functions.

wrap_builtin_llm_query(name, state)

@spec wrap_builtin_llm_query(String.t(), map()) :: function()

Wrap the builtin llm-query tool.

Splits args into control keys (:prompt, :signature, :llm, :response_template) and template args (everything else). Constructs an ephemeral %LLMTool{} and delegates to execute_llm_json/4.

wrap_llm_tool(name, tool, state)

@spec wrap_llm_tool(String.t(), PtcRunner.SubAgent.LLMTool.t(), map()) :: function()

Wrap an LLMTool in a function closure that executes an ephemeral single-shot SubAgent.

The wrapped function:

  • Resolves LLM from tool config or inherits from parent
  • Creates a single-shot SubAgent with output: :text
  • Inherits system limits (nesting_depth, remaining_turns, mission_deadline)
  • Creates a child trace file when parent has tracing enabled

wrap_return(name, func)

@spec wrap_return(String.t(), function()) :: function()

Wrap a regular tool function to handle various return formats.

Converts:

  • {:ok, value} -> value
  • {:error, reason} -> raises with error message
  • value -> value (pass-through)

wrap_sub_agent_tool(name, tool, state)

@spec wrap_sub_agent_tool(String.t(), PtcRunner.SubAgent.SubAgentTool.t(), map()) ::
  function()

Wrap a SubAgentTool in a function closure that executes the child agent.

The wrapped function:

  • Resolves LLM in priority order: agent.llm > bound_llm > parent's llm
  • Inherits llm_registry, nesting_depth, remaining_turns, and mission_deadline
  • Creates a child trace file when parent has tracing enabled
  • Returns the child agent's return value or raises on failure

When trace_context is present in state, the wrapped function:

  1. Starts a new TraceLog session for the child (creating a physical trace file)
  2. Returns a special map with __child_trace_id__ so callers can collect trace IDs

wrap_with_telemetry(name, func, agent)

@spec wrap_with_telemetry(String.t(), function(), PtcRunner.SubAgent.t()) ::
  function()

Wrap a tool function with telemetry events.

Emits [:sub_agent, :tool, :start] and [:sub_agent, :tool, :stop] events.