Compilation logic for SubAgents.
This module provides the compile/2 function that transforms a SubAgent into
a CompiledAgent by running it once with an LLM to derive the PTC-Lisp program.
The resulting CompiledAgent can then be executed many times without further LLM calls.
SubAgentTools Support
Compiled agents can include SubAgentTools. The orchestrator's PTC-Lisp code is deterministic (sequencing tool calls), while SubAgentTools execute their child agents with an LLM at runtime.
When executing a compiled agent with SubAgentTools, pass the LLM at runtime:
{:ok, compiled} = SubAgent.compile(orchestrator, llm: compile_llm)
compiled.execute.(%{topic: "cats"}, llm: runtime_llm)See PtcRunner.SubAgent.compile/2 for the public API.
Summary
Functions
Compiles a SubAgent into a reusable PTC-Lisp function.
Functions
@spec compile( PtcRunner.SubAgent.t(), keyword() ) :: {:ok, PtcRunner.SubAgent.CompiledAgent.t()} | {:error, PtcRunner.Step.t()}
Compiles a SubAgent into a reusable PTC-Lisp function.
The LLM is called once during compilation to derive the logic. The resulting
CompiledAgent can then be executed many times without further LLM calls,
making it efficient for processing many items with deterministic logic.
Requirements
max_turns: 1- Only single-shot agents can be compiledoutput: :ptc_lisp- Only PTC-Lisp output mode (not:json)
Tool Support
- Pure Elixir tools - Supported, executed directly
LLMTool- Supported (requires LLM at runtime)SubAgentTool- Supported if child agent has nomission_timeout
When SubAgentTools are present, the compiled agent requires an llm option
at execute time for the child agents.
Options
llm- Required. LLM callback used once during compilation. Can be a function or atom.llm_registry- Required ifllmis an atom. Maps atoms to LLM callbacks.sample- Optional sample data to help LLM understand the input structure (default: %{})
Returns
{:ok, CompiledAgent.t()}- Successfully compiled agent{:error, Step.t()}- Compilation failed (agent execution failed)
Examples
iex> tools = %{"double" => fn %{"n" => n} -> n * 2 end}
iex> agent = PtcRunner.SubAgent.new(
...> prompt: "Double the input number {{n}}",
...> signature: "(n :int) -> {result :int}",
...> tools: tools,
...> max_turns: 1
...> )
iex> mock_llm = fn _ -> {:ok, ~S|(return {:result (tool/double {:n data/n})})|} end
iex> {:ok, compiled} = PtcRunner.SubAgent.Compiler.compile(agent, llm: mock_llm, sample: %{n: 5})
iex> compiled.signature
"(n :int) -> {result :int}"
iex> is_binary(compiled.source)
true
iex> is_function(compiled.execute, 2)
true
iex> result = compiled.execute.(%{n: 10}, [])
iex> result.return.result
20Agents with LLMTool compile successfully (LLMTool requires LLM at runtime):
iex> alias PtcRunner.SubAgent.LLMTool
iex> tools = %{"classify" => LLMTool.new(prompt: "Classify {{x}}", signature: "(x :string) -> :string")}
iex> agent = PtcRunner.SubAgent.new(prompt: "Process {{item}}", signature: "(item :string) -> {category :string}", tools: tools, max_turns: 1)
iex> {:ok, compiled} = PtcRunner.SubAgent.Compiler.compile(agent, llm: fn _ -> {:ok, ~S|(return {:category "test"})|} end)
iex> compiled.llm_required?
true