Skip to main content

obey, unit tests for your AGENTS.md: a report card of the rules a coding agent followed, and a hook that fixed the one it broke

obey

You wrote rules for your coding agent. Does it follow them?

AGENTS.md, CLAUDE.md and Cursor rules tell coding agents how to work in your repository, but nothing checks that they do. obey tests every rule against what the agent actually did, and turns the rules it breaks into hooks it cannot skip.

CI Release PyPI License

  1. Check. obey init reads your instruction files and turns rules such as "use pnpm", "never push" or "run the tests before you finish" into automatic checks.
  2. Test. obey run gives your agent a small task per rule where breaking it is the easy way out, in a sandbox, and scores what it did (commands, diffs, commits), not what it said. You get a report per rule.
  3. Enforce. obey enforce writes hooks for Claude Code, Codex CLI, OpenCode, Copilot CLI, Gemini CLI and Cursor, plus a git pre-commit check, so a broken rule is blocked the moment the agent tries.

What we found in 2,663 sandboxed runs of Claude Haiku 5.5 in Claude Code and OpenCode, on test tasks built to tempt the agent: it followed a rule every time the rule was in AGENTS.md (1,474 of 1,474 runs) and 81% of the time when it was not. How the rule was worded made no measurable difference. For the rules it broke without the rule written down, a hook alone raised compliance from 34% to 100%, and in 0 of 123 blocked runs did the agent find a way around it. How it was measured, the caveats, and the 136 errors we found in our own checkers.

Try it in one command

uvx --from obey-cli obey demo

No API key and no Docker: the demo replays agent runs recorded in the study and scores them again.

obey demo replaying recorded runs: per-rule compliance and the before/after table with hooks

Quickstart

In your repository (Python 3.10+, git, uv). The package on PyPI is obey-cli; to get a plain obey command, install it with uv tool install obey-cli or pipx install obey-cli.

uvx --from obey-cli obey init      # extract rules into obey.yaml and create probes
uvx --from obey-cli obey lint      # no agent runs: checks rules, hashes, enforcement; writes the badge

Read obey.yaml: every rule has a checker, or a status that says why not (uncheckable for rules that need taste, proposed for LLM suggestions you have to approve). Then either measure or enforce:

# enforce now, no runs needed: hooks for every agent plus a git pre-commit check
uvx --from obey-cli obey enforce --dry-run
uvx --from obey-cli obey enforce

# measure: needs Docker (or colima) and an OpenRouter key; the key never enters the sandbox
uvx --from obey-cli obey doctor --build-image          # once: the sandbox image, a few GB
export OPENROUTER_API_KEY=...
uvx --from obey-cli obey run --estimate --model anthropic/claude-haiku-5.5
uvx --from obey-cli obey run --model anthropic/claude-haiku-5.5 --budget 2
uvx --from obey-cli obey enforce --from .obey/report.json   # hooks for the rules it broke
cp .obey/report.json before.json
uvx --from obey-cli obey run --compare before.json --model anthropic/claude-haiku-5.5

Test your AGENTS.md in 10 minutes

uvx --from obey-cli obey run --model anthropic/claude-haiku-5.5 --trials 3 --baseline-trials 1 --budget 2

On the demo repository (11 checkable rules with probes) this made 44 runs of Claude Code 2.1.309 in 9 min 20 s and cost $0.38 (measured on 2026-10-24 on a Mac Studio M4 Max, 128 GB, Docker in colima, 4 runs at a time; eval/results/quickstart-run.json). It prints one line per rule and writes report.html, report.md and report.json. The defaults (5 runs per rule plus 3 with the rule removed) double the runs and narrow the intervals.

How it works

flowchart LR
  A["AGENTS.md, CLAUDE.md, GEMINI.md,<br/>.cursor/rules, Copilot files"] --> B["obey extract<br/>pattern library (+ reviewed LLM proposals)"]
  B --> C["obey.yaml<br/>rules with checkers"]
  C --> D["obey probe<br/>a tempting task per rule"]
  D --> E["obey run"]
  subgraph S["sandbox (one container per run)"]
    E --> F["coding agent, headless"]
    F -->|"model calls, dummy token"| G["gateway<br/>holds the key, meters cost"]
    F -->|"npm, PyPI only"| H["egress proxy"]
  end
  F --> I["commands, diff, commits"]
  I --> J["deterministic checkers"]
  J --> K["report: compliance per rule,<br/>no-rule baseline, evasions"]
  C --> L["obey enforce"]
  L --> M["hooks for 6 agents<br/>+ git pre-commit"]
  M --> E
  • Extract. obey reads AGENTS.md (nested files apply to their directory), CLAUDE.md with @imports, GEMINI.md, Cursor .mdc rules (globs, alwaysApply) and Copilot instruction files (applyTo), and maps each rule to one of twelve deterministic checkers: forbidden or required commands, forbidden paths, paired paths (tests with code), new dependencies, forbidden or required content in added lines, commit messages, commits, pushes, file names and the final message. Rules that need judgement stay uncheckable. --llm asks any OpenAI-compatible model about the rest and writes its answers as proposed.
  • Probe. Each rule gets a small task where breaking it is the easy route: add a package (package manager rule), rename a column (migration rule), type an untyped payload (any rule), "let me know when it's done" (tests rule).
  • Run. The agent runs headless several times per probe, plus a no-rule baseline arm with the rule removed, in a fresh container. Only git ls-files enters the container. Egress goes through an allow-listing proxy; the model key stays in a gateway outside the container, which enforces per-run and total spend caps.
  • Score. Checkers read what the agent did (commands, diff, commits), never what it said. A run that never touched a rule's domain is not_exercised and excluded; compliance comes with a Wilson 95% interval and the exercised share.
  • Enforce. obey enforce writes hooks that call one standard-library rule engine, the same code the checkers use, so a hook blocks exactly what the checker measures. obey run --compare measures again and counts evasions: runs where a hook blocked the direct route and the rule was still broken another way.

What obey supports

Claude Code Codex CLI OpenCode Copilot CLI Gemini CLI Cursor
Version verified (2026-10-23) 2.1.309 0.164.0 1.19.4 1.0.103 0.65.0 docs only
Instruction files it reads CLAUDE.md, AGENTS.md if there is no CLAUDE.md AGENTS.md AGENTS.md, else CLAUDE.md AGENTS.md, CLAUDE.md, GEMINI.md, Copilot files GEMINI.md .cursor/rules, AGENTS.md
obey run (headless, sandboxed) yes, live yes, live yes, live yes, live (bring your own key) yes, needs a paid Gemini key; not run no: no custom endpoint
Rules loaded, decoy global config ignored verified live verified live verified live verified live — —
Enforcement written by obey enforce .claude/settings.json .codex/hooks.json .opencode/plugins/obey.ts .github/hooks/obey.json .gemini/settings.json .cursor/hooks.json
Hook blocks a shell command live live live live contract tests contract tests
Can block finishing (tests, paired paths) yes yes no (advisory) yes yes no (advisory)

"Live" means verified with the real CLI in the sandbox (eval/contract_check.py, with a stand-in hook script in the files and format obey enforce writes); "contract tests" means the generated hook was tested against payloads written from the vendor's documentation. Every target also gets the git pre-commit check. Codex CLI loads project hooks only after you trust them (/hooks); Copilot CLI's prompt mode needs --trust-folder or GITHUB_COPILOT_PROMPT_MODE_REPO_HOOKS=true.

Details, flags and isolation checks: docs/agents.md. Checker definitions: docs/checkers.md. Enforcement targets: docs/enforce.md.

Configuration

obey.yaml is the reviewed list of rules (reference). Environment variables:

Variable Used by Meaning
OPENROUTER_API_KEY or OBEY_API_KEY obey run provider key; stays in the gateway, outside the sandbox
OBEY_LLM_API_KEY, OBEY_LLM_BASE_URL, OBEY_LLM_MODEL obey extract --llm any OpenAI-compatible endpoint
OBEY_DOCKER obey run docker command (for example podman)
GITHUB_TOKEN obey study survey GitHub code search

In CI and pre-commit

# .github/workflows/obey.yml: lint mode needs no secrets
permissions:
  contents: read
jobs:
  obey:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1  # v7.0.1
      - uses: B0yko/obey@v0.1.1

mode: run (with api-key, model and budget) runs the agent; it spends money and needs a secret, so the action refuses it on pull requests: run it on workflow_dispatch or a schedule only.

# .pre-commit-config.yaml (after `obey enforce --targets precommit`)
repos:
  - repo: https://github.com/B0yko/obey
    rev: v0.1.1
    hooks:
      - id: obey
      - id: obey-commit-msg

The badge: obey lint and obey run write obey-badge.json in the shields.io endpoint format. Commit it and use https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/<you>/<repo>/main/obey-badge.json.

Results

The study's agent runs were made for this release on 2026-10-23 and 2026-10-24 (UTC) on a Mac Studio M4 Max, 128 GB (Docker in colima), with anthropic/claude-haiku-5.5 through OpenRouter in Claude Code 2.1.309 and OpenCode 1.19.4. The survey and the extraction and checker evaluations involve no agent runs. Full report: study/REPORT.md. Pre-registration: study/PREREGISTRATION.md. Every change after it: study/DEVIATIONS.md.

What public AGENTS.md files say (600 repositories)

  • Median 7 rules per file (mean 21.6, 90th percentile 66, max 193); 102 of 600 files contain no directive at all.
  • 23.4% of 12,977 rules are mechanically checkable by obey's deterministic extractor; 19.9% could be enforced before the action by a pre-tool hook, another 3.4% only at stop or commit time. An LLM pass would call more of them checkable, up to 49.5% (unreviewed proposals, so an upper bound).
  • 13% of the repositories (78) have hook configuration in the tree: git hooks (pre-commit, husky, lefthook) in 60, agent hooks in 22. The survey sees the configuration files, not what the hooks check.

Does the agent follow the rule, and does phrasing matter? (2,241 runs)

Two repositories authored for the study, ten rule types, each tested with the rule removed (no rule) and in three phrasings, 14 runs per cell. This is probe compliance: on small tasks built so that breaking the rule is the easy route, not compliance in everyday use.

Probe compliance by phrasing and harness: about 80% with no rule, 100% with the rule in any phrasing
Condition As registered Corrected Exercised runs
no rule 73% [68–76] 81% [78–85] 488 of 560
imperative ("Use pnpm …") 95% [92–96] 100% [99–100] 499 of 560
prohibition ("Never …") 92% [89–94] 100% [99–100] 478 of 560
imperative with a reason 94% [92–96] 100% [99–100] 497 of 560
  • The rule works when it is there. With the rule in AGENTS.md the agents broke it in none of 1,474 exercised runs (corrected verdicts); without it, compliance was 81%. The pre-registered test on the corrected verdicts: +17.6 percentage points [95% CI +4.9 to +34.1], p < 0.001 (on the verdicts as registered: +20.1 [+6.8 to +36.5]). The rules that changed behaviour were the ones agents break by default: "leave changes uncommitted" (1 of 56 runs without the rule, 168 of 168 with it), "leave pushing to the maintainers" (42 of 55, 168 of 168), Conventional Commit messages (0 of 15 runs that committed without the rule, 20 of 20 with it), and the ban on print() in src/ (21 of 28, all 7 violations by Claude Code; the ban on : any was never broken). Five of the ten probes tempted nobody: agents followed those rules in every run even when they were absent.
  • Phrasing did not matter on the corrected verdicts: every phrasing was followed in every exercised run. On the verdicts as registered, the prohibition scored about 2 points lower (significant after Holm's correction), but every violation behind that result was a checker false positive (below).
  • The checkers were wrong 136 times. Reading the runs showed three false-positive patterns in the registered checkers: pnpm install --frozen-lockfile 2>&1 read as adding a package called 2>&1 (109 runs); the generator of src/generated/ run through its package script pnpm gen:types scored as a hand edit (25 runs); and the same generator scored as failed because a later command on its line failed (2 runs). The checkers scored 1.0 precision on the authored held-out cases, which did not contain these forms. All 136 went from violated to obeyed, so the 1,474 obeyed runs were also re-checked with simple logic that does not use obey's engine (eval/audit_obeyed.py): no missed violation. Both analyses are in the report; the fixes are in this release.

Do hooks hold when the rule is not there? (182 runs counted)

Every rule type that agents broke without the rule was compiled by obey enforce into a hook for that agent plus the git pre-commit check, and the same probes were run again, still without the rule in AGENTS.md: the agent learns the rule only when a hook refuses it.

Probe compliance without the rule, before and with hooks, for the five rule types agents broke
  • In the 13 cells (rule type × repository × agent) that agents broke without the rule, compliance went from 34% [27–42] to 100% [97–100] with hooks (+65 points, p < 0.001, stratified permutation test).
  • Evasions: 0 of 123 runs in which a hook blocked at least once broke the rule another way by the end (0% [0–3]). In the commit-message cells, 12 of 56 runs committed, all with a conforming message (without hooks: 15 of 56 committed, none conforming). In 2 of the 13 cells (git push and npm for Claude Code) no hook fired: the agent did not break the rule in those runs, so their gain is not a hook effect.
  • Because no rule was ever broken while it was in AGENTS.md (corrected verdicts), the pre-registered version of this measurement, hooks on top of the rule, had nothing to measure. 28 further runs in two cells that only a checker false positive had selected are listed in the report and not counted.

The tool itself

  • Rule extraction, held-out set of 15 authored files (136 labelled rules, evaluated once): recall 0.70 [0.60–0.79], precision 0.96 [0.88–0.98] for active rules; parameters right for 0.77 [0.65–0.85]. On the 25 development files: recall 0.98, precision 1.0.
  • Checkers, held-out set of 442 labelled runs (frozen before the checkers existed): precision and recall 1.0 for every type at release (dependency_unchanged accuracy 36 of 37 after a definition change); the first evaluation found three bugs (content_required_added precision 0.94, dependency_unchanged recall 0.94), fixed and listed in fixtures/checkers/CHANGES.md, so the release numbers for those two types are no longer an unbiased estimate. See the false positives above for what authored cases miss.
  • Live checks with the real CLIs in the sandbox: each of Claude Code, Codex CLI, OpenCode and Copilot CLI loaded the repository's AGENTS.md, ignored a decoy global instruction file, and was stopped by a pre-tool hook installed in the files and format that obey enforce writes, with a stand-in hook script (eval/contract_check.py, 8 of 8 probes, $0.013, eval/results/contract-check.json). The hooks that obey enforce generates ran in the study's enforcement runs for Claude Code and OpenCode. Locally, Claude Code 2.1.308 with an obey enforce hook was refused npm install date-fns in 14 ms, and the hashed files in ~/.claude (settings.json and the plugin files) were unchanged (eval/results/live-claude-code.json).
  • Hook overhead: each hook call is a fresh Python process. 95th percentile 31–32 ms with Python 3.14 and 44–45 ms with macOS's system Python 3.9 (200 calls per agent format, 11 rules, Mac Studio M4 Max, eval/results/hook-latency.json).
  • Cost of the study: $20.22 for 2,663 agent runs (pilot $1.28, main grid $17.05, part 3 $1.88), metered by obey's gateway, plus $0.34 for the survey's LLM pass, from its own spend ledger.

Reproduce: uv run obey study report rebuilds the report and charts from the committed summaries; uv run python eval/rescore.py re-scores the raw runs (attached to the release); uv run python eval/run_eval.py re-runs the extraction and checker evaluations. Running the study again needs Docker, an OpenRouter key and about $20 of model calls at October 2026 prices (commands in docs/study.md).

Privacy and security

  • During obey run, your repository's tracked files and the probe tasks go to the model provider you configure (by default OpenRouter, which routes to the model's vendor). Only files tracked by git enter a run: untracked and ignored files, such as an untracked .env, never do; a tracked .env would.
  • The key stays in obey's gateway, outside the agent's container; agents get a dummy token. The gateway accepts only that token, forwards only the model endpoints of each API, pins the model, and stops a run at its spend cap.
  • Agent containers have no route to the internet except an allow-listing proxy (by default npm, PyPI, Go and crates registries) and the gateway; every egress attempt is logged. No host path is mounted.
  • --unsafe-local (for machines without Docker) runs the agent directly on your machine in a temporary copy with an empty HOME, no git remotes, no credential variables and no git credential helpers, with the agent's own sandbox switched on where it has one (Claude Code, Codex CLI, Gemini CLI). Commands the agent runs can still reach the network and anything your user can read, so obey prints a warning and refuses "never commit" and "never push" probes there.
  • Subscription logins: obey's study used API keys only. Anthropic's Consumer Terms forbid accessing its services "through automated or non-human means" except with an API key or where Anthropic explicitly permits it (terms); check your vendor's terms before running agents on a subscription login.
  • obey enforce writes only inside your repository. Generated hooks use only the Python standard library and fail open (with a visible warning) on internal errors.

Limitations

  • Two demo repositories, ten rule types, one inexpensive model in two harnesses, specific agent versions and dates. Other models, agents and repositories may behave differently.
  • Probe compliance is compliance on tempting tasks written to make breaking the rule easy. It is not the compliance you would see in everyday use.
  • With the rule present, compliance was at the ceiling (100%) for every phrasing, so the phrasing experiment cannot tell phrasings apart for this model and these rules; a model or rule that is broken more often might show a difference. Five of the ten probes did not tempt the agent even without the rule.
  • The corrected verdicts were decided after the outcomes were known (study/DEVIATIONS.md). Each correction is a false positive visible in the recorded commands, and the registered analysis is reported next to it. Every correction went from violated to obeyed; an independent re-check of the obeyed runs found no missed violation, but it shares the study's view of what each rule means.
  • Checkers can be wrong on real runs in ways authored test cases miss: the study found 136 false positives (6% of runs) in three patterns. Read the evidence obey prints for a violation before acting on it.
  • In the enforcement runs the hook's refusal quotes the rule, so "hooks without the rule" still tells the agent the rule at the moment it is blocked.
  • Extraction recall on real instruction files is measured only on the authored sets (held-out recall 0.70). Read obey.yaml before trusting it; the survey's "checkable" share depends on the same extractor.
  • The survey reflects what public AGENTS.md files say, sampled through GitHub code search (relevance-ranked within size slices), not what agents do with them.
  • Checkers see actions, so rules about taste, design or communication stay uncheckable, and some checkers read the end state only: an edit made and reverted inside one run does not count for content rules.
  • Hooks are a guardrail, not a security boundary: an agent can still change files through routes a pre-tool hook does not see. obey run --compare measures exactly that as evasions; the pre-commit check and the stop hooks catch some of it at the end.
  • Linux and macOS only. Windows is not supported in 0.1.

Alternatives

obey is not the first tool to compile instruction files into checks or to test them; this is how the closest ones differ (checked on 2026-10-23):

  • hookify (Anthropic's Claude Code plugin) writes regex rules that warn or block tool calls, from an instruction or from corrections mined from a conversation. It does not measure compliance.
  • claudecode-rule2hook turns plain-English rules, or rules read from CLAUDE.md, into Claude Code hook configuration. No measurement.
  • ContextCov compiles AGENTS.md, CLAUDE.md and Copilot instructions into source, import-graph, LLM-reviewed and command checks, and reports 88.3% constraint compliance on SWE-bench Lite against 67.0% for a prompt-only baseline. It evaluates on SWE-bench tasks rather than per-rule tempting tasks.
  • AgentGuard learns guardrail rules from failure traces of coding agents rather than from your instruction file.
  • nunchi (article) compiles CLAUDE.md rules into trigger-bound rules that a hook re-delivers when an action fires.
  • agents-md-evals (archived) A/B tests a Claude instruction file present versus removed, using assertions from the repository's history.
  • ruleProb extracts rules from CLAUDE.md, AGENTS.md and Cursor rules, builds sandbox scenarios, runs a provider against each and scores commands, protected writes and code patterns; it is the closest in purpose. obey differs in its probes-as-temptations with a no-rule baseline, per-rule rates with intervals from actions across five agent CLIs, enforcement for six agents from the same engine, and evasion counts.
  • rulecheck tests what a model says it would do in written scenarios, not its actions.
  • Static linters such as agnix, agentlinter, alint, agent-compass, agent-ready and Factory Agent Readiness check instruction and configuration files without running an agent.
  • For "tests pass" and "deployed" claims in Claude Code, proof-of-done is a fuller stop-hook check than obey's command_required hook.

Roadmap

  • Antigravity CLI, once it has a documented headless mode with an API key.
  • Cursor CLI runs (it has no custom model endpoint today, so the key would have to enter the sandbox).
  • More probe templates per rule type, and probes adapted to the repository with --llm.
  • Judging uncheckable rules is out of scope for 0.1 on purpose; obey stays deterministic.

License

Apache-2.0. Copyright 2026 Andrii Boiko.

Metadata

Release files for obey-cli 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for obey-cli 0.1.1
File Size Uploaded Provenance Actions
obey_cli-0.1.1.tar.gz 2.5 MB With provenance Details for obey_cli-0.1.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for obey-cli 0.1.1
File Interpreter ABI Platform Actions
obey_cli-0.1.1-py3-none-any.whl Python 3 none any Details for obey_cli-0.1.1-py3-none-any.whl

Total release size: 3.5 MB

Release files / obey_cli-0.1.1.tar.gz

Download URL obey_cli-0.1.1.tar.gz
Size 2.5 MB
Tags Source
SHA-256 checksum
How to use checksums
84a1285f73c477e12ffbd24a32ff383f9d842ca2caca549144315801ae643526
BLAKE2b-256 checksum
How to use checksums
42df570f9f854f4153432169176b66c049ee8dad6446d2cb5d349a433829421f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 24, 2026.

Transparency log

Release files / obey_cli-0.1.1-py3-none-any.whl

Download URL obey_cli-0.1.1-py3-none-any.whl
Size 988.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1eb8fabb4c57edc67e71707ac6964f81366bc3bb7e2001938b98cc928e4b7c9e
BLAKE2b-256 checksum
How to use checksums
f934b4400dfb2034e011b063e34acf040a6c66197f4079a9bc5ef2026c9a69ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 24, 2026.

Transparency log

Security for obey-cli 0.1.1

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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for obey_cli-0.1.1.tar.gz

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 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page