Skip to main content

Pure-function policy matrix evaluator for AI coding agents (repo x capability x context -> deny/require_approval/auto_allow).

Project description

agent-policy

Pure-function policy matrix for AI coding agents. Maps (repo, capability, context) to one of three modes: deny / require_approval / auto_allow.

Status: 0.1.0 alpha. The public API is frozen for v0.1; examples and hook/wrapper recipes will grow in v0.2.

Why

AI coding agents (Claude Code, Codex, Aider, and friends) need a single place to answer one question, the same way, every time:

"The agent wants to do X in repo Y — should I let it?"

agent-policy is that single place. It is deliberately tiny:

  • One pure functionevaluate(policy, repo, capability, context).
  • No I/O, no logging, no global state. The evaluator does not touch disk, network, or clocks. It is safe to call from a hook, a test, or a long-running daemon.
  • Fail-closed defaults. A missing default_mode is require_approval, unknown fields in policy files are rejected, and hard guardrails cannot be overridden by repo policy.

It does not parse shell commands, manage state, or send messages. Those belong in the wrapper layer that calls evaluate.

Install

pip install yui-agent-policy  # once published to PyPI

From a source checkout (until the PyPI release is live), install the package in editable mode so both the library and examples/check.py can resolve import agent_policy:

pip install -e .

Requires Python 3.11+ (uses stdlib tomllib). The only runtime dependency is pydantic >= 2.

Quick start

from agent_policy import evaluate, PolicyMatrix, RepoPolicy

policy = PolicyMatrix(
    default_mode="require_approval",
    repo_policy=[
        RepoPolicy(
            repo="acme/app",
            ownership_class="internal",
            capabilities={
                "read": "auto_allow",
                "commit": "auto_allow",
                "push": "auto_allow",
                "shell": "require_approval",
            },
        ),
    ],
)

decision = evaluate(
    policy,
    repo="acme/app",
    capability="commit",
    context={"ownership_class": "internal"},
)

print(decision.mode)         # "auto_allow"
print(decision.reason)       # "repo_policy"
print(decision.matched_repo) # "acme/app"

Load the same policy from a TOML file:

from agent_policy import evaluate, load_policy_file

policy = load_policy_file("policy.toml")
decision = evaluate(policy, repo="acme/app", capability="commit")

evaluate also accepts a plain dict in the same shape as PolicyMatrix, which is convenient for tests and one-off scripts.

Decision model

Every call returns a frozen PolicyDecision with three fields:

Field Type Meaning
mode "deny" | "require_approval" | "auto_allow" What the caller should do.
reason "hard_guardrail" | "repo_policy" | "default_mode" | ... Which rule produced the decision.
matched_repo str | None The repo string that matched, or None.

Decisions are evaluated in this order:

  1. Hard guardrails — cannot be overridden by repo policy.
    • push.force → always deny.
    • merge.pr → always require_approval.
    • External first_write_to_repo on a mutating capability → require_approval. Read is not blocked.
  2. Repo policy match — every [[repo_policy]] entry for the requested repo is scanned (optionally gated by ownership_class). The first entry that declares the capability wins. Splitting a repo's policy across multiple entries is supported.
  3. default_mode fallback — used when no repo policy declares the capability. Defaults to require_approval if unset.

HARD_GUARDRAILS is exported as a constant so tooling can assert against it without importing private symbols.

Policy file format

# policy.toml
default_mode = "require_approval"

[[repo_policy]]
repo = "acme/app"
ownership_class = "internal"

[repo_policy.capabilities]
read = "auto_allow"
commit = "auto_allow"
push = "auto_allow"

[[repo_policy]]
repo = "acme/app"                # same repo, extra constraint
[repo_policy.capabilities]
shell = "require_approval"

Unknown top-level fields or typos inside [[repo_policy]] fail loudly with a pydantic.ValidationError — there is no silent degradation.

Wrapper pattern

agent-policy deliberately does not know how to parse git push --force or a shell command line. The intended shape is:

           ┌────────────────────────┐
agent ───▶ │ wrapper (hook / CLI)   │ ──▶ agent-policy.evaluate()
           │  - normalize capability│         │
           │  - build context       │         ▼
           │  - act on decision     │   PolicyDecision
           └────────────────────────┘

The wrapper owns: parsing the agent's intent, mapping it to one of the MVP capabilities (read, write, commit, push, push.force, merge.pr, shell), and executing whatever side effect the decision implies (block, prompt for approval, log and allow).

A runnable minimal wrapper lives in examples/check.py.

Examples

See examples/. Runnable after installing the package (pip install yui-agent-policy, or pip install -e . from a source checkout):

  • policy.toml — a minimal fail-closed policy with two repos.
  • check.py — a tiny CLI wrapper that maps PolicyDecision to JSON on stdout and a process exit code, suitable for PreToolUse hooks.

License

MIT.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

yui_agent_policy-0.1.1.tar.gz (14.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

yui_agent_policy-0.1.1-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

File details

Details for the file yui_agent_policy-0.1.1.tar.gz.

File metadata

  • Download URL: yui_agent_policy-0.1.1.tar.gz
  • Upload date:
  • Size: 14.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for yui_agent_policy-0.1.1.tar.gz
Algorithm Hash digest
SHA256 91637f92e58de29d9c8e4675e4867c6bfaa02b174ede3c21175a4b1bd3892bb3
MD5 d1bea866493ed44f4c15bb5e78ad43e7
BLAKE2b-256 5b5efe5ff032e95c316b18ca0e192ef09898e3fc0641c4743090c33aa429536c

See more details on using hashes here.

Provenance

The following attestation bundles were made for yui_agent_policy-0.1.1.tar.gz:

Publisher: release.yml on yui-stingray/agent-policy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file yui_agent_policy-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for yui_agent_policy-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 1d5f57d8b85908c03deb7f674708f3064ee01cb918aee417defdb6c17863f308
MD5 a257dd0d0cb08ab6e2d55872fdd22c64
BLAKE2b-256 b40b78a27dacb0664d7a5f9d5cae847f3ddb09ed4dc751e41d891b28fc0e6afa

See more details on using hashes here.

Provenance

The following attestation bundles were made for yui_agent_policy-0.1.1-py3-none-any.whl:

Publisher: release.yml on yui-stingray/agent-policy

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page