# Formal Theory 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. --- ## Paper I: The Lambda-Agent (LA) Calculus ### Core Insight 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. ### 11 Constructs 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)}` | Dependent type / refinement type with retry | | 11 | **Memory** | `Gamma' = Gamma union s` | Environment extension -- persistent state injection | ### Type System 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 | tau2` - `Json(S)` reuses JSON Schema as the structural type language (Definition 2) - Subtyping (Definition 5) supports width/depth structural subtyping, numeric promotion (`Int <: Float`), and union introduction ### Lint Rules and the 835-Config Evaluation The compiler includes 26 lint rules (L001--L026) organized into three severity levels: - **ERROR (6 rules)**: True defects requiring fix before deployment (e.g., missing `terminate` tool = no Y combinator base case) - **WARN (11 rules)**: Potential issues (e.g., `maxSteps > 10` without CEK engine) - **INFO (9 rules)**: Informational (e.g., detected framework, suggested optimizations) 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: Operational Semantics ### 23 Reduction Rules Paper II defines small-step operational semantics via 23 reduction rules, formalized as a labeled transition system: ``` --[alpha]--> ``` The rules fall into three categories: 1. **Dispatch rules** (C-Lam, C-Tool, C-Comp, C-If, C-Route, C-Loop, C-Pair, C-Guard, C-Mem, C-Generic): examine the control component and set up the continuation stack. 2. **Return rules** (C-CompRet, C-LoopRet, C-PairMid, C-PairRet, C-GuardOK, C-GuardRetry, C-GuardFail, C-MemRet, C-IfRet, C-RouteRet, C-Halt): pop the continuation stack and deliver values. 3. **Special rules** (C-LoopBase, C-LoopBound, C-Fst, C-Snd): handle loop termination and projections. ### CEK Machine 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: 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: - **Step-by-step execution** via `machine.step()` with full state inspection at every transition - **Observable labels** (`tau` for silent steps, `llm(name)` for LLM calls, `tool(name)` for tool calls, `mem(key)` for store updates) - **Transition trace** records every step with rule name, label, cost delta, and wall-clock duration ### YIELD Mechanism 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: 1. Suspend the machine (conceptually -- in practice, the call blocks synchronously or `await`s asynchronously via `run_async()`) 2. Invoke the oracle (LLM API or Python function) 3. Resume with the oracle's return value as the new control This maps naturally to `async/await`: each C-Lam and C-Tool step is an await point. ### 6 Algebraic Laws The reduction rules satisfy 6 algebraic laws that enable equational reasoning about agent programs: 1. **Associativity of Compose**: `(f >> g) >> h = f >> (g >> h)` -- pipeline order doesn't matter for grouping 2. **Identity of Compose**: `id >> f = f = f >> id` -- identity agent is the unit 3. **Pair symmetry**: `Pair(f, g)` produces `(f(x), g(x))` regardless of evaluation order when store-independent 4. **Loop unfolding**: `Loop(body, cond, n)(x) = if cond(x, 0) then x else Loop(body, cond, n-1)(body(x))` 5. **Guard idempotence**: `Guard(Guard(f, P), P) = Guard(f, P, retry=2*retry)` -- nesting guards compounds retries 6. **Route exhaustiveness**: `Route(cls, routes, default)` always produces a value when default is provided ### Pair Confluence (Proposition 30) When `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: Type and Effect System ### 15 Type Rules 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` | LLM abstraction | | T-App | `Gamma |- f : A ->^e B, Gamma |- x : A => f(x) : B` | Application | | 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 | ### Effect Algebra 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: - **Serial** (`epsilon1 . epsilon2`): Compose(f, g) -- execute f's effects then g's - **Parallel** (`epsilon1 || epsilon2`): Pair(f, g) -- effects occur simultaneously - **Iterate** (`epsilon^n`): Loop(body, n) -- body's effect repeated n times The 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). ### Graded Types 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: - Lam: cost = `max_tokens * price_per_token(model)` - Compose: cost = `sum(stage costs)` - Loop: cost = `body_cost * max_steps` - Pair: cost = `max(f_cost, g_cost)` for latency, `sum` for tokens/money - Guard: cost = `inner_cost * (1 + retry)` ### Algebraic Effect Handlers 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 effects - `handle_tool(tool_name, fn, input)` -- how to handle IO effects - `handle_state_read/write(store, key, ...)` -- how to handle state effects - `handle_cost(tokens, latency, model)` -- cost tracking hook Three 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. --- ## Key Theorems ### Type Safety (Progress + Preservation) - **Progress**: If `Gamma |- M : tau` and M is not a value, then there exists M' such that `M -> M'`. No well-typed agent gets "stuck." - **Preservation**: If `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). ### Bounded Termination (Theorem 5.4) Every agent program with finite `maxSteps` terminates in at most `O(maxSteps * pipeline_depth)` CEK transitions. This follows from: 1. Loop unfolding decreases `remaining` by 1 at each iteration 2. Compose pushes at most `|stages|` CompK frames 3. Guard retries are bounded by the `retry` parameter 4. The CEK machine enforces a global `max_steps` limit (default 10,000) Unbounded execution requires an explicit `maxSteps: infinity` annotation, which the linter flags as a warning. ### Cost Monotonicity (Proposition 23) 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. --- ## Summary: The Lambda-Agent Correspondence 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.