Core ConceptsEnforcement flow

Enforcement flow

The ten ordered checks every guard.protect() call runs, from agent identity binding to policy evaluation.

Every @guard.protect() call executes ten steps in order:

Agent identity binding check

If agent_id is supplied, it is validated against the agent registry. Draft and deprecated agents are denied. The agent's permitted_roles must include the claimed role. If the calling key has a bound_agent_id, the agent_id in the request must match. Requests without agent_id skip this step entirely.

Cross-agent session isolation

The first agent role to use a session_id owns it. Any other role attempting the same session is immediately denied and logged with policy_name="cross_agent_isolation".

Session lifecycle check

Sessions that are expired or revoked are denied before policy evaluation runs. Status values: active, expired, revoked.

Policy evaluation

The PolicyEngine checks permitted_agent_ids, denied sources, denied tasks, allowed tasks, allowed sources, and sensitivity ceiling in order. No matching policy defaults to deny.

OTEL span emitted

An autopil.evaluate span is started. Decision, latency, and policy name are recorded. DENY sets StatusCode.ERROR on the span.

Retrieval executes (ALLOW only)

The wrapped function runs. On DENY, the data source is never queried.

Context hashing (ALLOW only)

A SHA-256 hash of the returned context is computed (first 16 hex chars stored) for provenance verification.

Audit event recorded

Every decision — allow or deny — is written with policy name, reason, context hash, agent_id, and timestamp. Append-only; no delete API.

Alert rules evaluated

The AlertEngine checks threshold rules against the audit log. Webhook and email delivery is dispatched in a background thread.

PermissionError raised (DENY only)

The caller receives a PermissionError with the policy name and denial reason.