lambdagentpaas is grounded in a three-paper formal framework that establishes agent configurations as serialized Lambda calculus expressions. This document summarizes the core theoretical foundations.
Every agent configuration (YAML, JSON, or programmatic) is a serialized Lambda calculus expression. Compiling an agent config with from_config() is desyntactic sugar -- it recovers the underlying Lambda term. Running the agent is beta-reduction.
The LA calculus defines 11 constructs, each with an independent Lambda semantics:
| # | Construct | LA Term | Lambda Correspondence |
|---|---|---|---|
| 1 | Lam | lambda_D . F_{M,D} |
Lambda abstraction -- LLM oracle call |
| 2 | Application | (f x) |
Function application = beta-reduction = autoregressive decoding |
| 3 | Compose | lambda x. g(f(x)) |
Function composition: f >> g |
| 4 | If | IF c t e = lambda c.lambda t.lambda e. c t e |
Church conditional |
| 5 | Loop | Y = lambda f. (lambda x. f(x x)) (lambda x. f(x x)) |
Y combinator with bounded unfolding |
| 6 | Pair | PAIR = lambda a.lambda b.lambda f. f a b |
Church pair -- parallel execution |
| 7 | Fst/Snd | FST = lambda p. p TRUE / SND = lambda p. p FALSE |
Pair projections |
| 8 | Tool | lambda x. oracle(x) |
External function lifted into Lambda world |
| 9 | Route | CASE (classifier x) [(l1, a1), ...] |
Generalized Church boolean -- multi-way dispatch |
| 10 | Guard | `{x : T | P(x)}` |
| 11 | Memory | Gamma' = Gamma union s |
Environment extension -- persistent state injection |
Each agent has a function type tau1 ->^epsilon tau2 (Paper III Definition 3), where:
tau ranges over base types: Str | Int | Bool | Float | Any | Json(S) | tau1 x tau2 | tau1 | tau2Json(S) reuses JSON Schema as the structural type language (Definition 2)Int <: Float), and union introductionThe compiler includes 26 lint rules (L001--L026) organized into three severity levels:
terminate tool = no Y combinator base case)maxSteps > 10 without CEK engine)These rules were validated against 2,225 real-world YAML configs scraped from open-source repositories. Of these, 881 parsed successfully and 835 passed lint -- providing an empirical validation of the static analysis pipeline.
Paper II defines small-step operational semantics via 23 reduction rules, formalized as a labeled transition system:
<C, E, K, sigma, c> --[alpha]--> <C', E', K', sigma', c'>
The rules fall into three categories:
The Agent CEK Machine (cek_machine.py) implements these rules as an abstract machine in the style of Felleisen & Friedman (1986). Machine state is a 5-tuple:
<C, E, K, sigma, c>
C: control -- current term or value
E: environment -- variable bindings dict
K: continuation -- stack of frames (CompK, LoopK, PairLK, PairRK, GuardK, MemK, IfK, RouteK, HaltK)
sigma: store -- memory store dict
c: cost -- cumulative CostVector(tokens, latency_s, money_usd)
Key design choices:
machine.step() with full state inspection at every transitiontau for silent steps, llm(name) for LLM calls, tool(name) for tool calls, mem(key) for store updates)LLM and Tool calls are effectful operations that "yield" control to external oracles. In the CEK machine, these correspond to the C-Lam and C-Tool rules, which:
awaits asynchronously via run_async())This maps naturally to async/await: each C-Lam and C-Tool step is an await point.
The reduction rules satisfy 6 algebraic laws that enable equational reasoning about agent programs:
(f >> g) >> h = f >> (g >> h) -- pipeline order doesn't matter for groupingid >> f = f = f >> id -- identity agent is the unitPair(f, g) produces (f(x), g(x)) regardless of evaluation order when store-independentLoop(body, cond, n)(x) = if cond(x, 0) then x else Loop(body, cond, n-1)(body(x))Guard(Guard(f, P), P) = Guard(f, P, retry=2*retry) -- nesting guards compounds retriesRoute(cls, routes, default) always produces a value when default is providedWhen writes(f) intersection writes(g) = emptyset (store independence), then Pair(f, g) is confluent -- the result is independent of scheduling strategy. This is verified at runtime by check_store_independence() and is the theoretical basis for AsyncPar's safety guarantee.
Paper III defines a type-and-effect system with 15 typing rules:
| Rule | Judgment | Description |
|---|---|---|
| T-Lam | `Gamma | - Lam(name, prompt, model) : Str ->^{llm(m)} Str` |
| T-App | `Gamma | - f : A ->^e B, Gamma |
| T-Compose | f: A ->^e1 B, g: B' ->^e2 C, B <: B' => f >> g : A ->^{e1 . e2} C |
Composition with subtype check |
| T-If | cond: A -> Bool, t: A ->^e1 B, e: A ->^e2 C => If: A ->^{max(e1,e2)} (B \| C) |
Conditional |
| T-Loop | body: A ->^e A => Loop(body, cond, n) : A ->^{e^n} A |
Bounded iteration |
| T-Pair | f: A ->^e1 B, g: A ->^e2 C => Pair(f, g) : A ->^{e1 \|\| e2} (B, C) |
Parallel pair |
| T-Fst | Fst : (A, B) -> A |
Left projection |
| T-Snd | Snd : (A, B) -> B |
Right projection |
| T-Tool | Tool(name, fn) : A ->^io B |
External oracle |
| T-Route | cls: A ->^e1 Label, routes: {l: A ->^ei Bi} => Route: A ->^{e1 . max(ei)} Union(Bi) |
Multi-way dispatch |
| T-Guard | agent: A ->^e B, P: B -> Bool => Guard(agent, P, k): A ->^{e^(1+k)} B |
Refinement with retry |
| T-Memory | agent: A ->^e B, store: S => Memory(agent, store): A ->^{state . e} B |
State injection |
| T-Sub | e : A, A <: B => e : B |
Subsumption |
| T-StrJson | Str <: Json(string) |
String embedding |
| T-NumPromo | Int <: Float |
Numeric promotion |
Effects model the observable side effects of agent execution:
epsilon ::= pure | llm(m) | io | state(s) | epsilon1 . epsilon2 | epsilon1 || epsilon2 | epsilon^n
Three composition operators:
epsilon1 . epsilon2): Compose(f, g) -- execute f's effects then g'sepsilon1 || epsilon2): Pair(f, g) -- effects occur simultaneouslyepsilon^n): Loop(body, n) -- body's effect repeated n timesThe effect lattice defines a partial order: pure <= state <= io <= llm, with pure as the bottom element (Proposition 10: monotonicity -- adding effects only increases the position in the lattice).
Cost is tracked as a graded type via CostVector(tokens, latency_s, money_usd). The cost_grade module provides compile-time cost estimation -- an upper bound on execution cost derived from the agent's structure:
max_tokens * price_per_token(model)sum(stage costs)body_cost * max_stepsmax(f_cost, g_cost) for latency, sum for tokens/moneyinner_cost * (1 + retry)Paper III Section 6 introduces algebraic effect handlers that decouple agent semantics from execution strategy. The EffectHandler interface defines four effect operations:
handle_llm(prompt, input, model, ...) -- how to handle LLM effectshandle_tool(tool_name, fn, input) -- how to handle IO effectshandle_state_read/write(store, key, ...) -- how to handle state effectshandle_cost(tokens, latency, model) -- cost tracking hookThree built-in handlers:
| Handler | LLM | Tool | State | Use Case |
|---|---|---|---|---|
ProductionHandler |
Real API call | Real execution | Real storage | Production |
TestHandler |
Deterministic mock | Mock result | In-memory dict | Testing |
TraceHandler |
Real call + full logging | Real + audit trail | Real + audit | Debugging |
Handler Type Preservation Theorem: If agent : A ->^epsilon B, then for any well-typed handler h, h(agent) : A ->^{epsilon'} B -- the input/output types are preserved, only effects may change.
Gamma |- M : tau and M is not a value, then there exists M' such that M -> M'. No well-typed agent gets "stuck."Gamma |- M : tau and M -> M', then Gamma |- M' : tau. Types are preserved across beta-reduction steps. For stores: if Sigma |- M : tau and M -> M', then Sigma' |- M' : tau where Sigma' supseteq Sigma (stores only grow).Every agent program with finite maxSteps terminates in at most O(maxSteps * pipeline_depth) CEK transitions. This follows from:
remaining by 1 at each iteration|stages| CompK framesretry parametermax_steps limit (default 10,000)Unbounded execution requires an explicit maxSteps: infinity annotation, which the linter flags as a warning.
The cumulative cost vector is monotonically non-decreasing across transitions:
c' >= c (component-wise: tokens' >= tokens, latency' >= latency, money' >= money)
This invariant is checked at every CEK step when check_cost_monotonicity=True. A CostMonotonicityViolation is raised if any component decreases -- indicating a bug in cost accounting, not in the agent program. This property enables reliable cost budgeting: if c has reached the budget threshold at any point, the execution can be safely paused.
The central contribution is a formal, executable mapping:
Agent Configuration <--> Lambda Calculus Expression
YAML/JSON Lambda Term
from_config() deserialization
agent(input) beta-reduction
CEK machine abstract machine
effect handler algebraic effects
lint rules static analysis
type checker type system
cost estimator graded types
This correspondence is not merely an analogy -- it is implemented as 10,000+ lines of Python in the lambdagent library, validated against 835 real-world configs, and supported by formal proofs of type safety, termination, and cost monotonicity.