v0.1.1 alpha · 826 tests passing

Forge AI agents
that prove what
they did.

Cleat is a compiled, statically typed language for auditable, safe AI agents. Agents, guards, tools, streams, state machines, provenance chains, and HTTP servers are compiler-enforced primitives—not library abstractions you hope someone wired up correctly.

826
Tests
15
Stdlib pkgs
29
Examples
8
Primitives
analyze.cleat
1tool analyze(repo: string, pr: int) 2 -> Result[Analysis, string] 3 needs { net } 4 timeout: 30s 5 retry: 2 6{ 7 let url = "https://api/${repo}/pr/${pr}" 8 let resp = http.get(url)? 9 let data = json.decode(resp.body)? 10 return Ok(data) 11} 12 13test "analyze can be mocked" { 14 using analyze = fn(r, p) { 15 return Ok(Analysis { ... }) 16 } 17 assert(analyze().IsOk()) 18}
[ 01 / PRIMITIVES ]

Eight first-class primitives.
Enforced by the compiler.

Most languages bolt orchestration on through frameworks. Cleat bakes it in. agent, guard, tool, stream, state, chain, server, and fn are keywords—and the type-checker has opinions about all of them.

tool

Typed callables, declared effects.

Tools have timeouts, retries, and explicit side-effect declarations. The compiler refuses to compile a tool that lies about what it touches.

tool fetch_data(url: string)
  -> Result[string, string]
  needs { net }
  timeout: 10s
  retry: 1
{ ... }
stream

Backpressured by default.

First-class streaming with typed yields and bounded buffers. No more wrapping callbacks in a Promise wrapped in a generator wrapped in regret.

stream tokens(prompt: string) -> string
  needs { llm }
  buffer: 16
{
  yield "chunk1"
  yield "chunk2"
}
state

Reachability, checked at compile.

Declarative state machines with deadlock and reachability detection. Unreachable states won't compile. Neither will deadlocks.

state PRReview {
  initial: Pending
  Pending -> InReview { when: assigned }
  InReview -> Approved { when: approved && ci_green }
  any -> Cancelled { when: closed }
  terminal: Merged, Cancelled
}
chain

Cryptographic provenance, native.

Append-only, signed records with retention policies. Every action your AI takes can be cryptographically attested without bolting on a separate audit pipeline.

chain AuditTrail {
  signing: ed25519
  @retention(7y)
  record Entry {
    source: string,
    hash: sha256,
    timestamp: time
  }
}
agent

Tool-using LLM loops. Structurally typed.

Agents declare their system prompt, model, tool set, and max turns inline. The compiler wires the tool-calling loop and extracts typed output from the final response — no prompt-engineering a JSON schema.

agent Reviewer {
  model: "claude-sonnet-4-5"
  tools: [read_file, list_files]
  max_turns: 8
  system: "You are a code reviewer."
}
guard

Policy enforcement. Before the call runs.

Guards run as a pre-flight check on tool calls, agent prompts, and HTTP handlers. Return allow, deny, or transform. Composable, testable, unskippable.

guard no_secrets(input: string)
  -> GuardResult
{
  if strings.contains(input, "sk-") {
    return Deny("api key leak")
  }
  return Allow
}
server

Declarative HTTP. Typed handlers.

Route declarations are part of the language. Handler signatures are type-checked against the route pattern. JSON in, JSON out — no framework, no codegen, no surprises.

server API {
  port: 8080
  GET "/users/:id" -> get_user
  POST "/users"    -> create_user
  middleware: [log, auth]
}
fn

Pure functions. Effects by opt-in.

Ordinary functions default to zero effects. Add needs { } to let them touch the world. A pure caller cannot silently invoke an effectful callee.

fn score(review: Review) -> int {
  match review.severity {
    High   -> 3,
    Medium -> 2,
    Low    -> 1,
  }
}
[ 02 / EFFECTS ]

Side effects? Declared.
Or the build fails.

Every function declares what it touches with needs. A pure function calling an effectful one is a compile error—not a runtime surprise found in production at 3am.

Tests use using to swap real effects for mocks. Type-checked, scoped, and impossible to forget to remove.

pure_transform.cleat compile error
fn pure_transform(s: string) -> string {
    http.get(s)
    ^─── missing needs effect: net
    return strings.to_upper(s)
}
fetch.cleat checked ✓
fn fetch(url: string)
  -> Result[string, string]
  needs { net }
{
    let resp = http.get(url)?
    return Ok(resp.body)
}
review.cleat typed LLM output
type Review = struct {
    @description("Safe to merge?")
    approved: bool,
    severity: Severity,
    issues:   []string,
    notes:    string = "",
}

let review: Review =
    llm.generate(model: m, prompt: p)?

// review.approved is bool,
// review.issues is []string —
// guaranteed at compile time.
[ 03 / STRUCTURED OUTPUT ]

Typed LLM output. No JSON jiggery-pokery.

Three lines of Cleat replace forty lines of Python with stronger safety guarantees. The compiler lifts a JSON Schema out of your struct, sends it as a tool constraint, and deserializes the response into a typed value.

Non-serializable fields (functions, channels, streams) fail at compile time. Fields with defaults drop out of the schema's required array. @description annotations steer the model. Agents use the same trick — let r: MyStruct = Agent.run(p)? runs the tool-calling loop, then extracts into MyStruct.

[ 04 / STDLIB ]

Fifteen packages.
No npm required.

Everything you need to build a real backend, in the box. Each package declares its effects so callers know exactly what they're inheriting.

std/json
Encode, decode, pretty-print, validate.
needs ·
std/http
GET, POST, custom requests.
needs · net
std/fs
Read, write, list, exists, mkdir.
needs · fs
std/strings
19 string functions: split, join, trim…
needs ·
std/math
abs, min, max, clamp, floor, sqrt, pow.
needs ·
std/env
Environment variable access.
needs · io
std/log
Leveled logging to stderr.
needs · log
std/os
Process args, exit, getwd, hostname.
needs · io
std/io
Print to stdout.
needs · io
std/crypto
SHA-256 hashing, hex encoding.
needs ·
std/time
Wall-clock + deterministic test clocks.
needs · time
std/llm
Prompt, stream, typed structured output.
needs · llm
std/server
Declarative HTTP with typed handlers.
needs · net
std/supervisor
Agent retry, timeout, tracing, summary.
needs ·
std/memory
Namespaced KV store + context injection.
needs ·
[ 05 / ERGONOMICS ]

Modern syntax.
No ceremony tax.

Pattern matching, pipes, string interpolation, named arguments. The things you actually want, without writing a 12-line type signature first.

01

Pattern matching

Exhaustive, with bindings. The compiler tells you what you forgot.

match expr { Ok(v) => v, Err(e) => 0, _ => -1 }
02

Pipe operators

Read left-to-right like the data flows. Compose without nesting hell.

xs |> filter(x > 0) |> map(x * 2) |> sum()
03

Result & ?

Errors are values. The question mark propagates them, no try/catch.

let data = risky_call()?
04

String interpolation

Templates with full expression support. No printf. No backticks.

"hello, ${name}! found ${items.len()} items"
05

Built-in test runner

Tests live next to code. cleat test just works.

test "low score is Low risk" { assert(...) }
06

Named & default args

Call sites that read like English. Defaults that document themselves.

analyze(repo: "acme", pr: 42)

Start forging.

Cleat compiles to native binaries through Go. One build step, zero runtime dependencies.

$ go build -o cleat ./cmd/cleat
requires go 1.22+ · macOS, linux, windows