Result of executing a PTC program or SubAgent mission.
Returned by both PtcRunner.Lisp.run/2 and PtcRunner.SubAgent.run/2.
Fields
return
The computed result value on success.
Type:
term() | nil- Set when: Mission/program completed successfully
- Nil when: Execution failed (check
failfield)
fail
Error information on failure. See fail/0 for the structure.
Type:
t:fail/0 | nil- Set when: Execution failed
- Nil when: Execution succeeded
memory
Final memory state after execution.
- Type:
map() - Always set: Contains accumulated memory from all operations
- Access in PTC-Lisp: values available as plain symbols
signature
The contract used for validation.
Type:
String.t() | nil- Set when: Signature was provided to
run/2 - Used for: Type propagation when chaining steps
usage
Execution metrics. See usage/0 for available fields.
Type:
t:usage/0 | nil- Set when: Execution completed (success or failure after running)
- Nil when: Early validation failure (before execution)
turns
List of Turn structs capturing each LLM interaction cycle. See PtcRunner.Turn.
Type:
[PtcRunner.Turn.t()] | nil- Set when: SubAgent execution
- Nil when: Lisp execution
trace_id
Unique identifier for this execution (for tracing correlation).
Type:
String.t() | nil- Set when: SubAgent execution (32-character hex string)
- Nil when: Lisp execution
- Used for: Correlating traces in parallel and nested agent executions
parent_trace_id
ID of parent trace for nested agent calls.
Type:
String.t() | nil- Set when: This agent was spawned by another agent
- Nil when: Root-level execution (no parent)
- Used for: Linking child executions to their parent
See PtcRunner.Tracer for trace generation and management.
field_descriptions
Descriptions for signature fields, propagated from SubAgent.
Type:
map() | nil- Set when: SubAgent had
field_descriptionsoption - Nil when: No field descriptions provided
- Used for: Passing field documentation through chained executions
messages
Full conversation history in OpenAI format.
Type:
[t:message/0] | nil- Set when:
collect_messages: trueoption passed toSubAgent.run/2 - Nil when:
collect_messages: false(default) - Used for: Debugging, persistence, and displaying the LLM conversation
summaries
Accumulated step-done summaries from semantic progress reporting.
- Type:
%{String.t() => String.t()} - Default:
%{} - Set when: LLM calls
(step-done "id" "summary")during execution - Used for: Progress checklist rendering when agent has a
plan
Error Reasons
Complete list of error reasons in step.fail.reason:
| Reason | Source | Description |
|---|---|---|
:parse_error | Lisp | Invalid PTC-Lisp syntax |
:analysis_error | Lisp | Semantic error (undefined variable, etc.) |
:eval_error | Lisp | Runtime error (division by zero, etc.) |
:timeout | Both | Execution exceeded time limit |
:memory_exceeded | Both | Process exceeded heap limit |
:validation_error | Both | Input or output doesn't match signature |
:tool_error | SubAgent | Tool raised an exception |
:tool_not_found | SubAgent | Called non-existent tool |
:reserved_tool_name | SubAgent | Attempted to register return or fail |
:max_turns_exceeded | SubAgent | Turn limit reached without termination |
:max_depth_exceeded | SubAgent | Nested agent depth limit exceeded |
:turn_budget_exhausted | SubAgent | Total turn budget exhausted |
:mission_timeout | SubAgent | Total mission duration exceeded |
:llm_error | SubAgent | LLM callback failed after retries |
:llm_required | SubAgent | LLM option is required for agent execution |
:no_code_found | SubAgent | No PTC-Lisp code found in LLM response |
:llm_not_found | SubAgent | LLM atom not in registry |
:llm_registry_required | SubAgent | Atom LLM used without registry |
:invalid_llm | SubAgent | Registry value not a function |
:chained_failure | SubAgent | Chained onto a failed step |
:template_error | SubAgent | Template placeholder missing |
| Custom atoms | SubAgent | From (fail {:reason :custom ...}) |
Usage Patterns
Success Check
case SubAgent.run(prompt, opts) do
{:ok, step} ->
IO.puts("Result: #{inspect(step.return)}")
IO.puts("Took #{step.usage.duration_ms}ms")
{:error, step} ->
IO.puts("Failed: #{step.fail.reason} - #{step.fail.message}")
endChaining Steps
Pass a successful step's return and signature to the next step:
{:ok, step1} = SubAgent.run("Find emails",
signature: "() -> {count :int, _ids [:int]}",
llm: llm
)
# Option 1: Explicit
{:ok, step2} = SubAgent.run("Process emails",
context: step1.return,
context_signature: step1.signature,
llm: llm
)
# Option 2: Auto-extraction (SubAgent only)
{:ok, step2} = SubAgent.run("Process emails",
context: step1, # Extracts return and signature automatically
llm: llm
)Accessing Firewalled Data
Fields prefixed with _ are hidden from LLM history but available in return:
{:ok, step} = SubAgent.run("Find emails",
signature: "() -> {count :int, _email_ids [:int]}",
llm: llm
)
step.return.count #=> 5 (visible to LLM)
step.return._email_ids #=> [101, 102, 103, 104, 105] (hidden from LLM)
Summary
Types
Error information on failure.
A single message in OpenAI format.
Parallel map/calls execution record for tracing.
Tool call information in trace.
Execution metrics.
Functions
Creates a new failed Step.
Creates a failed Step with additional details.
Creates a failed Step with additional details and options.
Creates a new successful Step.
Types
@type fail() :: %{ :reason => atom(), :message => String.t(), optional(:op) => String.t(), optional(:details) => map() }
Error information on failure.
Fields:
reason: Machine-readable error code (atom)message: Human-readable descriptionop: Optional operation/tool that faileddetails: Optional additional context
@type message() :: %{role: :system | :user | :assistant, content: String.t()}
A single message in OpenAI format.
Fields:
role: The message role (:system, :user, or :assistant)content: The message content
@type pmap_call() :: %{ type: :pmap | :pcalls, count: non_neg_integer(), child_trace_ids: [String.t()], child_steps: [any()], timestamp: DateTime.t(), duration_ms: non_neg_integer(), success_count: non_neg_integer(), error_count: non_neg_integer() }
Parallel map/calls execution record for tracing.
Fields:
type::pmapor:pcallscount: Number of parallel taskschild_trace_ids: List of trace IDs from SubAgentTool executionstimestamp: When execution startedduration_ms: Total execution timesuccess_count: Number of successful executionserror_count: Number of failed executions
@type t() :: %PtcRunner.Step{ child_steps: [t()], child_traces: [String.t()], fail: fail() | nil, field_descriptions: map() | nil, journal: map() | nil, memory: map(), messages: [message()] | nil, original_prompt: term(), parent_trace_id: String.t() | nil, pmap_calls: [pmap_call()], prints: [String.t()], prompt: String.t() | nil, return: term() | nil, signature: String.t() | nil, summaries: %{required(String.t()) => String.t()}, tool_cache: map(), tool_calls: [tool_call()], tools: map() | nil, trace_id: String.t() | nil, turns: [PtcRunner.Turn.t()] | nil, usage: usage() | nil }
@type tool_call() :: %{ name: String.t(), args: map(), result: term(), error: String.t() | nil, timestamp: DateTime.t(), duration_ms: non_neg_integer() }
Tool call information in trace.
Fields:
name: Tool nameargs: Arguments passed to toolresult: Tool resulterror: Error message if tool failedtimestamp: When tool was calledduration_ms: How long tool took
@type usage() :: %{ :duration_ms => non_neg_integer(), :memory_bytes => non_neg_integer(), optional(:turns) => pos_integer(), optional(:input_tokens) => non_neg_integer(), optional(:output_tokens) => non_neg_integer(), optional(:total_tokens) => non_neg_integer(), optional(:llm_requests) => non_neg_integer(), optional(:schema_used) => boolean(), optional(:schema_bytes) => non_neg_integer() }
Execution metrics.
Fields:
duration_ms: Total execution timememory_bytes: Peak memory usageturns: Number of LLM turns used (SubAgent only, optional)input_tokens: Total input tokens (SubAgent only, optional)output_tokens: Total output tokens (SubAgent only, optional)total_tokens: Input + output tokens (SubAgent only, optional)llm_requests: Number of LLM API calls (SubAgent only, optional)schema_used: Whether JSON schema was sent to LLM (JSON mode only, optional)schema_bytes: Size of JSON schema in bytes (JSON mode only, optional)
Functions
Creates a new failed Step.
Examples
iex> step = PtcRunner.Step.error(:timeout, "Execution exceeded time limit", %{})
iex> step.fail.reason
:timeout
iex> step.return
nil
Creates a failed Step with additional details.
Examples
iex> PtcRunner.Step.error(:validation_failed, "Invalid input", %{}, %{field: "name"})
%PtcRunner.Step{
return: nil,
fail: %{reason: :validation_failed, message: "Invalid input", details: %{field: "name"}},
memory: %{},
signature: nil,
usage: nil,
turns: nil,
trace_id: nil,
parent_trace_id: nil,
field_descriptions: nil
}
Creates a failed Step with additional details and options.
Options
:journal- journal to preserve on the error step
Examples
iex> step = PtcRunner.Step.error(:timeout, "timed out", %{}, %{}, journal: %{"a" => 1})
iex> step.journal
%{"a" => 1}
Creates a new successful Step.
Examples
iex> step = PtcRunner.Step.ok(%{count: 5}, %{})
iex> step.return
%{count: 5}
iex> step.fail
nil