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/12. 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() end5. 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 visibilitytype- Tool type::native,:llm,:subagentcache- 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 byPtcRunner.SubAgent.Exposure)native_result- Native preview metadata (keyword list ornil). Only valid whenexpose: :bothANDcache: true. SeePtcRunner.SubAgent.Validatorfor the validation contract andPtcRunner.SubAgent.Exposurefor 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 (default20); only consulted forpreview: :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
@type expose_layer() :: :native | :ptc_lisp | :both
Functions
@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 stringformat- 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
:nativeFunction 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