PtcRunner.Lisp (PtcRunner v0.5.1)
View SourceExecute PTC programs written in Lisp DSL (Clojure subset).
PTC-Lisp enables LLMs to write safe programs that orchestrate tools and transform data. Unlike raw code execution (Python, JavaScript), PTC-Lisp provides safety by design: no filesystem/network access, no unbounded recursion, and deterministic execution in isolated BEAM processes with resource limits.
See the PTC-Lisp Specification for the complete language reference.
Tool Registration
Tools are functions that receive a map of arguments and return results.
Note: tool names use kebab-case in Lisp (e.g., "get-user" not "get_user"):
tools = %{
"get-user" => fn %{"id" => id} -> MyApp.Users.get(id) end,
"search" => fn %{"query" => q} -> MyApp.Search.run(q) end
}
PtcRunner.Lisp.run(~S|(tool/get-user {:id 123})|, tools: tools)Contract:
- Receives:
map()of arguments (may be empty%{}) - Returns: Any Elixir term (maps, lists, primitives)
- Should not raise (return
{:error, reason}for errors)
Summary
Functions
Format an error tuple into a human-readable string.
Useful for displaying errors to users or feeding back to LLMs for retry.
Examples
iex> PtcRunner.Lisp.format_error({:parse_error, "unexpected token"})
"Parse error: unexpected token"
iex> PtcRunner.Lisp.format_error({:eval_error, "undefined variable: x"})
"Eval error: undefined variable: x"
@spec run( String.t(), keyword() ) :: {:ok, PtcRunner.Step.t()} | {:error, PtcRunner.Step.t()}
Run a PTC-Lisp program.
Parameters
source: PTC-Lisp source code as a stringopts: Keyword list of options:context- Initial context map (default: %{}):memory- Initial memory map (default: %{}):tools- Map of tool names to functions (default: %{}):signature- Optional signature string for return value validation:float_precision- Number of decimal places for floats in result (default: nil = full precision):timeout- Timeout in milliseconds (default: 1000):max_heap- Max heap size in words (default: 1_250_000):max_symbols- Max unique symbols/keywords allowed (default: 10_000):max_print_length- Max characters perprintlncall (default: 2000):filter_context- Filter context to only include accessed data keys (default: true)
Return Value
On success, returns:
{:ok, Step.t()}with:step.return: The value returned to the callerstep.memory: Complete memory state after executionstep.usage: Execution metrics (duration_ms, memory_bytes)
On error, returns:
{:error, Step.t()}with:step.fail.reason: Error reason atomstep.fail.message: Human-readable error descriptionstep.memory: Memory state at time of error
Memory Contract
The memory contract is applied only at the top level (via apply_memory_contract/3):
- If result is not a map:
step.return= value, no memory update - If result is a map without
:return: merges map into memory, returns map asstep.return - If result is a map with
:return: merges remaining keys into memory, returns:returnvalue asstep.return
Related modules:
PtcRunner.SubAgent.Loop- Uses this contract to persist memory across turnsPtcRunner.Lisp.Eval- Evaluates programs with user_ns (memory) symbol resolution
Float Precision
When :float_precision is set, all floats in the result are rounded to that many decimal places.
This is useful for LLM-facing applications where excessive precision wastes tokens.
# Full precision (default)
{:ok, step} = PtcRunner.Lisp.run("(/ 10 3)")
step.return
#=> 3.3333333333333335
# Rounded to 2 decimals
{:ok, step} = PtcRunner.Lisp.run("(/ 10 3)", float_precision: 2)
step.return
#=> 3.33Resource Limits
Lisp programs execute with configurable timeout and memory limits:
PtcRunner.Lisp.run(source, timeout: 5000, max_heap: 5_000_000)Exceeding limits returns an error:
{:error, {:timeout, ms}}- execution exceeded timeout{:error, {:memory_exceeded, bytes}}- heap limit exceeded
Context Filtering
By default, PTC-Lisp performs static analysis to identify which data/xxx keys are accessed
by a program, then filters the context to only include those datasets. This significantly
reduces memory pressure when the context contains large datasets that aren't used.
# Only products is loaded into the sandbox, orders/employees are filtered out
ctx = %{"products" => large_list, "orders" => large_list, "employees" => large_list}
PtcRunner.Lisp.run("(count data/products)", context: ctx)Scalar context values (strings, numbers, nil) are always preserved as they typically represent metadata like prompts or configuration.
Disable filtering if you need all context available (e.g., for dynamic data access):
PtcRunner.Lisp.run(source, context: ctx, filter_context: false)See PtcRunner.Lisp.DataKeys for the static analysis implementation.