Choosing an integration pattern
Choose between the embedded decorator and the REST API for your deployment and identity model.
Two integration paths
AutoPIL has two primary integration paths for Python — the embedded decorator and the REST API. They enforce the same policies and write to the same audit log, but the right choice depends on how your stack is deployed and how user identity flows through your system.
Decision guide
Decision guide:
| Scenario | Use | Why |
|---|---|---|
| Local development or single-process self-hosted deployment | Embedded decorator (@guard.protect) | No network hop; policy files load directly from disk in the same process |
| AutoPIL deployed on Render or any remote host | REST API (POST /v1/context/evaluate) | The embedded ContextGuard would open its own SQLite/Postgres — separate from the server's. All agents must call the shared server |
| Service-account style access — one known identity per retrieval function | Embedded decorator | agent_role and user_id are fixed at decoration time; all calls to that function share the same audit identity |
| Multi-user app — different end users hit the same retrieval path | REST API | user_id comes in the request payload per call, so each request gets a distinct identity in the audit log |
| Polyglot stack (Go, Java, TypeScript, Ruby) | REST API | The embedded SDK is Python-only; any language can call /v1/context/evaluate |
| Already using LangChain, LlamaIndex, Bedrock, or OpenAI Agents SDK | Framework guard for that library | Wraps at the tool or query-engine level without changing your retrieval code |
| Enforcing governance without touching agent code | AutoPILMiddleware (ASGI) | Intercepts every request at the HTTP layer; agents need no changes |
How user_id flows
The user_id distinction: user_id identifies who triggered a retrieval in the audit log — it does not affect allow/deny decisions. How it flows differs between the two patterns:
| Embedded decorator | REST API | |
|---|---|---|
| Set at | Decoration time — fixed per function | Request time — varies per call |
| Good for | Service accounts, internal agents with a single identity | Multi-user apps where attribution matters per request |
| Tradeoff | All calls to the function share one user_id in the audit trail | Each call carries its own user_id — more granular audit trail |
agent_role works the same way. In both patterns, agent_role is developer-supplied — it declares the identity of the access path, not a runtime claim by the agent. The agent cannot change it. In the decorator, it is fixed at decoration time. In the REST API, it is a declared field in the request payload trusted because the API key is already authenticated.
Side-by-side example
Side-by-side example — the same governance check (analyst accessing financial reports at high sensitivity) written both ways:
from autopil import ContextGuard
guard = ContextGuard(policy_path="policies/", audit_db="autopil.db")
# agent_role and user_id are fixed here — the agent cannot change them
@guard.protect(
agent_role="analyst",
user_id="svc-reporting-agent", # same for every call
source_id="financial_reports",
sensitivity_level="high",
)
def retrieve_report(query: str):
return vectorstore.search(query)
import httpx
def retrieve_report(query: str, user_id: str, session_id: str):
r = httpx.post(
"https://your-app.onrender.com/v1/context/evaluate",
headers={"X-API-Key": AUTOPIL_API_KEY},
json={
"agent_role": "analyst",
"user_id": user_id, # varies per caller
"source_id": "financial_reports",
"sensitivity_level": "high",
"query": query,
"session_id": session_id,
},
timeout=10,
)
r.raise_for_status()
result = r.json()
if result["decision"] == "DENY":
raise PermissionError(result["reason"])
return vectorstore.search(query)
Never use the embedded decorator against a remote AutoPIL instance. If your AutoPIL server is on Render, calling ContextGuard(audit_db="...") locally opens a separate database — your events will not appear in the shared audit log and policies will not be in sync. Use the REST pattern for any hosted deployment.