Language Overview
AEX v0 is intentionally tiny. Master these concepts and you can read any contract.
Task Header
task fix_test v0Names the contract and declares the spec version. The task keyword is preferred; agent is accepted as an alias for backwards compatibility.
Goal
goal "Fix the failing test with the smallest safe change."Human-readable statement of the desired outcome.
Capabilities
use file.read, file.write, tests.run
deny network.*, secrets.readuse grants requested capabilities. deny explicitly blocks capabilities. Runtime policy will intersect with these declarations.
In policy files, allow is used instead of use — see Policy Files.
Inputs
need test_cmd: str
need target_files: list[file]need declares required runtime inputs. AEX validates the presence and type of each input before executing any tool or model step. Supported types: str, num, int, bool, file, url, json, list[T], and T? (optional). Missing or mistyped inputs block execution immediately.
Steps
do: call a toolmake: request model outputcheck: validate a conditionconfirm: require human approval before a tool callreturn: finish the contract with typed data
make steps are executed through the runtime's model handler. When you integrate via the CLI or adapters (for example @aex-lang/openai-agents), you provide a function that takes the make step description and returns the generated artifact.
Example:
do tests.run(cmd=test_cmd) -> failure
make patch: diff from failure, sources with:
- fix the failing test
- preserve public behaviorEach do binds a result. Each make produces structured model output.
Checks and Confirmation
Checks are guardrails that must pass. Confirmation steps create human approval gates for side-effectful tools.
check patch touches only target_files
confirm before file.writeBuilt-in checks include:
result— treats the value as truthy/non-emptyvalue has "Literal"— substring inclusionvalue has citations— ensures Markdown or URLs include citationsvalue does not include other_value— forbids sensitive tokenspatch is valid diff— validates unified diff structurepatch touches only target_files— ensures the diff stays within an allowed list
Control Flow
Conditional (if)
Execute steps only when a condition is true:
do tests.run(cmd=test_cmd) -> result
if result.passed
do file.write(diff=patch) -> writtenThe body is indented under the if line. If the condition is false, the body is skipped.
Iteration (for)
Loop over a list input:
need repos: list[str]
for repo in repos
do git.diff(paths=[repo]) -> diff
check diff is valid diffThe loop variable (repo) is scoped to the body. Budget limits count each iteration separately — a budget calls=3 with a 5-element list will block after the third call.
Built-in Tools
The reference runtime ships several safe defaults so you can run contracts locally without wiring custom handlers:
file.read— read files into memoryfile.write— apply structured writes or unified diffs with optional confirmation gatestests.run— execute a test command and capture pass/fail outputgit.diff/git.apply— inspect and apply diffs in the current repository
All tools participate in the permission intersection (use/deny + runtime policy) and respect call budgets and confirmation gates.
Return Value
Contracts end with an explicit return payload:
return {
status: "fixed",
patch: patch,
test: final
}The runtime serializes this structure into JSON for downstream consumers.
Generating Contracts
Instead of writing contracts by hand, generate them from natural language:
aex draft "fix the failing test in src/foo.ts" --model anthropicThe draft command calls an LLM to produce a valid AEX task contract, validates it against the active policy, and saves it to .aex/runs/. Review before executing:
aex review .aex/runs/fix-test.aex
aex review .aex/runs/fix-test.aex --runPolicy Files
AEX also supports policy files — ambient authority boundaries that apply across an entire session rather than a single task. Policy files use the policy keyword instead of task and define allow/deny/confirm/budget constraints without any execution steps.
policy workspace v0
goal "Default security boundary for this repository."
allow file.read, file.write, tests.run, git.*
deny network.*, secrets.read
confirm before file.write
budget calls=100Policy vs Task
| Policy | Task | |
|---|---|---|
| Header | policy name v0 | task name v0 |
| Purpose | Ambient guardrails for a session | Specific task execution |
| Allowed keywords | allow, deny, confirm before, budget, goal | All keywords |
| Forbidden keywords | need, do, make, check, return, if, for | None |
Merge Semantics
When a policy and task contract are both active, the effective permissions are the most restrictive combination:
- Allow = policy allow ∩ task use (intersection)
- Deny = policy deny ∪ task deny (union)
- Confirm = policy confirm ∪ task confirm (union)
- Budget = min(policy budget, task budget)
Directory Convention
repo/
.aex/
policy.aex # ambient authority boundary
runs/ # generated one-off contracts
fix-test.aex
fix-test.audit.jsonl
tasks/
fix-test.aex # reusable checked-in task contracts
review-pr.aexThe CLI auto-discovers .aex/policy.aex in the current directory. tasks/ holds reusable contracts checked into the repo. .aex/runs/ holds generated one-off contracts from aex draft. Use aex init --policy to scaffold a policy.