Policy engine for governing AI agent tool execution.
Project description
Guardian Angel
A lightweight Python SDK for governing AI agent tool execution.
Guardian Angel intercepts agent actions, evaluates policy, and returns allow, deny, or require_approval — before the tool runs.
Install
pip install guardian-angel
# optional CLI
pip install guardian-angel[cli]
Quickstart
# policy.yaml
rules:
- name: block_risky_delete
tool: resource.delete
decision: deny
all:
- key: resource.environment
op: eq
value: prod
- key: context.risk_level
op: eq
value: high
// policy.json
{
"rules": [
{
"name": "block_risky_delete",
"tool": "resource.delete",
"decision": "deny",
"all": [
{ "key": "resource.environment", "op": "eq", "value": "prod" },
{ "key": "context.risk_level", "op": "eq", "value": "high" }
]
}
]
}
from guardian_angel import ActionRequest, DecisionStatus, GuardConfig, GuardianAngel
# From YAML
guard = GuardianAngel.from_yaml(
"policy.yaml",
config=GuardConfig(
default_decision=DecisionStatus.ALLOW,
on_evaluation_error=DecisionStatus.DENY,
),
)
# Or from JSON
guard = GuardianAngel.from_json(
"policy.json",
config=GuardConfig(
default_decision=DecisionStatus.ALLOW,
on_evaluation_error=DecisionStatus.DENY,
),
)
decision = guard.authorize(
ActionRequest(
tool="resource.delete",
attributes={
"resource.environment": "prod",
"context.risk_level": "high",
},
)
)
print(decision.status) # "deny"
First matching rule wins. No match uses default_decision, which defaults to allow.
CLI
guardian-angel evaluate policy.yaml request.json
guardian-angel evaluate policy.yaml request.json --explain
guardian-angel --verbose evaluate policy.yaml request.json
guardian-angel --version
--explain prints the matched rule and reason. --verbose adds input context.
Features
- Predicate rules —
when,all,any,notwith operators (eq,ne,in,not_in,contains,gt,gte,lt,lte, …) - Explicit failure semantics — configurable default/no-match behavior, evaluation-error behavior, protected tools, and required request fields
- Cross-field comparison —
value_fromto compare one attribute against another - Approval signal — rules returning
require_approvalraiseApprovalRequiredError, letting the calling framework handle human-in-the-loop approval in whatever way is native to it (LangGraph interrupt, CrewAI human input, webhook, etc.) - Tool invocation —
guard.invoke()(sync) andguard.ainvoke()(async) for policy enforcement on any function without decorators - YAML, JSON, or Python — define rules in files (
from_yaml,from_json) or constructRuleobjects in code - CLI — evaluate policies from the command line with colored output
See examples/ for more.
For YAML policies see examples/yaml_policy_example.py; for JSON see examples/json_policy_example.py.
If you want one end-to-end reference that wires everything together, start with examples/complete_pipeline_example.py.
How It Works
Agent tool call → ActionRequest → GuardianAngel.authorize() → Decision
├─ allow → execute
├─ deny → PolicyDeniedError
└─ require_approval → ApprovalRequiredError
Safety Modes
Guardian Angel separates:
- no rule matched
- policy evaluation failed
from guardian_angel import DecisionStatus, GuardConfig, GuardianAngel
# Global allow, but protected tools require approval when no rule matches.
guard = GuardianAngel(
rules=rules,
config=GuardConfig(
default_decision=DecisionStatus.ALLOW,
on_evaluation_error=DecisionStatus.DENY,
protected_tool_prefixes=("github.", "filesystem."),
protected_no_match_decision=DecisionStatus.REQUIRE_APPROVAL,
),
)
# Full fail-closed mode.
fail_closed_guard = GuardianAngel(
rules=rules,
config=GuardConfig(default_decision=DecisionStatus.DENY),
)
Operator Semantics
- Missing keys do not match ordinary comparisons such as
eq,gt,in, orcontains. - Use
existsandnot_existswhen presence itself matters. - Type mismatches are converted into deterministic evaluation errors.
- Critical request fields can be required globally with
GuardConfig(required_fields=(...)).
Approval Signal
When a rule returns require_approval, Guardian Angel raises ApprovalRequiredError with the full Decision attached. It does not handle the approval workflow itself — the calling framework (LangGraph interrupt, CrewAI human input, webhook, Slack bot, etc.) decides how to obtain human approval.
from guardian_angel import ApprovalRequiredError
try:
result = guard.invoke(
update_resource,
"doc-1",
guard_ctx=GuardContext(
tool="resource.update",
attributes={"resource.environment": "prod", "subject.role": "developer"},
),
)
except ApprovalRequiredError as exc:
print(f"Approval needed: {exc.decision}")
# Hand off to your framework's approval mechanism
See examples/approval_example.py (sync) and examples/async_approval_example.py (async) for full working examples.
guard.invoke() / guard.ainvoke()
invoke and ainvoke call any function under policy enforcement without decorating it.
Policy context is passed via guard_ctx; the function itself stays completely clean:
from guardian_angel import GuardContext
def update_resource(resource_id):
return {"updated": True, "resource_id": resource_id}
# Sync
result = guard.invoke(
update_resource,
"doc-1",
guard_ctx=GuardContext(
tool="resource.update",
attributes={"resource.environment": "prod", "subject.role": "developer"},
request_id="req-1",
),
)
# Async — works with both sync and async functions
async def update_resource_async(resource_id):
return {"updated": True, "resource_id": resource_id}
result = await guard.ainvoke(
update_resource_async,
"doc-1",
guard_ctx=GuardContext(
tool="resource.update",
attributes={"resource.environment": "prod"},
),
)
If guard_ctx.tool is not set, the function's __name__ is used as the policy tool name.
CLI Validation
The CLI now validates request payloads before evaluation.
- Exit code
2: invalid request input - Exit code
3: invalid policy input
Roadmap
- v0.1 — Local policy evaluation, YAML rules, decorator
- v0.2 — Stronger validation, policy linting
- v0.3 — CLI with
evaluate,--explain,--verbose - v0.4 — Approval signal via
ApprovalRequiredError(current) - v0.5 — Framework adapters (LangGraph, OpenAI, CrewAI)
License
MIT
Project details
Release history Release notifications | RSS feed
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 guardian_angel-0.4.5.tar.gz.
File metadata
- Download URL: guardian_angel-0.4.5.tar.gz
- Upload date:
- Size: 80.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
655a65c9e2e650253832b43bfc8c3b25d7e38ad136fe60407cbb46b358faa11f
|
|
| MD5 |
783db8560e4fe2ae7c5a93319310103f
|
|
| BLAKE2b-256 |
8b64f2359c69cf7b06d387eea0bf665190fe3f6e170d1c5c8c5bc0690bc4a60a
|
File details
Details for the file guardian_angel-0.4.5-py3-none-any.whl.
File metadata
- Download URL: guardian_angel-0.4.5-py3-none-any.whl
- Upload date:
- Size: 20.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ad6405299e5a499bfffa8affd266add950014067e4294e7f9a2bd126fb01b608
|
|
| MD5 |
a541a6e6dbc996c621e23136ae74e86d
|
|
| BLAKE2b-256 |
30a709ec58e1c3c887097b1f7b3d7536e915b0983357e4374d41afc3e84f10e1
|