PtcRunner.Tool (PtcRunner v0.11.0)

Copy Markdown View Source

Normalized tool definition for PTC-Lisp and SubAgent.

Tools can be defined in multiple formats and are normalized to this struct. Supports function references, explicit signatures, and introspection.

Tool Type

Tools can be one of three types:

  • :native - Elixir function
  • :llm - LLM-powered tool (SubAgent only)
  • :subagent - SubAgent wrapped as tool (SubAgent only)

Tool Formats

All tool formats are accepted and normalized internally. Common patterns:

1. Function reference (extracts @spec and @doc)

"get_user" => &MyApp.get_user/1

2. Function with explicit signature

"search" => {&MyApp.search/2, "(query :string, limit :int) -> [{id :int}]"}

3. Function with signature and description

"analyze" => {&MyApp.analyze/1,
  signature: "(data :map) -> {score :float}",
  description: "Analyze data and return anomaly score"
}

4. Anonymous function

"get_time" => fn _args -> DateTime.utc_now() end

5. Skip validation explicitly

"dynamic" => {&MyApp.dynamic/1, :skip}

Type Definition

%PtcRunner.Tool{
  name: "get_user",
  function: &MyApp.get_user/1,
  signature: "(id :int) -> {id :int, name :string}",
  description: "Get user by ID",
  type: :native
}

Field Reference

  • name - Tool name as string (required)
  • function - Callable (required for native tools)
  • signature - Optional signature for validation: "(inputs) -> outputs"
  • description - Optional description for LLM visibility
  • type - Tool type: :native, :llm, :subagent
  • cache - Enable result caching by {tool_name, args} (default: false)
  • expose - Exposure layer for combined text/PTC mode: :native | :ptc_lisp | :both (default: nil, resolved per-mode by PtcRunner.SubAgent.Exposure)

  • native_result - Native preview metadata (keyword list or nil). Only valid when expose: :both AND cache: true. See PtcRunner.SubAgent.Validator for the validation contract and PtcRunner.SubAgent.Exposure for resolution semantics.

Result Caching

Set cache: true on tools with stable, pure outputs — identical inputs always produce the same result. Cached results persist across turns within a single SubAgent.run/2 call.

"get-config" => {&MyApp.get_config/1,
  signature: "(key :string) -> :any",
  cache: true
}

Do not use cache: true on tools that read mutable state modifiable by other tools in the session, as the cache has no automatic invalidation.

In pmap, two parallel branches may both miss the cache and execute — last-write-wins on merge. Only successful results are cached; errors are never stored.

Exposure And Native Preview (combined text + PTC mode)

When an agent runs in combined mode (output: :text, ptc_transport: :tool_call), each tool may declare how it surfaces to the LLM:

"search_logs" =>
  {&MyApp.search_logs/1,
   signature: "(query :string) -> [:any]",
   description: "Search log events.",
   expose: :both,
   cache: true,
   native_result: [preview: :metadata]}

Accepted expose: values: :native, :ptc_lisp, :both. Missing/nil is accepted and resolved to a per-mode default by PtcRunner.SubAgent.Exposure.effective_expose/2.

native_result: is a keyword list with optional fields:

  • preview::metadata (default), :rows, or a 1-arity function that receives the tool's full result and returns a JSON-encodable map.
  • limit: — positive integer (default 20); only consulted for preview: :rows.

native_result: is rejected at agent construction unless the tool also has expose: :both AND cache: true. Setting expose: :both, cache: false (without native_result:) is legal — native calls return the actual result and PTC-Lisp calls re-execute the function (no shared cache).

Summary

Functions

Creates a normalized Tool struct from a name and format.

Types

expose_layer()

@type expose_layer() :: :native | :ptc_lisp | :both

t()

@type t() :: %PtcRunner.Tool{
  cache: boolean(),
  description: String.t() | nil,
  expose: expose_layer() | nil,
  function: (map() -> term()) | nil,
  name: String.t(),
  native_result: keyword() | nil,
  signature: String.t() | nil,
  type: :native | :llm | :subagent
}

tool_format()

@type tool_format() ::
  (map() -> term())
  | {(map() -> term()), String.t()}
  | {(map() -> term()), keyword()}
  | {(map() -> term()), :skip}

Functions

new(name, format)

@spec new(String.t(), tool_format()) :: {:ok, t()} | {:error, term()}

Creates a normalized Tool struct from a name and format.

Handles multiple input formats and normalizes to a consistent structure. Attempts to extract @spec and @doc from bare function references.

Parameters

  • name - Tool name as string
  • format - One of: function, {function, signature}, {function, options}, :skip

Returns

{:ok, tool} on success, {:error, reason} on failure.

Examples

Simple function reference (auto-extracts @doc and @spec if available):

iex> {:ok, tool} = PtcRunner.Tool.new("get_time", fn _args -> DateTime.utc_now() end)
iex> tool.name
"get_time"
iex> tool.type
:native

Function with explicit signature:

iex> {:ok, tool} = PtcRunner.Tool.new("search", {fn _args -> [] end, "(query :string, limit :int) -> [{id :int}]"})
iex> tool.signature
"(query :string, limit :int) -> [{id :int}]"

Function with signature and description:

iex> {:ok, tool} = PtcRunner.Tool.new("analyze", {fn _args -> %{} end,
...>   signature: "(data :map) -> {score :float}",
...>   description: "Analyze data and return anomaly score"
...> })
iex> tool.description
"Analyze data and return anomaly score"

Skip validation:

iex> {:ok, tool} = PtcRunner.Tool.new("dynamic", {fn _args -> nil end, :skip})
iex> tool.signature
nil