Config as code
Keep an environment's actions, packs, approval policies, impact limits and budgets in anlyon.yaml. anlyon plan shows each change as tightening or loosening, anlyon apply applies it, anlyon pull writes what is live.
anlyon.yaml holds what your agent may do in each environment: its actions, the packs installed, the approval policies, the impact limits and the budgets. You change it in a pull request like any other code. anlyon plan shows the difference with what is live, anlyon apply makes it live, and anlyon pull writes what is live back to the file.
Know why you would use it
Guardrails changed by hand in a console drift: nobody remembers why a limit was raised, staging and production stop matching, and a change that loosens a rule ships with no review.
- Less risk. Every change is marked tightening or loosening before it applies. Tightening applies at once. Loosening, from a CLI login or an API key, waits for a workspace admin to approve it at a sign-off link: your agent, or the CI job it can trigger, can never loosen its own guardrails.
- Less effort. One file per repository, every environment in it. Review the plan in the pull request, apply on merge.
- A record. The file’s history says who changed which rule, when and why.
Start from what is live
anlyon auth login --scope anlyon:guardrails
anlyon pull
pull writes the environment your credential is bound to into anlyon.yaml, creating the file if there is none. Other environments in the file and its comments are kept. Commit the file.
The Guardrails consent group lets a CLI login read and change policies and limits. It starts unticked on the consent screen. In CI, use an API key with policies:read, policies:write, impact-limits:write, actions:write, and budgets:read and budgets:write if the file has budgets.
The file
# anlyon.yaml
version: 1
environments:
production: # an environment's slug
secrets: [POSTMARK, GITHUB] # names that must exist. Never values.
packs:
github: { actions: [list-issues, create-issue], withLimits: true }
actions:
send-receipt:
description: Email a receipt
method: POST
url: https://api.postmarkapp.com/email
headers: { X-Postmark-Server-Token: "{{secret:POSTMARK}}" }
governed: true
impact: { dimension: emails, amount: count(input.to), bound: exact }
policies:
big-refunds:
name: Refunds over $500 need two approvers
target: { action: refund }
when: input.amount >= 50000
approvals: 2
limits:
Emails per day: { dimension: emails, amount: 500, period: day }
Old limit: null # archive it
budgets:
<api-key-id>: { monthlyCostCentsCap: 5000, dimensionCaps: { actionInvokes: 10000 } }
staging:
limits:
Emails per day: { dimension: emails, amount: 50, period: day }
| Section | Holds | When it is not in the file |
|---|---|---|
secrets |
Secret names the actions need. plan lists a missing one as needs you: store its value with anlyon connections add or in the console. |
Not checked. |
packs |
Catalogue packs by name (github) or a pack directory, with the pack flags: actions, withLimits, secretNames, params, credential, noPrefix. |
Installed packs are left alone. |
actions |
Actions by name: description, method, url, headers, body, inputSchema, requiresApproval, enabled, governed, impact, verify. A field you leave out is not managed. |
Left alone. |
policies |
The policies block of a policy file. The file owns them: removing one from the file deletes it. |
Left alone. |
limits |
Impact limits by name: dimension, amount, period, and currency, action, resourcePrefix, scope (environment or end_user). null archives one. |
Left alone. |
budgets |
Budgets by the API key id they cap: monthlyCostCentsCap, dimensionCaps, alertThreshold, enabled. null removes one. |
Left alone. |
A credential acts on the environment it is bound to, so plan and apply use that environment’s entry. Policies are stored under the policy file id anlyon.yaml; set policyFile: at the top to use another.
Secret values never go in the file. A value that looks like a credential (an Authorization header that does not reference {{secret:NAME}}, or a string shaped like a provider key) stops the command and names where it is.
Plan
anlyon plan
Plan for anlyon.yaml: environment production (<environment-id>)
! secrets.GITHUB needs you store its value: anlyon connections add, or Secrets in the console. The file holds names only.
~ actions.send-receipt tightening adds an impact declaration
+ policies.big-refunds tightening adds a policy that gates calls
~ limits.Emails per day tightening lowers the limit from 1000 to 500
- limits.Old limit loosening archives the limit (10 emails per day)
3 tightening, 1 loosening (waits for a person's sign-off), 0 neutral, 1 needs you, 4 unchanged.
plan sends no change. --markdown prints a table for a pull-request comment; --json prints {data: {changes, summary, errors}}.
How each change is read:
| Change | Direction |
|---|---|
| Add an action, enable one, change where or what it sends, remove an impact or a read-back | loosening |
| Disable an action, require approval, make it governed, add an impact or a read-back | tightening |
| Add a policy that gates or denies; make one stricter | tightening |
| Delete or disable a policy that gates calls; make one looser; change what it matches | loosening |
| Add a limit, or lower one | tightening |
| Raise, archive or replace a limit | loosening |
| Add or lower a budget cap, turn a budget on, alert earlier | tightening |
| Raise or remove a cap, turn a budget off, alert later | loosening |
| A description, a rename, a disabled policy | neutral |
The server makes the final call when you apply. A change it reads differently is reported by apply.
Apply
anlyon apply --yes
apply plans, then makes the changes: tightening and neutral ones at once, loosening ones last. Policies go in two passes, so a new rule that tightens is never held up behind one that loosens.
A loosening change made with a CLI login or an API key is not applied. The server answers with a sign-off request, and apply prints its link:
These changes loosen a guardrail. A workspace admin approves each one at its link; nothing here can approve them:
limits.Old limit: needs a person's sign-off
https://console.anlyon.com/sign-offs/sor_...
limits.Emails per day applied
limits.Old limit sign-off pending (sor_...)
1 change(s) wait for a person's sign-off. Run anlyon plan after they decide.
--wait 30m waits for the decisions and reports them. Exit codes: 0 when everything applied, 15 when a sign-off is pending, 9 when --wait ran out, 14 when a change failed or a sign-off was rejected or expired.
Run plan on pull requests and apply on merge
Store an API key with the scopes above as the repository secret ANLYON_API_KEY, one per environment.
# .github/workflows/anlyon.yml
name: Anlyon guardrails
on:
pull_request:
paths: [anlyon.yaml]
push:
branches: [main]
paths: [anlyon.yaml]
permissions:
contents: read
pull-requests: write
jobs:
guardrails:
runs-on: ubuntu-latest
env:
ANLYON_API_KEY: ${{ secrets.ANLYON_API_KEY }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- name: Plan
if: github.event_name == 'pull_request'
run: npx --yes @anlyonhq/cli plan --markdown > plan.md
- name: Comment the plan
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
run: gh pr comment ${{ github.event.pull_request.number }} --body-file plan.md --edit-last || gh pr comment ${{ github.event.pull_request.number }} --body-file plan.md
- name: Apply
if: github.event_name == 'push'
run: npx --yes @anlyonhq/cli apply --yes
On merge, tightening changes apply and the job succeeds. Loosening ones exit 15 with their sign-off links in the job log, so the run shows as failed until a workspace admin decides. Pin the CLI version once you rely on it.
Pull
anlyon pull --yes
Writes the live environment: every action except those of packs the file lists, the policies the file owns, the limits that are not archived, the budgets (when the credential can read them), and the secret names the actions reference. Line endings and comments elsewhere in the file are kept. --yes replaces an environment already in the file without asking.
Related
- Policy as code: the policy file format and its tests.
- Impact limits.
- Set up from your agent.

