PTC-Lisp Playground
# For local dev: run `mix deps.get` in the project root first
repo_root = Path.expand("..", __DIR__)
deps =
if File.exists?(Path.join(repo_root, "mix.exs")) do
[{:ptc_runner, path: repo_root}]
else
[{:ptc_runner, "~> 0.4.0"}]
end
Mix.install(deps, consolidate_protocols: false)
Introduction
PTC-Lisp is a small, safe subset of Clojure designed for Programmatic Tool Calling. Programs run in sandboxed BEAM processes with resource limits (1s timeout, 10MB memory).
Key concepts:
-
->>threads data through a pipeline (like Elixir’s|>) -
:keywordaccesses map fields (converted to string keys internally) -
wherebuilds predicates for filtering -
ctx/tool-namecalls external tools
Basic Example
Filter expenses and sum amounts:
tools = %{
"get-expenses" => fn _args ->
[
%{"category" => "travel", "amount" => 500},
%{"category" => "food", "amount" => 50},
%{"category" => "travel", "amount" => 200}
]
end
}
program = ~S|(->> (ctx/get-expenses) (filter (where :category = "travel")) (sum-by :amount))|
{:ok, step} = PtcRunner.Lisp.run(program, tools: tools)
IO.puts("Travel expenses: #{step.return}")
Step-by-step breakdown
-
(ctx/get-expenses)- calls the tool, returns list of expense maps -
(filter (where :category = "travel"))- keeps only travel expenses -
(sum-by :amount)- sums the amount field
Working with Variables
Use let to bind intermediate results:
program = ~S"""
(let [expenses (ctx/get-expenses)
travel (filter (where :category = "travel") expenses)]
{:count (count travel)
:total (sum-by :amount travel)
:avg (avg-by :amount travel)})
"""
{:ok, step} = PtcRunner.Lisp.run(program, tools: tools)
step.return
Error Handling
PTC-Lisp provides helpful error messages with hints:
# Missing operator in where clause
bad_program = ~S|(filter (where :status "active") items)|
case PtcRunner.Lisp.run(bad_program, tools: %{}) do
{:error, error} -> IO.puts("Error: #{inspect(error)}")
{:ok, step} -> step.return
end
# Type error - sum-by needs a collection
bad_program = ~S|(sum-by :amount "not a list")|
case PtcRunner.Lisp.run(bad_program, tools: %{}) do
{:error, error} -> IO.puts("Error: #{inspect(error)}")
{:ok, step} -> step.return
end
Advanced: Data Transformation
Transform and join data from multiple sources:
tools = %{
"get-users" => fn _args ->
[
%{"id" => 1, "name" => "Alice", "email" => "alice@example.com"},
%{"id" => 2, "name" => "Bob", "email" => "bob@example.com"}
]
end,
"get-orders" => fn _args ->
[
%{"user-id" => 1, "product" => "Laptop", "total" => 1200},
%{"user-id" => 2, "product" => "Mouse", "total" => 25},
%{"user-id" => 1, "product" => "Keyboard", "total" => 150}
]
end
}
program = ~S"""
(let [users (ctx/get-users)
orders (ctx/get-orders)
high-value (filter (where :total > 100) orders)]
(->> high-value
(mapv (fn [order]
(let [user (find (where :id = (:user-id order)) users)]
{:customer (:name user)
:product (:product order)
:total (:total order)})))))
"""
{:ok, step} = PtcRunner.Lisp.run(program, tools: tools)
step.return
Advanced: Grouping and Aggregation
Group expenses by category and compute totals:
expenses_tools = %{
"get-expenses" => fn _args ->
[
%{"category" => "travel", "amount" => 500},
%{"category" => "food", "amount" => 50},
%{"category" => "travel", "amount" => 200},
%{"category" => "food", "amount" => 75}
]
end
}
program = ~S"""
(let [expenses (ctx/get-expenses)
by-category (group-by :category expenses)]
(->> (keys by-category)
(mapv (fn [cat]
{:category cat
:total (sum-by :amount (get by-category cat))
:count (count (get by-category cat))}))))
"""
{:ok, step} = PtcRunner.Lisp.run(program, tools: expenses_tools)
step.return
Learn More
- PTC-Lisp Specification - Complete language reference
- SubAgent Getting Started - Build LLM-powered agents
- LLM Agent Livebook - Interactive agent example