PtcRunner.Lisp (PtcRunner v0.4.1)

View Source

Execute 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|(ctx/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.

Run a PTC-Lisp program.

Functions

format_error(other)

@spec format_error(term()) :: String.t()

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"

run(source, opts \\ [])

@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 string
  • opts: 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)

Return Value

On success, returns:

  • {:ok, Step.t()} with:
    • step.return: The value returned to the caller
    • step.memory: Complete memory state after execution
    • step.usage: Execution metrics (duration_ms, memory_bytes)

On error, returns:

  • {:error, Step.t()} with:
    • step.fail.reason: Error reason atom
    • step.fail.message: Human-readable error description
    • step.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 as step.return
  • If result is a map with :return: merges remaining keys into memory, returns :return value as step.return

Related modules:

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.33

Resource 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