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 function —
evaluate(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_modeisrequire_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:
- Hard guardrails — cannot be overridden by repo policy.
push.force→ alwaysdeny.merge.pr→ alwaysrequire_approval.- External
first_write_to_repoon a mutating capability →require_approval. Read is not blocked.
- Repo policy match — every
[[repo_policy]]entry for the requested repo is scanned (optionally gated byownership_class). The first entry that declares the capability wins. Splitting a repo's policy across multiple entries is supported. default_modefallback — used when no repo policy declares the capability. Defaults torequire_approvalif 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 mapsPolicyDecisionto 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91637f92e58de29d9c8e4675e4867c6bfaa02b174ede3c21175a4b1bd3892bb3
|
|
| MD5 |
d1bea866493ed44f4c15bb5e78ad43e7
|
|
| BLAKE2b-256 |
5b5efe5ff032e95c316b18ca0e192ef09898e3fc0641c4743090c33aa429536c
|
Provenance
The following attestation bundles were made for yui_agent_policy-0.1.1.tar.gz:
Publisher:
release.yml on yui-stingray/agent-policy
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yui_agent_policy-0.1.1.tar.gz -
Subject digest:
91637f92e58de29d9c8e4675e4867c6bfaa02b174ede3c21175a4b1bd3892bb3 - Sigstore transparency entry: 1256316903
- Sigstore integration time:
-
Permalink:
yui-stingray/agent-policy@4aadc1c7edc21793452c5fdf4c54860fd7333c03 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/yui-stingray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4aadc1c7edc21793452c5fdf4c54860fd7333c03 -
Trigger Event:
push
-
Statement type:
File details
Details for the file yui_agent_policy-0.1.1-py3-none-any.whl.
File metadata
- Download URL: yui_agent_policy-0.1.1-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1d5f57d8b85908c03deb7f674708f3064ee01cb918aee417defdb6c17863f308
|
|
| MD5 |
a257dd0d0cb08ab6e2d55872fdd22c64
|
|
| BLAKE2b-256 |
b40b78a27dacb0664d7a5f9d5cae847f3ddb09ed4dc751e41d891b28fc0e6afa
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
yui_agent_policy-0.1.1-py3-none-any.whl -
Subject digest:
1d5f57d8b85908c03deb7f674708f3064ee01cb918aee417defdb6c17863f308 - Sigstore transparency entry: 1256317083
- Sigstore integration time:
-
Permalink:
yui-stingray/agent-policy@4aadc1c7edc21793452c5fdf4c54860fd7333c03 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/yui-stingray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4aadc1c7edc21793452c5fdf4c54860fd7333c03 -
Trigger Event:
push
-
Statement type: