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
Metadata
Release files for guardian-angel 0.4.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| guardian_angel-0.4.5.tar.gz | 80.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| guardian_angel-0.4.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 101.5 kB
Release files / guardian_angel-0.4.5.tar.gz
| Download URL | guardian_angel-0.4.5.tar.gz |
|---|---|
| Size | 80.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
655a65c9e2e650253832b43bfc8c3b25d7e38ad136fe60407cbb46b358faa11f
|
|
BLAKE2b-256 checksum How to use checksums |
8b64f2359c69cf7b06d387eea0bf665190fe3f6e170d1c5c8c5bc0690bc4a60a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|
Release files / guardian_angel-0.4.5-py3-none-any.whl
| Download URL | guardian_angel-0.4.5-py3-none-any.whl |
|---|---|
| Size | 20.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ad6405299e5a499bfffa8affd266add950014067e4294e7f9a2bd126fb01b608
|
|
BLAKE2b-256 checksum How to use checksums |
30a709ec58e1c3c887097b1f7b3d7536e915b0983357e4374d41afc3e84f10e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is 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}
|