An AI opinionated ideal language that ignores human-friendliness
Pact is a programming language designed for AI agents, emphasizing machine-readable specifications and constraints over human-friendliness. It's based on S-expressions and features provenance, effect tracking, totality, latency budgets, and dependency graphs. The compiler generates Rust code and includes tools for web scaffolding and YAML spec conversion. While strong for service contracts, it has limitations for algorithmic specifications.
Article intelligence
Key points
- Pact is an S-expression language for AI agents, prioritizing metadata and formal specifications.
- Key features include provenance, effect tracking, totality, and latency budgets.
- It compiles to Rust and offers web scaffolding and YAML-to-Pact conversion.
- Suitable for API service contracts but not for algorithms or data structures.
Why it matters
This matters because pact is an S-expression language for AI agents, prioritizing metadata and formal specifications.
Technical impact
May affect model selection, inference cost, product capability, and evaluation benchmarks.
Notifications You must be signed in to change notification settings
Fork 3
Star 45
BranchesTags
Open more actions menu
Folders and files
NameName
Last commit message
Last commit date
Latest commit
History
8 Commits
8 Commits
doc
doc
examples
examples
src
src
.gitignore
.gitignore
Cargo.lock
Cargo.lock
Cargo.toml
Cargo.toml
README.md
README.md
grammar.ebnf
grammar.ebnf
Repository files navigation
A compiler for Pact — a programming language designed for AI agents, not humans.
Pact inverts the usual ratio: programs are mostly specification, provenance, and constraints with a thin layer of computation. The logic is the easy part. Knowing why something exists, what else is affected, and what guarantees must hold is where the language spends its budget.
What is Pact?
Pact is an S-expression based language where every function carries rich, machine-readable metadata:
(fn get-user-by-id :provenance {req: "SPEC-2024-0042#section-3", test: ["T-101" "T-102" "T-103"]} :effects [db-read http-respond] :total true :latency-budget 50ms :called-by [api-router/handle-request admin-panel/user-detail]
(param id UUID :source http-path-param :validated-at boundary)
(returns (union (ok User :http 200 :serialize :json) (err :not-found {:id id} :http 404) (err :invalid-id {:id id} :http 400)))
(let [validated-id (validate-uuid id)] (match validated-id (err _) (err :invalid-id {:id id}) (ok uuid) (match (query user-store {:id uuid}) (none) (err :not-found {:id uuid}) (some u) (ok u)))))
Key language features:
Provenance — every function and type knows why it exists (spec reference, author, tests)
Effect tracking — functions declare exactly what I/O they perform (reads, writes, sends)
Totality — functions marked :total true must handle all cases exhaustively
Latency budgets — performance constraints are part of the code, not tribal knowledge
Dependency graphs — :called-by makes impact analysis instant
Union return types — every possible outcome is enumerated with HTTP status mappings
Type invariants — constraints like :min-len, :max-len, :format are first-class
Building
cargo build --release
No external dependencies. Just Rust's standard library.
Usage
Generate a .pct file from a YAML spec (human intent → machine format)
pact generate examples/user-service.spec.yaml -o user-service.pct
Compile a Pact file to Rust source code
pact compile examples/user-service.pct -o output/
Compile targeting pact-runtime (produces code that compiles against pact-runtime crate)
pact compile --runtime examples/user-service.pct -o output/
Scaffold an Axum web project from a Pact file
pact scaffold examples/user-service.pct -o ../user-service-web/
Check for errors without generating code
pact check examples/user-service.pct
Parse only (show the concrete syntax tree)
pact parse examples/minimal.pct
Codegen backends
The compiler ships two codegen backends:
Backend Flag Output Use case
v1 (rust.rs) (default) Standalone Rust with trait-based effects Inspection, documentation
v2 (rust_v2.rs) --runtime Rust targeting pact-runtime crate Compilable, runnable applications
The --runtime backend produces Rust that compiles against pact-runtime and can be used directly in applications like pact-web. It handles:
use pact_runtime::prelude::*; imports
#[derive(Serialize, Deserialize)] on structs
Named input structs for map-typed parameters (e.g., CreateUserInput)
Named fields in error enum variants (not inline structs)
Store trait bounds instead of per-store effect traits
Builtin mapping (query → store.query_by_id(), insert! → store.insert(), etc.)
HasId, HasUniqueFields, from_input(), validate_input() implementations
Web Project Scaffolding
The scaffold command generates a complete Axum web project from a .pct file — routes, handlers, HTML templates, and Cargo.toml. This is a one-time generation intended as a starting point that you customize afterward.
1. Scaffold the web project
pact scaffold examples/user-service.pct -o ../user-service-web/
2. Generate the domain code into it
pact compile --runtime examples/user-service.pct -o ../user-service-web/src/generated/
3. Build and run
cd ../user-service-web && cargo run
This produces:
user-service-web/ ├── Cargo.toml # Dependencies (axum, tokio, serde, pact-runtime) └── src/ ├── main.rs # AppState, Router with HTML + JSON API routes ├── handlers.rs # HTML handlers (list, show, create, delete) + JSON API handlers ├── html.rs # Tailwind CSS HTML helpers (page, nav, table, form, alert) └── generated/ └── mod.rs # "pub mod user_service;"
Route inference
Routes are inferred from the AST — no configuration needed:
AST signal Generated route
EffectKind::Reads/Writes on a store GET /{plural} (list), GET /{plural}/new (form), POST /{plural}/{id}/delete
FnDef with UUID :source http-path-param + reads-only GET /{plural}/{id} (show) + GET /api/{plural}/{id}
FnDef with Map :source http-body + writes POST /{plural} (create) + POST /api/{plural}
FieldDef :format :email in forms
FieldDef :min-len, :max-len minlength/maxlength attributes
Variant :http 404 StatusCode::NOT_FOUND in match arms
Generated handler patterns
The generated handlers follow the same patterns as the hand-written pact-web:
List — store.list_all() → HTML table with Tailwind styling
Show — calls domain function → matches Ok/Err variants with correct HTTP status codes
Create (HTML) — Form → calls domain function → redirect on success, error alert on failure
Create (API) — Json → calls domain function → JSON response with status
Delete — store.delete(&uuid) → redirect to list
Spec-to-Pct Generator
The generate command translates human-readable YAML specs (Layer 0 — human intent) into .pct files (Layer 1 — AI-native format) that feed into the compiler pipeline:
Spec (.yaml) → YamlParser → SpecAST → PctEmitter → .pct file → [compiler]
YAML Spec Format
Write requirements in plain English:
spec: SPEC-2024-0042 title: "User service" owner: platform-team domain: User: fields:
- name: required, string, 1-200 chars
- email: required, email format, unique
- id: auto-generated, immutable
endpoints: get-user: description: "Returns a user by ID" input: user id (from URL) outputs:
- success: the user found (200)
- not found: when the ID doesn't exist (404)
constraints:
- max response time: 50ms
- read-only
create-user: description: "Creates a new user" input: user data (from body) outputs:
- created: the new user (201)
- duplicate email: email already exists (409)
- validation failed: invalid input (422)
constraints:
- idempotent by: email
- max response time: 200ms
quality:
- all functions must be total
traceability: known dependencies: api-router, admin-panel
What Gets Mapped
Spec descriptor Generated .pct
required, string, 1-200 chars (field name String :min-len 1 :max-len 200) + invariant
email format, unique (field email String :format :email :unique-within )
auto-generated, immutable (field id UUID :immutable :generated)
read-only constraint effect set db-read [:reads ]
max response time: 50ms :latency-budget 50ms
idempotent by: email :idempotency-key (hash (. input email))
output success (200) (ok Type :http 200 :serialize :json)
output not found (404) (err :not-found {:id id} :http 404)
all functions must be total :total true on every function
The generator also scaffolds function bodies: read endpoints get validate-query-match logic, write endpoints get validate-insert-match logic.
The generated .pct is validated by round-tripping through lexer, parser, and lowerer before writing to disk.
Scope and Limitations
The generator is designed for service contract specifications — CRUD endpoints, API contracts, input validation, error variants. It handles the domain well:
Domain types with field constraints
Read/write endpoints with HTTP status mappings
Effect tracking, latency budgets, idempotency keys
Traceability and provenance metadata
It is not designed for algorithmic specifications (data structures, sorting algorithms, state machines). Those require language features Pact doesn't yet have: generic types, recursive types, trait bounds, and algorithmic body templates.
Compiler Pipeline
The compiler has 6 phases:
Source (.pct) → Lexer → Parser (CST) → Lowering (AST) → Semantic Analysis → Codegen (Rust)
Phase What it does
Lexer Tokenizes source into symbols, keywords, strings, integers, durations, regex literals
Parser Builds a generic S-expression tree (lists, vectors, maps, atoms) — no semantic knowledge
Lowering Converts CST to typed AST (Module, TypeDef, FnDef, Expr, Pattern, etc.)
Semantic analysis Name resolution, effect checking, match exhaustiveness
Codegen Emits Rust source: structs, traits, enums, functions with doc comments
What Gets Generated
v1 backend (default)
Given a Pact module, the compiler produces Rust code with:
Pact construct Rust output
(type User ...) pub struct User with validate() method
(effect-set db-read ...) pub trait DbRead with typed methods
(fn get-user ...) pub fn get_user() with trait-bounded context
(returns (union ...)) pub enum GetUserResult with http_status() and Display
:provenance, :called-by, etc. Doc comments preserving all metadata
:invariants, :min-len, :max-len Validation logic in validate()
v2 backend (--runtime)
The runtime-targeting backend produces code that compiles and runs:
Pact construct Rust output
(type User ...) pub struct User with HasId, HasUniqueFields, validate(), validate_input(), from_input()
(param input {:name String}) pub struct CreateUserInput (named struct)
(effect-set db-read ...) Store trait bound on function
(fn get-user ...) pub fn get_user(store: &impl Store, ...)
(err :not-found {:id id}) NotFound { id: String } (named fields)
(query store {:id uuid}) store.query_by_id(&uuid)
(insert! store (build User input)) store.insert(User::from_input(input.clone()))
(validate-against User input) User::validate_input(&input)
(non-empty? errors) non_empty(&errors)
Examples
The examples/ directory contains several Pact modules:
minimal.pct — Starting point
The smallest valid module. One type, one effect set, one function.
pact compile examples/minimal.pct -o output/
user-service.pct — Canonical example
The reference example from the language spec. A user CRUD service with two functions (get-user-by-id, create-user), full provenance, effect tracking, and union return types.
pact compile examples/user-service.pct -o output/
auth-service.pct — Authentication
Token-based authentication with session management. Demonstrates multiple effect sets (session reads/writes, user lookup, audit logging), expiration handling, and password verification flows.
pact compile examples/auth-service.pct -o output/
inventory.pct — Inventory management
Stock tracking with reservations. Multiple types (Product, StockEntry, Reservation), cross-type queries, quantity constraints, and write-heavy operations.
pact compile examples/inventory.pct -o output/
notification.pct — Notifications
Multi-channel delivery with template rendering. Shows send effects (email-gateway, sms-gateway), long latency budgets (2000ms), and chained operations (render → deliver → persist).
pact compile examples/notification.pct -o output/
Language Reference
Module
Every .pct file contains a single module:
(module module-name :provenance {req: "SPEC-ID", author: "agent:name", created: "ISO-8601"} :version 7 :parent-version 6 :delta (operation target "description")
;; declarations: types, effect-sets, functions ...)
Types
Types have named fields with constraints:
(type User :invariants [(> (strlen name) 0) (matches email #/.+@.+/)] (field id UUID :immutable :generated) (field name String :min-len 1 :max-len 200) (field email String :format :email :unique-within user-store))
Supported field annotations: :immutable, :generated, :min-len, :max-len, :format, :unique-withi
[truncated for AI cost control]