Example rule files¶
Generated from agentltl-coding/examples/
It is kept with the code it documents. Edit it there.
showcase.yaml¶
Showcase rules: each one does something CLAUDE.md or permission rules cannot.
# Showcase rules: each one does something CLAUDE.md or permission rules cannot.
# Every rule here is exercised by `agentltl check` (see README, Examples).
rules:
- id: staging-before-prod
before:
first: {tool: make, with: {argv: deploy-staging}}
then: {tool: make, with: {argv: deploy-prod}}
since: [Edit, Write]
scope: project
why: Prod only gets what staging has already run.
- id: plan-before-apply
before: {first: terraform_plan, then: terraform_apply, since: [Edit, Write]}
why: Never apply infrastructure changes nobody has seen planned.
- id: one-pr-per-session
at_most: {call: gh_pr_create, times: 1}
mode: ask
why: A retried `gh pr create` opens duplicate PRs.
- id: no-push-to-main
never: git_push
where: {refspecs: [main, "*:main"]}
why: Main only changes through reviewed PRs.
- id: destructive-git
never:
- {tool: git_reset, with: {hard: true}}
- {tool: git_clean, with: {force: true}}
- {tool: git_checkout, with: {targets: "."}}
- {tool: git_branch, with: {force_delete: true}}
- {tool: docker_compose_down, with: {volumes: true}}
- docker_system_prune
mode: warn
why: These throw away work or data that cannot be recovered.
- id: stay-in-scope
require: {tool: [Edit, Write], where: {file_path: ["src/**", "tests/**"]}}
mode: ask
why: The task is limited to src/ and tests/.
- id: no-prod-data
never:
- {tool: [psql, mysql, redis_cli], where: {argv: "*prod*"}}
- {tool: "kubectl_*", where: {"*": "*prod*"}} # namespace, context, names...
mode: stop
why: Production data is off limits to the agent.
- id: known-hosts-only
require:
- {tool: curl, where: {urls: ["https://api.github.com/*", "https://pypi.org/*"]}}
- {tool: WebFetch, where: {url: ["https://docs.python.org/*", "https://pypi.org/*"]}}
mode: ask
why: Network access is limited to known hosts.
- id: lint-before-commit
before: {first: ruff, then: git_commit, since: [Edit, Write]}
mode: retry
why: Commits must pass ruff.
- id: audit-sudo
never: sudo
mode: log
why: Every sudo is worth a second look.
creative.yaml¶
Creative rules: workflows, research hygiene and security tripwires.
# Creative rules: workflows, research hygiene and security tripwires.
# Each one is exercised with `agentltl check` in the commit that added it.
rules:
# Numbers in results/ must come out of code, never typed in by the model.
- id: no-hand-typed-results
never: {tool: [Edit, Write, NotebookEdit], where: {file_path: ["results/**", "figures/**"]}}
why: Results are produced by scripts, so every number can be traced to code.
fix: Change the script that produces the file and run it.
# Every experiment run maps to a commit.
- id: commit-before-experiment
before:
first: git_commit
then: {tool: python, where: {script: "train*.py"}}
since: [Edit, Write]
why: An experiment run on uncommitted code cannot be reproduced.
# Plan before code.
- id: plan-first
before:
first: {tool: Write, where: {file_path: PLAN.md}}
then: {tool: [Edit, Write], where: {file_path: "src/**"}}
why: Write the plan to PLAN.md before touching src/.
# Test-first: the test changes before the code does.
- id: test-first
before:
first: {tool: [Edit, Write], where: {file_path: "tests/**"}}
then: {tool: [Edit, Write], where: {file_path: "src/**"}}
since: git_commit
mode: warn
why: Write or change a test before the implementation, in every commit.
# Prompt-injection tripwire: nothing legitimate ever reads the canary.
- id: canary
never: {tool: [Read, cat, head, tail, grep, cp, Grep], where: {"*": "**/CANARY_*"}}
mode: stop
why: The canary file is bait; reading it means instructions came from untrusted content.
# Break the exfiltration chain: after reading the web, outbound sends need you.
- id: web-then-send
ltl: 'G(now("WebFetch") -> G(!now("curl") & !now("wget") & !now("scp")))'
mode: ask
why: Untrusted web content was read this session; sending data out needs your approval.
# Conventional commits.
- id: conventional-commits
require: {tool: git_commit, where: {message: ["feat*", "fix*", "docs*", "test*", "refactor*", "chore*"]}}
mode: retry
why: Commit messages follow Conventional Commits (feat:, fix:, docs:, ...).
# Subagent budget.
- id: subagent-budget
at_most: {call: [Agent, Task], times: 3}
mode: ask
why: Subagents are expensive; ask before a fourth.
# Look before you overwrite: the same file must have been read first.
- id: read-before-overwrite
before:
first: {tool: Read, with: {file_path: $f}}
then:
- {tool: [Edit, Write], with: {file_path: $f}, exists: true}
- {tool: [sed, perl, tee], with: {paths: $f}, exists: true} # in-place edits, tee
- {tool: sponge, with: {file: $f}, exists: true}
- {tool: "*", with: {overwrite_to: $f}, exists: true} # > file (>> only appends)
why: Read a file before replacing it.
# Decision log before anything destructive.
- id: log-before-destroy
before:
first: {tool: [Edit, Write], where: {file_path: DECISIONS.md}}
then: [{tool: rm, with: {recursive: true}}, {tool: git_reset, with: {hard: true}}]
why: Record why in DECISIONS.md before deleting anything.
# A runbook that enforces itself.
- id: drain-before-reboot
before:
first: kubectl_drain
then: {tool: ssh, where: {command: "*reboot*"}}
scope: project
why: Drain the node before rebooting it.
AGENTLTL.yaml¶
AGENTLTL.yaml – rules the AgentLTL plugin enforces on every Claude Code tool call.
# AGENTLTL.yaml – rules the AgentLTL plugin enforces on every Claude Code tool call.
# Shell commands are parsed into structured calls first; list the names with `agentltl tools`.
settings:
mode: block # default escalation: block | warn | retry | ask | stop | log
retries: 3 # for mode: retry, blocked attempts before you are asked
unparseable: # a shell command the guard cannot analyse
interactive: ask # normal permission modes: you decide (ask | note | allow | deny)
auto: note # auto mode: let it through, tell Claude it was not checked
announce: true # list the rules to Claude at session start and after compaction
rules:
- id: tests-before-push
before: {first: [pytest, {tool: make, with: {argv: test}}], then: git_push, since: [Edit, Write]}
why: CI takes 20 minutes; a red build blocks everyone.
fix: Run the test suite (pytest) after your last edit, then push.
- id: no-force-push
never: git_push
with: {force: true}
why: Force-pushing rewrites shared history.
mode: warn
- id: no-secrets
never: [Edit, Write, Read, cat, cp, mv, rm, echo, tee]
where: {"*": ["**/.env", "**/.env.*"]}
why: .env files hold credentials.
mode: stop
- id: lockfile-by-tool-only
never: [Edit, Write]
where: {file_path: "*.lock"}
why: Lockfiles are generated; edit the manifest and re-run the package manager.
mode: ask
- id: one-migration
at_most: {call: {tool: alembic_revision}, times: 1}
why: One migration per change.
mode: retry
tools: # cli-to-tools specs for project commands
alembic:
subcommands:
revision:
options:
- {flags: [-m, --message]}
- {flags: [--autogenerate], type: bool}
upgrade: {positionals: [{name: revision}]}