λ-Tool is a target language for LLM code generation. Instead of generating Python for PTC, you generate λ-Tool code. The type checker then verifies your code is safe BEFORE execution.
You are the intended user. λ-Tool is designed for LLMs to generate, not humans to write.
In Anthropic's PTC, you write Python like this:
# From PTC docs: batch processing with loops
regions = ["West", "East", "Central", "North", "South"]
results = {}
for region in regions:
data = await query_database(f"<sql for {region}>")
results[region] = sum(row["revenue"] for row in data)This has runtime failure modes:
| Problem | What Happens |
|---|---|
row["revenue"] but field is total_revenue |
KeyError at runtime |
query_database fails |
Unhandled exception crashes program |
| Retry a write after partial failure | Data corruption (double-write) |
while True: with bad exit condition |
Infinite loop, timeout |
λ-Tool makes these compile-time type errors instead.
tool query_database: String -{Read}-> {revenue: Int, region: String};
tool send_email: {to: String, body: String} -{Write}-> Unit;
tool fetch_logs: String -{Network}-> List String;
Format: tool <name>: <InputType> -{Effects}-> <OutputType>;
Effects: Read, Write, Network, Pure
Unit, Bool, Int, String // primitives
{field1: T1, field2: T2} // record (row type)
[T] // list
Result<Ok, Err> // fallible result
!T // linear (use exactly once)
T1 -{Effects}-> T2 // function
// line comment
(* block comment *)
IMPORTANT: Comments use // or (* *). Do NOT use -- for comments.
// Records
{name = "Alice", age = 30}
record.field
// Functions
fn x: Type => body
f arg
// Let bindings
let x = expr in body
// Tool execution (ALWAYS returns Result)
exec tool <name> <arg> // for Unit-typed tools, pass (): exec tool foo ()
// Result handling (MANDATORY — Ok branch MUST come before Err branch)
match result {
Ok(x) => ...,
Err(e) => ...
}
// WRONG order (will not parse):
// match result { Err(e) => ..., Ok(x) => ... }
// Iteration (NO while, NO recursion)
map f list
fold f init list
filter pred list
traverse f list // effectful map, short-circuits on error
Python:
regions = ["West", "East", "Central"]
results = {}
for region in regions:
data = await query_database(f"SELECT revenue FROM sales WHERE region='{region}'")
results[region] = sum(row["revenue"] for row in data)λ-Tool:
tool query: String -{Read}-> {revenue: Int};
let regions = ["West", "East", "Central"] in
traverse (fn r: String => exec tool query r) regions
traversereplaces theforloop- Row type
{revenue: Int}guarantees field exists - Returns
Result<[{revenue: Int}], String>- error handling is mandatory
Python:
logs = await fetch_logs(server_id)
errors = [log for log in logs if "ERROR" in log]λ-Tool:
tool fetch_logs: String -{Network}-> List String;
match exec tool fetch_logs server_id {
Ok(logs) => filter (fn log: String => true) logs,
Err(e) => []: String
}
Note: []: String creates an empty List String. The type annotation is the element type, not List String.
- Must
matchthe Result - can't just iterate over a potentially-failed call
Python:
endpoints = ["us-east", "eu-west", "apac"]
for endpoint in endpoints:
status = await check_health(endpoint)
if status == "healthy":
print(f"Found healthy endpoint: {endpoint}")
breakλ-Tool:
tool check_health: String -{Network}-> {status: String};
let endpoints = ["us-east", "eu-west", "apac"] in
fold (fn acc: Result<String, String> => fn ep: String =>
match acc {
Ok(found) => Ok(found), // already found, skip
Err(_) =>
match exec tool check_health ep {
Ok(r) => if r.status == "healthy" then Ok(ep) else Err("continue"),
Err(e) => Err(e)
}
}
) (Err "none") endpoints
Python:
file_info = await get_file_info(path)
if file_info["size"] < 10000:
content = await read_full_file(path)
else:
content = await read_file_summary(path)λ-Tool:
tool get_file_info: String -{Read}-> {size: Int};
tool read_full: String -{Read}-> String;
tool read_summary: String -{Read}-> String;
match exec tool get_file_info path {
Ok(info) =>
if info.size < 10000
then exec tool read_full path
else exec tool read_summary path,
Err(e) => Err(e)
}
When you generate valid λ-Tool code, these are guaranteed:
| Guarantee | Mechanism | What It Prevents |
|---|---|---|
| No missing fields | Row types | KeyError / field access on wrong schema |
| No unhandled errors | Mandatory match on Result |
Silent failures, crashes |
| No unsafe retry | Linear types (!WriteToken) |
Double-writes, data corruption |
| Termination | No while, no recursion |
Infinite loops |
let data = exec tool query "..." in
map (fn r => r.name) data.rows // TYPE ERROR: data is Result, not record
match exec tool query "..." {
Ok(data) => map (fn r: {name: String} => r.name) data.rows,
Err(e) => []: String
}
while has_more_pages { // SYNTAX ERROR: no while in λ-Tool
...
}
let page_nums = [1, 2, 3, 4, 5] in // explicit bound
fold (fn acc => fn page => ...) [] page_nums
let token = get_token() in
exec tool write {data = "a", token = token};
exec tool write {data = "b", token = token} // TYPE ERROR: token consumed
let token1 = get_token() in
let token2 = get_token() in
exec tool write {data = "a", token = token1};
exec tool write {data = "b", token = token2}
| Error | Meaning | Fix |
|---|---|---|
MissingField "x" |
Field x not in row type |
Check tool declaration, use correct field name |
TypeMismatch: Result vs {..} |
Used Result without matching | Add match ... { Ok => ..., Err => ... } |
LinearAlreadyUsed "token" |
Reused linear variable | Get a fresh token for each write |
UnboundVariable "x" |
Variable not in scope | Check spelling, ensure let binding exists |
- Declare tools with their input/output types and effects
- Use
traverseinstead offorloops over tool calls - Always
matchResults - you cannot ignore errors - Use
foldwith explicit bounds instead ofwhile - Linear tokens for writes - one token, one write
Generate λ-Tool code. The type checker will verify it's safe.
λ-Tool replaces the Python sandbox in Anthropic's Programmatic Tool Calling. Instead of Claude generating unrestricted Python, it generates λ-Tool code that is type-checked before execution.
from lambda_tool import LambdaTool
lt = LambdaTool()
# Type check only (fast, no execution)
result = lt.typecheck(code)
# result.type = "String", result.effects = ["Read"]
# Type check + execute with real tool implementations
result = lt.run(code, executors={
"query_database": lambda arg: db.execute(arg),
"send_email": lambda arg: email.send(**arg),
})
# result.value = "Alice"In a real agent, the flow is:
- Describe available tools to Claude (via system prompt or tool definitions)
- Ask Claude to generate λ-Tool code (invoke this skill)
- Type check the generated code — reject if unsafe
- Execute with real tool implementations
import anthropic
from lambda_tool import LambdaTool, LambdaToolError
client = anthropic.Anthropic()
lt = LambdaTool()
# Step 1-2: Claude generates λ-Tool code
response = client.messages.create(
model="claude-sonnet-4-6",
messages=[{"role": "user", "content": task}],
# Use the lambda-tool skill or system prompt to guide generation
)
ltool_code = extract_code(response)
# Step 3-4: Type check and execute safely
try:
result = lt.run(ltool_code, executors={
"query": lambda arg: real_db_query(arg),
"send_email": lambda arg: real_email_send(arg),
})
except LambdaToolError as e:
# Type error or runtime error — code was unsafe, reject it
handle_error(e)Anthropic tool definitions map directly to λ-Tool tool declarations:
| Anthropic Tool Definition | λ-Tool Declaration |
|---|---|
{"name": "query", "input_schema": {"type": "object", "properties": {"sql": {"type": "string"}}}} |
tool query: String -{Read}-> {rows: List {id: Int}}; |
The key difference: λ-Tool declarations include the output type, which Anthropic tool definitions don't. This is what enables row type checking — the type checker knows what fields the result will have.
For non-Python hosts, the CLI supports a JSON callback protocol:
lambda_tool --json --interactive program.ltoolWhen a tool is called, the CLI writes to stdout:
{"tool_call": {"tool": "query", "argument": "SELECT * FROM users"}}The host responds on stdin:
{"tool_result": {"ok": {"rows": [{"id": 1, "name": "Alice"}]}}}This enables integration from any language (TypeScript, Go, Rust, etc.).