Policy as code · coding agents

Policy as Code for Coding Agents

Agents follow instructions most of the time. Rules have to hold all of the time. Nool turns your rules into code that checks every agent change before it lands, and gives the same answer every time.

  • Simple rules in YAML: block secrets, protect folders, require tests.
  • Your own logic compiled to WASM, running sealed off from the network, files and clock.
  • Every verdict is kept with the change, so you can show later what was checked.
A YAML rule, and what an agent sees when it breaks it
# policies/no_secret_keys.yaml (excerpt)
inv_id: POL-WS-001
title: Prevent AWS, GitHub, OpenAI, Slack, and generic private keys
mode: blocking
forbids:
  - diff_matches: "AKIA[0-9A-Z]{16}"
  - diff_matches: "-----BEGIN (RSA|EC|OPENSSH|DSA|PRIVATE) KEY-----"

$ nool propose --path src/config.ts --intent "Add AWS config" --solidify
  [Aram DSL] POL-WS-001 failed: diff_matches(AKIA[0-9A-Z]{16})
Error: Gate stack denied [POLICY_DENY] at stage pre-propose: reason code 403

Output from a run on nool 7.24.0. The same change gave the same refusal on a second run; with the key removed, the change landed.

Why instructions are not enough

Coding agents read your instructions: a CLAUDE.md, a rules file, a system prompt. Most of the time they follow them. But an instruction is a request, not a guarantee. A model can miss it in a long session, weigh it against another instruction, or decide differently on a second try. AI reviewers have the same weakness from the other side: the same change can pass one review and fail the next. That is fine for advice. It is not fine for the rules that must never be broken, such as keeping credentials out of the code, leaving protected folders alone, or shipping tests with risky changes. Those rules need something that does not change its mind.

What policy as code means here

You write each rule once, as a small file in a policies folder in your repository. Nool checks every change against those rules before the change lands, whether a person or an agent made it. The check is deterministic: no model takes part in the decision, so the same change gets the same verdict every time. A change that breaks a rule is refused with a message that names the rule and says what to fix. The agent can correct the change and try again; nothing it did is lost, and nothing that breaks the rule gets in.

Two kinds of rule

Most rules are simple, so they are written in YAML: block anything that looks like an AWS key or a private key, keep changes out of a folder, require a test when certain code changes. Edit the file and the next change is checked against the new rule; there is nothing to compile. When a rule needs real logic, such as looking at every file in a change together, you write it in code and compile it to WebAssembly (WASM). A WASM rule runs sealed off: it cannot reach the network, read files, look at the clock or use randomness, which is what keeps its answer the same everywhere it runs. Both kinds live in the same folder and are checked in a fixed order.

What we saw when we ran it

We tried both kinds of rule on the released Nool, 7.24.0, in a fresh repository. A YAML rule refused a change that contained an AWS access key and named the exact pattern it matched. A WASM rule refused billing code that arrived without a test, with its own plain message, and let the same code land once the test was part of the change. Each refusal came back identical on a second run. The change that landed carried a record of both rules and their verdicts, which is what you show an auditor later.

What the benchmark shows, including the limit

In the coordination benchmark, an authored policy blocked all ten violations in 36 of 40 seed-runs with zero false alarms; five violations shipped across the other four, so a rule is strong but not absolute, and it only catches what it was written to catch. Governance is also off in fast mode, which defers checks for quick local work. The practical rule is simple: keep fast mode for local iteration, and let full checks and your rules run wherever a change can reach something that matters.

Where it fits, and where people still decide

Rules are one of the checks a change passes on its way in, next to tests and the scope the agent declared before it started. They can also run earlier, on an agent's edit before it touches the file, and later, before history is pushed to a remote. An organisation can set a floor of rules for every repository; a repository can add rules on top but cannot remove the floor. Some decisions still belong to people: a change to sensitive paths can wait for a named reviewer's approval, recorded on the change itself.

Instructions and AI review vs Nool, side by side

CriterionInstructions and AI reviewNool
Who decidesA model, each timeYour rule, as code
Same change twiceCan get different answersSame verdict every time
When it actsWhile writing, or after the fact in reviewBefore the change lands
What happens on a breakA comment, if it is noticedThe change is refused, with the rule named
Record afterwardsChat logs or review threadsEach verdict kept with the change

Frequently asked questions

Do I need to know Rust or WebAssembly?
No. Most rules are a few lines of YAML. You only reach for WASM when a rule needs real logic, and Nool scaffolds and builds the project for you with nool admin plugin init and nool admin plugin build.
Does this replace code review?
No. Review is where people judge design and intent. Policy as code takes the rules that must never be broken off the reviewer's plate, so nobody has to catch a leaked key or a missing test by eye.
Which agents does it work with?
Any agent that lands work through Nool: Claude Code, Codex, Cursor and others through the CLI or the MCP server, plus nool code, which checks an agent's edits against your rules before they run.
Can an agent switch a rule off?
Not quietly. Changing the policy set or nool.toml is itself a change that needs a typed justification, and an organisation floor cannot be weakened from inside a repository. A rule that cannot be loaded refuses by default rather than letting work through.
Does the policy engine phone home?
No. Rules run locally, the licence verifies locally, and nothing is sent anywhere unless you opt into a hub for multi-machine coordination.