Infrastructure for autonomous AI agents: identity, policy, permissions, approval workflows, and audit.
Project description
Aegize
Infrastructure for autonomous AI agents.
Aegize is the runtime layer between autonomous AI agents and the tools they use.
It provides identity, policy enforcement, permissions, approval workflows, audit logging, observability, and runtime governance for every AI action.
Every agent action must have identity, permission, policy enforcement, and audit.
Architecture
See it in action
One agent attempts three tool calls. Aegize allows the web search, holds the email for approval, blocks the shell command, and writes an audit record for every attempt.
$ python examples/demo_story.py
[1] web_search.search query='Aegize runtime'
ALLOWED -> results for 'Aegize runtime'
[2] email.send to='ceo@example.com'
APPROVAL REQUIRED -> held for human review (not executed)
reason: approval required for tool 'email'
[3] shell.execute cmd='rm -rf /var/data'
DENIED -> blocked before execution
reason: denied by rule for tool 'shell'
Audit trail:
allowed web_search search
execution_succeeded web_search search
approval_required email send
denied shell execute
3 actions attempted · 1 allowed · 1 awaiting approval · 1 denied · 4 audit records written
The gated and denied calls never reach the underlying functions. See Demo to run it yourself.
Why Aegize?
AI systems are moving from answering questions to taking actions — running shells, sending email, moving money, calling internal APIs. A model that only returns text is easy to contain. An agent that acts is not: it needs an identity, scoped permissions, approvals for high-impact operations, and a record of everything it did.
That governance layer is usually missing today, and the model's own judgment stands in for it. Aegize is the runtime infrastructure that fills the gap, so organizations can let agents take actions without giving up control or visibility. Security is one capability this provides; operability, reviewability, and confidence in deployment are the rest.
Project Vision
Every meaningful AI action should pass through trusted runtime infrastructure before reaching the outside world.
What Aegize provides
AgentIdentity— a durable identity for each agent (owner, environment, metadata).PermissionPolicy— a YAML policy engine that returnsallow,deny, orrequire_approval.GuardedTool/@guarded_tool— the enforcement point: wrap any callable so it is identified, permissioned, gated, and audited.AuditLog— an append-only JSONL record of every attempt and outcome.- Typed, dependency-light, and easy to extend. One runtime dependency (PyYAML).
Install
pip install aegize
Or from source (for development):
git clone https://github.com/gggaswint/aegize.git
cd aegize
pip install -e ".[dev]"
Quickstart
from aegize import AgentIdentity, PermissionPolicy, GuardedTool, AuditLog
agent = AgentIdentity(
agent_id="research_bot",
name="Research Bot",
owner="Geoffrey",
environment="dev",
)
policy = PermissionPolicy.from_yaml("aegize.yaml")
audit = AuditLog("audit.jsonl")
def web_search(query: str) -> str:
return f"searched: {query}"
safe_web_search = GuardedTool(
tool_name="web_search",
operation="search",
func=web_search,
agent=agent,
policy=policy,
audit_log=audit,
risk_level="low",
)
result = safe_web_search("Aegize runtime")
If the policy allows the action, the function runs and two audit records are
written (authorization + result). If not, Aegize raises PolicyDenied or
ApprovalRequired and the function never executes.
from aegize import PolicyDenied, ApprovalRequired
try:
safe_web_search("Aegize runtime")
except ApprovalRequired as exc:
# route to a human approval workflow
...
except PolicyDenied as exc:
# blocked outright
...
Decorator quickstart (v0.2)
You don't have to wrap every function by hand. Declare a tool once with
@guarded_tool, bundle your agent/policy/audit into a GuardContext, and bind
them together when you have a context.
from aegize import (
AgentIdentity, PermissionPolicy, AuditLog,
GuardContext, guarded_tool, guard, ApprovalRequired,
)
@guarded_tool(tool_name="email", operation="send", risk_level="high")
def send_email(to: str, body: str) -> str:
... # your real implementation
ctx = GuardContext(
agent=AgentIdentity(agent_id="research_bot", name="Research Bot", owner="Geoffrey"),
policy=PermissionPolicy.from_yaml("aegize.yaml"),
audit_log=AuditLog("audit.jsonl"),
)
# Bind to a context -> a plain, signature-preserving callable you can register.
guarded_send = guard(send_email, context=ctx)
# e.g. server.add_tool(guarded_send) # MCP / any tool registry
try:
guarded_send("ceo@example.com", "Q3 numbers")
except ApprovalRequired:
... # gated for human approval; send_email never ran
guard() returns a callable that preserves the original __name__,
docstring, and signature, so tool registries (including MCP servers) that
introspect functions keep working.
Default context. Inside a with ctx: block (or after ctx.activate()),
decorated tools can be called directly:
with ctx:
send_email("ceo@example.com", "Q3 numbers") # uses the active context
Per-call metadata. Pass guard_metadata= to any guarded call to attach
context for policy decisions (e.g. a path for an allowlist) and the audit log.
It is stripped before your function runs:
guarded_read("report", guard_metadata={"path": "./safe_data/report.txt"})
Both styles are fully supported — use GuardedTool(...) directly when you want
explicit objects, or the decorator when you want ergonomics. They share the same
enforcement and audit code.
Policy YAML
Policies are per-agent. Each agent has allow, require_approval, and deny
sections. Evaluation order is deny → require_approval → allow → default-deny,
so an explicit deny always wins and anything unlisted is denied.
agents:
research_bot:
allow:
- tool: web_search
operations: ["search"]
risk_level_max: medium # block this rule above 'medium' risk
- tool: file_reader
operations: ["read"]
paths:
- "./safe_data/**" # only inside the allowlisted path
require_approval:
- tool: email
operations: ["send"]
- tool: shell
operations: ["execute"]
deny:
- tool: payments
operations: ["charge"]
- tool: shell
operations: ["rm", "delete"]
Rule fields:
| Field | Applies to | Meaning |
|---|---|---|
tool |
all | Tool name to match. |
operations |
all | Operations the rule covers. Omit to match every operation. |
risk_level_max |
allow |
Highest risk this rule permits (low…critical). |
paths |
allow |
Glob allowlist; a string argument must match one of these. |
Path matching: when a rule has
paths, Aegize checks the string arguments of the call (andmetadata["path"]) against the glob patterns. A call with no matching path is denied.
Policy tests
A policy is only as trustworthy as the behavior you can prove it has. Aegize ships a small CLI to assert that a policy makes the decisions you expect — before it ever gates a real agent. Write the expectations as YAML:
# policy_tests.yaml
tests:
- name: web search is allowed
agent: research_bot
tool: web_search
operation: search
expect: allow
- name: charging a card is denied
agent: research_bot
tool: payments
operation: charge
expect: deny
- name: reading outside the allowlist is denied
agent: research_bot
tool: file_reader
operation: read
metadata: { path: "/etc/passwd" }
expect: deny
Run them against a policy file:
aegize policy test examples/aegize.yaml examples/policy_tests.yaml
PASS web search is allowed (expected allow)
PASS charging a card is denied (expected deny)
PASS reading outside the allowlist is denied (expected deny)
3 passed, 0 failed
The command only evaluates policy — it never executes a tool. It exits 0
when every case passes, 1 when any decision doesn't match, and 2 when a file
is missing or malformed, so it drops straight into CI as a policy regression
gate. Each case takes:
| Field | Required | Meaning |
|---|---|---|
agent |
yes | The acting agent_id to evaluate. |
tool |
yes | Tool name. |
operation |
yes | Operation name. |
expect |
yes | Expected decision: allow, deny, or require_approval. |
name |
no | Label shown in the report (defaults to test #N). |
risk_level |
no | low…critical (default low); exercises risk_level_max rules. |
metadata |
no | Extra call context, e.g. { path: ... } for paths allowlists. |
This is the first step of the policy-as-code lifecycle explored in RFC 0008.
Audit log
Every attempt is appended to a JSONL file — one self-contained JSON object per
line, easy to tail, grep, or ship to a SIEM. A single allowed call:
{"timestamp": "2026-06-27T18:00:00+00:00", "event": "allowed", "agent_id": "research_bot", "tool_name": "web_search", "operation": "search", "risk_level": "low", "input_summary": "'Aegize runtime'", "reason": "allowed by rule for tool 'web_search'"}
{"timestamp": "2026-06-27T18:00:00+00:00", "event": "execution_succeeded", "agent_id": "research_bot", "tool_name": "web_search", "operation": "search", "risk_level": "low", "result_summary": "'searched: Aegize runtime'"}
Events: allowed, denied, approval_required, execution_succeeded,
execution_failed. The authorization decision is always written before the
function runs; the result is written after. Reading the log back is one call:
for record in audit.read_all():
print(record["event"], record["tool_name"], record["operation"])
Demo
The 60-second story — one agent, three tool calls, three outcomes, all audited:
python examples/demo_story.py
It runs the See it in action flow above against
examples/demo_policy.yaml and prints the path to
the audit log it wrote.
Examples
More runnable scripts live in examples/:
python examples/basic_allow.py # allowed web_search runs
python examples/denied_shell.py # denied shell command is blocked
python examples/approval_email.py # email send raises ApprovalRequired
python examples/decorator_usage.py # @guarded_tool + GuardContext (v0.2)
python examples/demo_story.py # the full allow / approve / deny story
Enforcement guarantees
- Default deny. No matching
allowrule means the action is denied. - Deny wins. An explicit
denyoverridesrequire_approvalandallow. - Gated actions never execute.
denyandrequire_approvalraise before the wrapped function is called. - Audit-first. The decision is recorded before any execution is attempted; the result is recorded after.
Project documents
The operating documents for Aegize — useful for contributors and for understanding where the project is headed.
Direction
- Vision — the thesis, the problem, and the long-term ambition.
- Roadmap — from the current SDK to runtime governance.
- Architecture — primitives, runtime flow, and trust model.
Operating
- Principles — the engineering and product tie-breakers.
- Anti-goals — what Aegize is deliberately not.
- Brand — positioning, messaging, and visual language.
Process & record
- Decisions — the record of why things are the way they are.
- Open questions — unresolved product and architecture questions.
- RFCs — how significant changes are proposed and recorded.
- Launch checklist — what's done and what's left to launch.
- Next steps — the focused two-week execution plan.
- CLAUDE.md — operating instructions for AI coding sessions.
Roadmap
✅ v0.2@guarded_tooldecorator +GuardContextergonomics.- Policy schema validation and a
aegize lintCLI. - First-class adapters for popular agent frameworks (a thin MCP registration
helper on top of the v0.2
guard()callable). - Pluggable approval backends (Slack, webhook, queue) for
require_approval. - Pluggable audit sinks (stdout, syslog, S3, SIEM) beyond local JSONL.
- Per-environment policy overlays (
dev/staging/prod). - Rate limits and budget/quota controls per agent and tool.
- Signed, tamper-evident audit logs.
Development
pip install -e ".[dev]"
pytest # run the test suite
ruff check . # lint
CI runs the same pytest + ruff checks on every push and pull request across
Python 3.9–3.12.
Contributing
Contributions are welcome. See CONTRIBUTING.md for the dev setup, the project's scope and design principles, and the bar for a mergeable change.
Before making major changes, read CLAUDE.md and the project documents in
docs/. They are the source of truth for Aegize's direction, positioning, and design. (python scripts/context_check.pyconfirms they're present.)
Reporting vulnerabilities
Aegize governs what agents are allowed to do, so we treat weaknesses in it seriously. Please report vulnerabilities privately — see SECURITY.md. Do not open a public issue for a suspected vulnerability.
License
MIT — see LICENSE.
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 aegize-0.3.0.tar.gz.
File metadata
- Download URL: aegize-0.3.0.tar.gz
- Upload date:
- Size: 26.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d7b8ed2e0c6b2b8f43a627b9d68272965bd46d95159b9658950998e601d889c
|
|
| MD5 |
f70ccace022a85847f09e5412f57c9ef
|
|
| BLAKE2b-256 |
556f202ffc3b21a43255c6917d5cc962cf9c6c78b06e5cf05d38856e704c8103
|
Provenance
The following attestation bundles were made for aegize-0.3.0.tar.gz:
Publisher:
publish.yml on gggaswint/aegize
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aegize-0.3.0.tar.gz -
Subject digest:
9d7b8ed2e0c6b2b8f43a627b9d68272965bd46d95159b9658950998e601d889c - Sigstore transparency entry: 2028852093
- Sigstore integration time:
-
Permalink:
gggaswint/aegize@5c0975224ee67a8c55d6cc41b9d2bde64e1068f0 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/gggaswint
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5c0975224ee67a8c55d6cc41b9d2bde64e1068f0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file aegize-0.3.0-py3-none-any.whl.
File metadata
- Download URL: aegize-0.3.0-py3-none-any.whl
- Upload date:
- Size: 22.8 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 |
ccb44178db201a2223889cc9390f1750004a825bdf069672fdbb7db8adf28b28
|
|
| MD5 |
1a81c10a234ce6ce7497c812791ae9ef
|
|
| BLAKE2b-256 |
491a8012881ac0749901d763dcff1acb8642717781605e588c393a23f65255ed
|
Provenance
The following attestation bundles were made for aegize-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on gggaswint/aegize
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
aegize-0.3.0-py3-none-any.whl -
Subject digest:
ccb44178db201a2223889cc9390f1750004a825bdf069672fdbb7db8adf28b28 - Sigstore transparency entry: 2028852211
- Sigstore integration time:
-
Permalink:
gggaswint/aegize@5c0975224ee67a8c55d6cc41b9d2bde64e1068f0 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/gggaswint
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5c0975224ee67a8c55d6cc41b9d2bde64e1068f0 -
Trigger Event:
release
-
Statement type: