This release is a pre-release and may not be stable for production use.
treaty
A Python CLI framework for command-line tools that AI agents can call safely.
treaty implements the CLI Agent Spec:
an agent reads every command, flag, and exit code in one manifest call, gets a JSON
envelope on every exit, and knows from the exit code alone whether a retry is safe. The
manifest is the treaty between the CLI author and the agent; exit codes are its clauses.
Why treaty
Agents drive CLIs by trial and error: they parse --help, guess flags, scrape stderr, and
retry commands that already half-ran. treaty makes the contract explicit and checks it:
- One-call discovery:
manifestreturns every command, flag, example, and exit code as JSON - An envelope on every exit:
ok,data,error,warnings,meta, even when a handler crashes - Typed exit codes: each one says whether it is retryable and what side effects it left; a handler that raises an undeclared code fails loudly
- Declared danger: every command is
safe,mutating, ordestructive; destructive ones refuse without--confirm-destructiveand show a dry-run preview instead - Safe retries: idempotency keys and session deduplication for mutating commands
- Strict validation: every argument error in one run, exit
2, and the handler never called - Secrets stay secret: read only from env or file, redacted in output and logs
- MCP for free:
treaty-mcp module:appserves any treaty app's commands as MCP tools - Zero runtime dependencies in the core
Install
uv add treaty # the library, inside a uv project
uv tool install treaty # the treaty CLI on PATH
Requires Python 3.14. Add the mcp extra (treaty[mcp]) for the MCP server.
Quick start
Handlers are plain functions over a frozen dataclass of arguments:
from dataclasses import dataclass
from treaty import Affects, App, Arg, Ctx, Exit, Flag
app = App("deployctl", version="1.4.0")
app.exit_code("DEPLOY_CONFLICT", 79, description="Target already has a deployment in progress",
retryable=False, side_effects="none")
@dataclass(frozen=True, slots=True)
class Rollback:
service: str = Arg(description="Service name")
to: str | None = Flag(default=None, description="Release tag to roll back to")
dry_run: bool = Flag(default=False, description="Plan the rollback, write nothing")
@dataclass(frozen=True, slots=True)
class Plan:
effect: str
service: str
release: str
would_affect: Affects | None = None
deploy = app.group("deploy", description="Manage deployments")
@deploy.command("rollback", description="Roll a service back to its previous release",
danger_level="destructive", exit_codes=["DEPLOY_CONFLICT"])
def rollback(args: Rollback, ctx: Ctx) -> Plan:
if args.to is None:
raise Exit.DEPLOY_CONFLICT("No previous release recorded", context={"service": args.service})
if args.dry_run:
affects = Affects(f"Rolls {args.service} back to {args.to}", (f"service/{args.service}",), 1)
return Plan("would_update", args.service, args.to, affects)
return Plan("updated", args.service, args.to)
if __name__ == "__main__":
app.main()
What the agent sees
The manifest entry for deploy rollback declares its danger level and every exit code it
can return:
{
"danger_level": "destructive",
"exit_codes": {
"6": {"name": "CONFLICT", "retryable": false, "side_effects": "none"},
"79": {"name": "DEPLOY_CONFLICT", "retryable": false, "side_effects": "none",
"description": "Target already has a deployment in progress"}
}
}
deployctl deploy rollback api --to v1.3.0 without confirmation changes nothing. The
agent gets exit 2, a preview of what would happen, and the exact fix (meta trimmed):
{
"ok": false,
"data": {
"effect": "would_update",
"service": "api",
"release": "v1.3.0",
"would_affect": {"summary": "Rolls api back to v1.3.0", "resources": ["service/api"], "count": 1}
},
"error": {
"code": "CONFIRMATION_REQUIRED",
"message": "Command deploy.rollback is destructive and was not applied; it would: Rolls api back to v1.3.0",
"phase": "validation",
"retryable": false,
"suggestion": "rerun with --confirm-destructive to apply (confirm_destructive: true in exec, MCP, or --raw-payload)"
},
"warnings": [],
"meta": {"command": "deploy.rollback", "exit_code": 2, "dry_run": true, "request_id": "66bd15e2696b"}
}
Without --to, the handler raises DEPLOY_CONFLICT: exit 79, retryable: false, and
error.context names the service.
Output is JSON automatically when stdout is not a terminal and human-readable text when it is. Off a terminal nothing prompts: an answer a command needs fails with an exit code.
Tooling
The treaty CLI is itself a treaty app:
| Command | What it does |
|---|---|
treaty init |
Scaffold a new CLI project that passes the audit |
treaty scaffold-from |
Write a treaty module from a typer, click, or argparse CLI |
treaty audit |
Check an app against the spec and print the next fix, in your own names |
treaty conformance |
Generate a conformance profile and run the spec's kit |
treaty agents-md |
Write AGENTS.md from the registry |
treaty generate-skills |
Write agent skill files from the command schemas |
treaty-mcp |
Serve any treaty app's commands as MCP tools over stdio |
Every app also gets built-ins: manifest, version, exec, doctor, status,
cleanup, completion, and more.
Documentation
- Tutorial: take a CLI, new or migrated from argparse, click, or typer, through every audit rule to a release
- Reference: the full API, one section per concern
- Design guide: naming, error context, exit codes, and when to split a command
- Public API: the surface frozen at 1.0
- Spec compliance: the requirement-by-requirement score
- Changelog
Status
treaty is in release candidates for 1.0 and follows semantic versioning from 1.0; until then a release may break an app, and the changelog's "Breaking" sections say where. treaty 1.0 claims CLI Agent Spec Level 2 conformance.
Contributing
Issues and pull requests are welcome. See CONTRIBUTING.md for the development setup and how changes are released, and SECURITY.md to report a vulnerability. Everyone taking part follows the Code of Conduct.
License
Metadata
Release files for treaty 1.0.0rc39
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| treaty-1.0.0rc39.tar.gz | 516.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| treaty-1.0.0rc39-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.1 MB
Release files / treaty-1.0.0rc39.tar.gz
| Download URL | treaty-1.0.0rc39.tar.gz |
|---|---|
| Size | 516.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0d8830d75fdf8e09973fd16191fb134ad3880f7ead06cbbd4f23e147009891e6
|
|
BLAKE2b-256 checksum How to use checksums |
b779be87477ce344b4665213767b3545dcb154d69d85f1f392e42f9bd8691baf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / treaty-1.0.0rc39-py3-none-any.whl
| Download URL | treaty-1.0.0rc39-py3-none-any.whl |
|---|---|
| Size | 566.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1efd7948327ad870c63e7e0411cbd025c258d52b44579e630fc9a044066e7b3d
|
|
BLAKE2b-256 checksum How to use checksums |
aef256ad55e3cfb3432346d3e364cbaeaca655bfb7a51451a1a9042ff2043ee5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency log