[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"lviwI2sbbF":3},"\u003Cp align=\"center\">\n  \u003Cimg src=\"assets/mascot.svg\" width=\"160\" alt=\"Lemma, the agent.lean mascot: a small microchip robot with a proof checkmark\">\n\u003C/p>\n\n\u003Ch1 align=\"center\">agent.lean\u003C/h1>\n\n\u003Cp align=\"center\">\n  \u003Cb>A tiny, verifiable agent library in Lean 4 that compiles to C.\u003C/b>\u003Cbr>\n  Write LLM agents with tools in Lean, prove what they may do, and ship them to phones and embedded boards.\n\u003C/p>\n\n\u003Cp align=\"center\">\n  \u003Ca href=\"LICENSE\">\u003Cimg alt=\"License: Apache-2.0\" src=\"https://img.shields.io/badge/license-Apache--2.0-blue.svg\">\u003C/a>\n  \u003Ca href=\"https://github.com/Volland/agent.lean/actions/workflows/ci.yml\">\u003Cimg alt=\"CI\" src=\"https://github.com/Volland/agent.lean/actions/workflows/ci.yml/badge.svg\">\u003C/a>\n  \u003Cimg alt=\"Status: v0.1\" src=\"https://img.shields.io/badge/status-v0.1%20preview-orange.svg\">\n  \u003Cimg alt=\"Lean 4\" src=\"https://img.shields.io/badge/Lean-4-6366f1.svg\">\n\u003C/p>\n\n---\n\n> **Status: v0.1 preview.** The core, codecs, policies, trace, C ABI, Lean driver, bundle, Swift and Kotlin bindings and the llama.cpp adapter are implemented; the guarantees below are proved in Lean or checked by tests. APIs may still change.\n\n## Why\n\nagent.cpp and agent.rs showed that an agent framework can be a small native library instead of a Python runtime. agent.lean takes that idea to Lean 4, which gives two things the others can't:\n\n- **Proofs about agents.** Rules such as *\"`send_payment` only after `confirm_user` in the same turn\"* or *\"at most 3 web fetches per turn\"* are **theorems enforced by construction**, not instructions in a prompt.\n- **Portable C output.** Lean compiles to C, so the same agent runs on a Raspberry Pi, an industrial gateway, an iPhone or an Android phone, with no interpreter and no garbage-collected VM.\n\n## How it works\n\n```\n┌──────────────────────── Lean (pure, proven) ────────────────────────┐\n│  step : State → Event → State × List Effect                         │\n│  codecs (OpenAI-compatible, Anthropic) · tools · policy monitors    │\n└───────────────▲──────────────────────────────────────┬──────────────┘\n         events │  (JSON bytes over the C ABI agent.h) │ effects\n┌───────────────┴──────────────────────────────────────▼──────────────┐\n│  Host: C driver · Lean driver (libcurl) · Swift · Kotlin            │\n│  runs HTTP or the in-process local model, executes host tools,      │\n│  stores the trace, holds the API keys                               │\n└─────────────────────────────────────────────────────────────────────┘\n```\n\n- **Pure core.** The whole agent is one deterministic function from state and event to new state and effects. The core performs no IO, so it can be proved about, replayed exactly and embedded anywhere.\n- **Host does the IO.** Phones use their own networking (URLSession, OkHttp), boards use libcurl. The host only moves bytes; Lean encodes and decodes the provider formats, so every platform behaves the same.\n- **Remote and local models.** OpenAI-compatible endpoints (OpenAI, Ollama, llama-server, vLLM, LM Studio, OpenRouter) and Anthropic Messages. Local models run in-process through a llama.cpp adapter that the core cannot tell apart from a remote API.\n- **Event-sourced.** Every event is appended to a trace. State is a fold over it, resume after the OS kills your app is exact, and every recorded session is a regression test.\n\n## What is guaranteed\n\n| Guarantee | How |\n|---|---|\n| Tools never see malformed input | Arguments are decoded into the tool's Lean type or rejected; JSON Schema is derived from the type |\n| Small local models emit valid tool calls | The derived schema becomes a decoding grammar in the local adapter |\n| Every run terminates within its budgets | Theorem over `step`; sub-agents cannot exceed their parent's remaining budget |\n| Your policies hold | Temporal policies compile to monitors with a soundness theorem; violations are refused before any effect |\n| Prompt injection cannot trigger guarded actions | Tools label output as untrusted; policies can forbid actions after untrusted input unless confirmed |\n| Replay is exact | `resume snapshot rest = replay full trace`, proved |\n| API keys never reach traces | Requests carry a credential *reference*; a `Secret` cannot be serialized |\n| Codecs are correct | Round-trip and chunk-split theorems for the JSON and SSE parsers |\n\n## Quick start\n\n```bash\nlake build                 # the library (Lean 4.35, pinned in lean-toolchain)\nlake test                  # 43 scenario tests, one per spec scenario\nscripts/check_proofs.sh    # no sorry; main theorems use only Lean's standard axioms\n\n# run the example assistant through the Lean driver against any OpenAI-compatible endpoint\nlake build driver_demo\nAGENT_CREDENTIAL_OPENAI=$OPENAI_API_KEY .lake/build/bin/driver_demo \\\n  --endpoint https://api.openai.com/v1/chat/completions --model gpt-4.1-mini \"What is 17 plus 25?\"\n# or a local model: --endpoint http://127.0.0.1:11434/v1/chat/completions --model \u003Collama model>\n\n# the portable source bundle: plain C/C++ + CMake, no Lean needed to build it\npython3 scripts/bundle.py --root Examples --out bundle/out --extra c/agent_abi.c --extra-include c/agent.h\n```\n\nWrite an agent:\n\n```lean\nimport AgentLean\nmeta import AgentLean.Meta     -- deriving handlers and `policy`, compile time only\nopen AgentLean AgentLean.Formula\n\nstructure TransferArgs where\n  to     : String\n  amount : UInt32\n  memo   : Option String\n  deriving ToJson, FromJson, ToolSchema   -- JSON Schema derived from the type\n\ndef transfer : Tool TransferArgs Receipt where\n  name       := \"transfer\"\n  description := \"Send money to a contact\"\n  executor   := .host \"bank.transfer\"     -- implemented by the iOS/Android app\n  idempotent := false                     -- required: never re-issued after a crash\n  sensitive  := true                      -- trace flagged for encryption, hashed on audit export\n  pre x      := x.amount > 0\n\npolicy payLimit := guard \"transfer\" \"amount ≤ 100\" (argNat \"amount\" (· ≤ 100))\n  because \"transfers above 100 need a human\"\npolicy confirmBeforePay := requiresBefore \"transfer\" \"confirm_user\"\npolicy noDelete halt := ¬ₚ call! \"delete_all\"\n\ndef assistant : Agent where\n  name     := \"assistant\"\n  system   := \"You are a careful assistant.\"\n  tools    := #[transfer.erase, confirmUser.erase, Agent.asTool \"researcher\" \"research\" \"Delegate research\"]\n  policies := #[payLimit, confirmBeforePay, noDelete]\n  budget   := { steps := 64, toolCalls := 10, tokens := 100000 }\n\ninitialize registerAgent assistant\n```\n\nSee [Examples/Agents.lean](Examples/Agents.lean) for the full example.\n\nDrive it from C (Swift, Kotlin and the Lean driver wrap the same five calls):\n\n```c\nagent_t *a = agent_new(cfg, cfg_len, NULL);   /* {\"agent\":\"assistant\",\"model\":...,\"endpoint\":...} */\nagent_feed(a, user_event, len);\nagent_buf_t e;\nwhile (agent_next_effect(a, &e)) {\n    /* llm_request → POST e.body to e.url, adding the secret named by e.credential; feed llm_response\n       tool_call   → run the host tool; feed tool_result\n       ui          → update the UI\n       complete    → done */\n    agent_buf_free(e);\n}\nagent_free(a);\n```\n\n## Proved and tested\n\n| Guarantee | Where |\n|---|---|\n| Policy monitors decide exactly what the formula says over the whole trace | `monitor_sound` ([Policy.lean](AgentLean/Policy.lean)) |\n| Every run ends within its step budget; a finished run does nothing | `final_done`, `done_sticky` ([Run.lean](AgentLean/Run.lean)) |\n| A child agent never gets more than its parent has left | `allocate_le` |\n| Resuming from a snapshot gives the same states and effects as replaying everything | `stepAll_append` ([Trace.lean](AgentLean/Trace.lean)) |\n| SSE decoding is independent of how the stream is split | `Sse.feed_chunks` |\n| Requests fit the context estimate; system prompt and last user turn are never dropped | `fit_fits`, `fit_head`, `fit_keeps_last_turn` |\n| Tool arguments decode into the tool's type or are rejected; schema acceptance = decoding | scenario tests |\n| Same behaviour byte for byte on every target and binding | golden traces in [traces/](traces/) |\n\n## Measured (v0.1, Apple M-series / Docker)\n\n| Target | Reference agent, stripped | Notes |\n|---|---|---|\n| macOS arm64 | 0.97 MB | step ≈ 40 µs, codec ≈ 80 MB/s, cold start \u003C 1 ms, ≈ 7 KB per idle agent |\n| Linux aarch64 (static musl) | 1.7 MB | includes musl and libc++; step ≈ 55 µs |\n| Linux x86_64 / armv7 / riscv64 (static musl, emulated) | 1.66 / 1.41 / 1.31 MB | conformance + thread tests pass under QEMU |\n| iOS arm64, Android arm64-v8a / x86_64 | builds | golden traces pass on the Android emulator (CI) and as Mac Catalyst; iOS device not run |\n\nBudgets live in [bench/budgets.json](bench/budgets.json) and [bundle/runtime-surface/](bundle/runtime-surface/); CI fails on regressions.\n\n## Targets\n\n| Class | Targets |\n|---|---|\n| Embedded Linux | aarch64, armv7, riscv64, x86_64 (static, musl) |\n| Mobile | iOS arm64 + simulator (XCFramework, Swift), Android arm64-v8a + x86_64 (AAR, Kotlin/JNI) |\n| Desktop | macOS, Linux |\n| Later | RTOS/MCU via a pure-C mini-runtime (v2 track) |\n\nBindings are hand-written over each platform's native FFI (Swift C interop, JNI, Kotlin/Native cinterop) and pass the same golden-trace conformance suite as the C ABI.\n\n## Layout\n\n| Path | What |\n|---|---|\n| `AgentLean/` | the runtime library (imports only `Init`) |\n| `AgentLean/Meta/` | compile-time deriving handlers and the `policy` command |\n| `AgentLean/Driver.lean`, `c/agent_curl.c` | the Lean-native driver (libcurl, loaded at run time) |\n| `c/agent.h`, `c/agent_abi.c` | the C ABI |\n| `c/llama/` | in-process llama.cpp adapter (grammar-constrained tool calls) |\n| `bindings/swift`, `bindings/kotlin` | Swift (XCFramework/SwiftPM) and Kotlin (JNI/AAR) bindings |\n| `scripts/bundle.py`, `bundle/` | the portable source bundle generator and runtime shim |\n| `traces/` | golden traces every target must reproduce |\n| `Tests/` | scenario tests, golden generator, axiom report |\n\n## Roadmap\n\nv0.1 (this): everything in the [bootstrap change](openspec/specs/). Next: a pure-C mini-runtime implementing the recorded runtime surface (RTOS/MCU targets), optional vendored mimalloc, MCP as an untrusted dynamic tool tier, streaming in the local adapter.\n\n## Design documents\n\n- **Architecture decisions**: [docs/adr/](docs/adr/) (ADRs 0002–0017)\n- **Specs**: [openspec/specs/](openspec/specs/), one per capability: `agent-core`, `tools`, `policies`, `llm-codecs`, `trace-replay`, `host-abi`, `portable-build`\n- **Design**: [design.md](openspec/changes/archive/) of the archived bootstrap change\n- **Typed knowledge graph**: [tg.md/](tg.md/) holds concepts, decisions and constraints as a typed graph, checked with the `tg` CLI\n\n## Out of scope for v1\n\nRuntime-registered or MCP tools, graph/workflow orchestration, token-level control of local inference, long-term memory inside the core (build it as tools), and Python/JS/Rust bindings.\n\n## Licence\n\n[Apache-2.0](LICENSE).\n\n## Meet Lemma\n\nLemma is a small chip that only does what it can prove. Its antenna listens to the model; the check on its chest is the proof.\n",1794686181554]