PACT is a typed language for AI agent orchestration. Permissions, contracts, and guardrails — built into the syntax, enforced by the compiler.
Website • Get Started • Examples • Docs • Contributing
PACT is a language, not a library. Where frameworks bolt safety onto Python after the fact, PACT encodes permissions, types, and agent contracts directly into its syntax. Every tool declares what it needs. Every agent declares what it may do. The compiler enforces the rest -- before a single API call is made. The result: AI agent systems you can reason about, audit, and trust.
| PACT | LangChain | CrewAI | AutoGen | |
|---|---|---|---|---|
| Language vs Library | Language | Python lib | Python lib | Python lib |
| Type safety | Built-in (::) |
None | None | None |
| Permission system | First-class (^) |
Manual | None | None |
| Agent contracts | Enforced at compile-time | Runtime only | Runtime only | Runtime only |
| Composable prompts | Templates + Directives | String concatenation | String templates | String templates |
| Source providers | Declarative (source:) |
Raw HTTP calls | Manual | Manual |
| Auto-guardrails | GDPR, HIPAA, PCI-DSS | Manual | None | None |
| Multi-backend | Claude, OpenAI, Ollama | Many | OpenAI | Many |
| Tooling | LSP, VS Code, formatter | IDE plugins | None | None |
# Install via Cargo
cargo install pact-lang
# Or via Homebrew (macOS / Linux)
brew tap pact-lang/tap && brew install pact-lang# Scaffold a new project
pact init my_agent.pact
# Type-check a contract
pact check examples/hello_agent.pact
# Run with Claude
export ANTHROPIC_API_KEY=sk-...
pact run examples/website_builder.pact --flow build_bilingual_site \
--args "coffee shop in Uppsala, Sweden" --dispatch claude
# Format code
pact fmt examples/hello_agent.pact --write
# Generate docs
pact doc examples/website_builder.pact -o docs.md
# Interactive playground
pact playground --load examples/research_flow.pactA complete, working PACT program -- a research agent that searches the web, summarizes findings, and drafts a report:
permit_tree {
^net { ^net.read }
^llm { ^llm.query }
}
tool #web_search {
description: <<Search the web for a query.>>
requires: [^net.read]
source: ^search.duckduckgo(query)
params { query :: String }
returns :: List<String>
}
tool #summarize {
description: <<Condense content into key points.>>
requires: [^llm.query]
params { content :: String }
returns :: String
}
agent @researcher {
permits: [^net.read, ^llm.query]
tools: [#web_search, #summarize]
model: "claude-sonnet-4-20250514"
prompt: <<You are a thorough research assistant.>>
}
flow research(topic :: String) -> String {
results = @researcher -> #web_search(topic)
summary = @researcher -> #summarize(results)
return summary
}Every symbol in PACT carries meaning through its sigil.
| Sigil | Meaning | Example |
|---|---|---|
@ |
Agent | @researcher |
# |
Tool | #web_search |
$ |
Skill | $summarize |
~ |
Memory | ~conversation |
^ |
Permission | ^net.read |
% |
Template / Directive | %report_format |
Every parameter, return value, and variable in PACT has a type. The compiler validates types at compile time -- no runtime surprises.
-- Parameters and returns are typed with ::
tool #analyze {
params {
data :: String
count :: Int
tags :: List<String>
}
returns :: String
}
-- Flow signatures are typed
flow process(input :: String, limit :: Int) -> String {
result = @agent -> #analyze(input, limit, ["tag1"])
return result
}Built-in types: String, Int, Float, Bool, List<T>, Map<K,V>, Optional<T>, Record, Any
The type checker also infers variable types from assignments. If a variable is assigned a String and later reassigned an Int, the compiler warns:
w variable 'result' was inferred as String but is being assigned Int
,---[pipeline.pact:5:5]
5 | result = 42
. ---+---
. `-- incompatible reassignment
`----
Types flow through the entire program: tool return types propagate to variables, flow parameters are validated at call sites, and template fields enforce their declared types.
Permissions are not an afterthought. They are part of the grammar. If an agent uses a tool it lacks permission for, the compiler rejects the program:
agent @writer {
permits: [^llm.query] -- only LLM access
tools: [#save_to_disk] -- tries to use a file tool
}
tool #save_to_disk {
requires: [^fs.write] -- needs filesystem write
params { content :: String }
returns :: String
} x agent '@writer' uses tool '#save_to_disk' which requires permission 'fs.write',
| but the agent does not have it
,---[contract.pact:3:13]
3 | tools: [#save_to_disk]
. ------+------
. `-- tool used here
`----
help: add '^fs.write' to the agent's permits list
This is caught at compile time -- before any API call, before any file is touched.
Templates define reusable output schemas that tools must conform to:
template %website_copy {
HERO_TAGLINE :: String <<one powerful headline>>
HERO_SUBTITLE :: String <<one compelling subtitle>>
ABOUT :: String <<two paragraphs about the business>>
MENU_ITEM :: String * 6 <<Name | Price | Description>>
}
tool #write_copy {
description: <<Write marketing copy for a website.>>
requires: [^llm.query]
output: %website_copy
params { brief :: String }
returns :: String
}Directives are reusable prompt blocks with typed parameters. Attach them to tools to compose complex behavior from small, testable pieces:
directive %scandinavian_design {
<<Use Google Fonts ({heading_font} for headings, {body_font} for body).
Rich color palette matching a Scandinavian brand.>>
params {
heading_font :: String = "Playfair Display"
body_font :: String = "Inter"
}
}
tool #generate_html {
description: <<Generate a one-page HTML website.>>
requires: [^llm.query]
directives: [%scandinavian_design, %scroll_animations]
params { content :: String }
returns :: String
}Instead of embedding HTTP endpoints in handler strings, declare what a tool needs and let PACT resolve it:
-- Before: fragile, hardcoded URL
tool #search {
handler: "http GET https://api.duckduckgo.com/?q={query}&format=json"
...
}
-- After: declarative source provider
tool #search {
source: ^search.duckduckgo(query)
...
}Providers are built-in, tested, and carry their own permission requirements. Swap ^search.duckduckgo for ^search.brave without changing anything else.
Flows chain agents together with dispatch (->), pipelines (|>), and fallbacks (?>):
flow build_bilingual_site(request :: String) -> String {
-- Chain agents with dispatch
research = @researcher -> #research_location(request)
english = @researcher -> #write_copy(research)
swedish = @translator -> #translate_to_swedish(english)
html = @designer -> #generate_html(swedish)
return html
}
flow safe_search(query :: String) -> String {
-- Fallback: if primary fails, try backup
result = @researcher -> #web_search(query) ?> @writer -> #draft_report(query)
return result
}| File | Description |
|---|---|
hello_agent.pact |
Minimal agent with a single tool and flow |
research_flow.pact |
Multi-agent research with fallback chains |
website_builder.pact |
Bilingual website generator with templates, directives, and source providers |
rag_pipeline.pact |
Retrieval-augmented generation with citations and quality checking |
code_review.pact |
AI-powered code reviewer with security analysis |
customer_support.pact |
Intent classification with match expressions and memory |
data_analyst.pact |
Data pipeline with fetch, clean, and insight generation |
ci_agent.pact |
CI/CD pipeline agent with shell execution |
age_verified_website.pact |
Age-gated content with compliance guardrails |
Run any example:
pact check examples/hello_agent.pact
pact run examples/hello_agent.pact --flow hello --args "world" --dispatch claudePACT detects compliance domains from your permission declarations and injects security boundaries into agent prompts at build time -- no manual boilerplate:
| Domain | Trigger | Standards |
|---|---|---|
| Personal Data | ^data.read, ^data.write |
GDPR, CCPA |
| Age Verification | ^user.age_check |
COPPA |
| Financial Data | ^payment.read, ^payment.write |
PCI-DSS |
| Health Data | ^health.read, ^health.write |
HIPAA |
| Credentials | ^auth.read, ^auth.write |
Secret masking |
Write 10 lines of PACT. Get production-grade guardrails for free.
flowchart TD
A[".pact source"] --> B["Lexer"]
B --> C["Parser"]
C --> D["Checker"]
D --> E["Build"]
D --> F["Dispatch"]
B -.- B1["Tokenizes sigils, keywords,\n<<prompt>> literals"]
C -.- C1["Recursive descent\nwith error recovery"]
D -.- D1["Types, permissions,\ntemplates, directives"]
E -.- E1["TOML configs\nMarkdown prompts\nClaude JSON schemas\nGuardrails"]
F -.- F1["Claude API\nOpenAI API\nOllama API\nRetry + Cache\nMediation"]
style A fill:#764ba2,stroke:#667eea,color:#fff
style B fill:#1a1a2e,stroke:#667eea,color:#fff
style C fill:#1a1a2e,stroke:#667eea,color:#fff
style D fill:#1a1a2e,stroke:#667eea,color:#fff
style E fill:#1a1a2e,stroke:#82aaff,color:#fff
style F fill:#1a1a2e,stroke:#c3e88d,color:#fff
style B1 fill:none,stroke:none,color:#888
style C1 fill:none,stroke:none,color:#888
style D1 fill:none,stroke:none,color:#888
style E1 fill:none,stroke:none,color:#888
style F1 fill:none,stroke:none,color:#888
flowchart LR
A["Flow"] --> B["@agent -> #tool(args)"]
B --> C{"Permission\nCheck"}
C -->|Pass| D["Source Provider\nor Handler"]
C -->|Fail| E["Compile Error"]
D --> F{"Retry?"}
F -->|Success| G["Cache Result"]
F -->|Fail + retries left| D
F -->|Fail + no retries| H["on_error\nfallback"]
G --> I["Validate Output\nagainst Template"]
I --> J["Return to Flow"]
style A fill:#764ba2,stroke:#667eea,color:#fff
style B fill:#1a1a2e,stroke:#82aaff,color:#fff
style C fill:#1a1a2e,stroke:#f78c6c,color:#fff
style D fill:#1a1a2e,stroke:#c3e88d,color:#fff
style E fill:#f07178,stroke:#f07178,color:#fff
style F fill:#1a1a2e,stroke:#ffcb6b,color:#fff
style G fill:#1a1a2e,stroke:#c3e88d,color:#fff
style H fill:#1a1a2e,stroke:#f78c6c,color:#fff
style I fill:#1a1a2e,stroke:#c792ea,color:#fff
style J fill:#764ba2,stroke:#667eea,color:#fff
Seven crates, one workspace:
| Crate | Purpose |
|---|---|
pact-core |
Lexer, parser, AST, checker, interpreter, formatter, docs |
pact-build |
Build pipeline: TOML, Markdown, Claude JSON, guardrails |
pact-dispatch |
Runtime: API clients, tool execution, retry, cache, mediation |
pact-cli |
CLI binary: check, run, fmt, doc, playground |
pact-lsp |
Language Server Protocol for editor integration |
pact-mermaid |
Bidirectional Mermaid diagram conversion |
pact-mcp |
Model Context Protocol server |
The pact-lang VS Code extension gives you a full development environment for .pact files.
What you get:
- Syntax highlighting for all sigils (
@,#,$,~,^,%), keywords, types, and<<prompt>>literals - Parameter interpolation highlighting inside prompts (
{param_name}) - Section label highlighting inside prompts (
DESIGN:,LAYOUT:) - Real-time error diagnostics with source locations and fix suggestions
- Hover information for agents, tools, templates, directives, and permissions
- Auto-completion for sigils, keywords, types, and references
- Go-to-definition support
Install from source:
# 1. Build the LSP server
cd /path/to/pact
cargo build --release --bin pact-lsp
# 2. Build the VS Code extension
cd editors/vscode
npm install
npm run compile
# 3. Package as VSIX
npx vsce package
# 4. Install in VS Code
# Cmd+Shift+P > "Extensions: Install from VSIX" > select the .vsix fileConfigure the LSP:
Add to your VS Code settings.json:
{
"pact.lspPath": "/path/to/pact/target/release/pact-lsp"
}The extension auto-starts the language server when you open a .pact file. Errors appear inline as you type — the same compile-time permission checks and type validation you get from pact check, but live in your editor.
PACT includes a built-in Model Context Protocol server. This lets AI assistants (Claude Desktop, Cursor, Windsurf, etc.) use PACT tools directly.
| Tool | Description |
|---|---|
pact_check |
Validate .pact source for syntax and semantic errors |
pact_list |
List all declarations (agents, tools, flows, templates, etc.) |
pact_run |
Execute a flow using mock dispatch |
pact_scaffold |
Generate .pact source from a high-level agent description |
pact_validate_permissions |
Check for overly broad permission boundaries |
Build the MCP server:
cargo build --release --bin pact-mcpAdd to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"pact": {
"command": "/path/to/pact/target/release/pact-mcp"
}
}
}Restart Claude Desktop. You can now ask Claude to check, scaffold, and run PACT programs directly.
Ask Claude: "Create a PACT agent system with a researcher that searches the web and a writer that drafts reports."
Claude will use pact_scaffold to generate valid .pact source, then pact_check to validate it.
| Command | Description |
|---|---|
pact init [file] |
Scaffold a new .pact project file |
pact check <file> |
Type-check and validate permissions |
pact build <file> [--out-dir dir] |
Compile to TOML, Markdown, and Claude JSON |
pact run <file> --flow <name> |
Execute a flow (add --dispatch claude for real API) |
pact run <file> --flow <name> --stream |
Stream output in real-time |
pact test <file> |
Run all test declarations |
pact fmt <file> [--write] |
Format a .pact file |
pact doc <file> [-o file] |
Generate Markdown documentation |
pact playground [--load file] |
Interactive REPL |
pact list [skills|prompts|all] |
List built-in skills and templates |
pact to-mermaid <file> |
Export flow as a Mermaid diagram |
pact from-mermaid <file> |
Import a Mermaid diagram as PACT |
| Provider | Permission | Description |
|---|---|---|
^search.duckduckgo |
^net.read |
Web search via DuckDuckGo |
^search.google |
^net.read |
Web search via Google Custom Search |
^search.brave |
^net.read |
Web search via Brave Search |
^http.get |
^net.read |
HTTP GET request |
^http.post |
^net.write |
HTTP POST with JSON body |
^fs.read |
^fs.read |
Read file contents |
^fs.write |
^fs.write |
Write file contents |
^fs.glob |
^fs.read |
Find files by glob pattern |
^time.now |
^time.read |
Current timestamp |
^json.parse |
^json.parse |
Parse and validate JSON |
- Core language -- lexer, parser, checker, interpreter
- Build system -- TOML, Markdown, Claude JSON compilation
- Real dispatch -- Claude API with tool-use conversation loop
- Runtime mediation -- compliance validation on every tool call
- Developer tooling -- formatter, doc generator, playground, LSP
- Streaming -- real-time token output via SSE
- Mermaid integration -- bidirectional diagram conversion
- Package registry -- share and reuse templates, directives, tools
- WASM compilation -- run PACT in the browser
- Visual editor -- drag-and-drop flow builder
- MCP integration -- Model Context Protocol support
- Debug mode -- step through flows, inspect agent state
- Issues -- Submit bugs, feature requests, or design discussions through GitHub Issues
- Contributing -- See CONTRIBUTING.md for development setup and guidelines
- Security -- Report vulnerabilities privately via SECURITY.md
If PACT is useful to you or your organization, consider sponsoring the project to support continued development:
Your support helps fund new features, documentation, and community growth.
MIT -- Copyright (c) 2025-2026 Gabriel Lars Sabadin