Agent Policy Layer - Portable, composable policies for AI agents
Project description
APL restrains your agents - when you need him to! ๐
Portable, composable policies for AI agents.
Installation โข Quick Start โข How It Works โข Examples โข API Reference
WITHOUT APL ๐ฐ
WITH APL ๐ก๏ธ
The Problem
You've built an HR agent for your enterprise. It works great in happy paths - updates employee records, applies for time-offs - great! But then:
- ๐ฑ It leaks a customer's SSN in a response
- ๐ธ It burns through your token budget in one conversation
- ๐๏ธ It deletes production data without asking
- ๐ซ It goes off-topic into areas you didn't intend
You need guardrails that can enforce your enterprise's policies.
But existing solutions are:
| Problem | Why It Hurts |
|---|---|
| Framework-specific | Locked into LangGraph? Can't use that CrewAI policy. |
| Code-embedded | Policies buried in your agent code/prompts. Hard to update. |
| Boolean only | Just allow/deny. Can't modify or escalate. |
| No composition | What happens when 3 policies disagree? |
The Solution: APL
APL is a protocol for agent policies โ like MCP, but for constraints instead of capabilities.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Your Agent โ
โ โ
โ "Delete all files" โ
โ โ โ
โ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ APL Policy Layer โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโ โโโโโโโโโโโโ โโโโโโโโโโโโ โ โ
โ โ โ PII โ โ Budget โ โ Confirm โ โ โ
โ โ โ Filter โ โ Limiter โ โ Delete โ โ โ
โ โ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โโโโโโฌโโโโโโ โ โ
โ โ โ โ โ โ โ
โ โ โผ โผ โผ โ โ
โ โ ALLOW ALLOW ESCALATE โ โ
โ โ โ โ โ
โ โ Final: ESCALATE โโโโโโ โ โ
โ โ "Confirm delete?" โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ ๐ก๏ธ Action blocked until user confirms
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key features:
| Feature | Description |
|---|---|
| ๐ Runtime-agnostic | Works with OpenAI, Anthropic, LangGraph, LangChain, or custom agents |
| ๐ฏ Rich verdicts | Not just allow/deny โ also modify, escalate, observe |
| ๐ Declarative policies | Write policies in YAML, no Python required |
| ๐ฅ Hot-swappable | Update policies without redeploying your agent |
| โก Auto-instrumentation | One line to protect all your LLM calls |
๐ฆ Installation
pip install apl
That's it. No Docker, no external services.
๐ Quick Start (2 minutes)
Option A: Auto-Instrumentation (Easiest)
One line protects all your OpenAI/Anthropic calls automatically:
import apl
# This patches OpenAI, Anthropic, LiteLLM, and LangChain
apl.auto_instrument(
policy_servers=["stdio://./my_policy.py"]
)
# Now use your LLM normally โ APL intercepts automatically
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "What's my SSN? It's 123-45-6789"}]
)
# If your policy redacts PII, the response is already clean!
print(response.choices[0].message.content)
# โ "Your SSN is [REDACTED]"
Option B: Create a Policy Server
Step 1: Create my_policy.py:
from apl import PolicyServer, Verdict
import re
server = PolicyServer("my-policies")
@server.policy(
name="redact-ssn",
events=["output.pre_send"],
)
async def redact_ssn(event):
text = event.payload.output_text or ""
if re.search(r'\d{3}-\d{2}-\d{4}', text):
redacted = re.sub(r'\d{3}-\d{2}-\d{4}', '[REDACTED]', text)
return Verdict.modify(
target="output",
operation="replace",
value=redacted,
reasoning="SSN detected and redacted"
)
return Verdict.allow()
if __name__ == "__main__":
server.run()
Step 2: Run it:
apl serve my_policy.py --http 8080
Step 3: Test it:
curl -X POST http://localhost:8080/evaluate \
-H "Content-Type: application/json" \
-d '{
"type": "output.pre_send",
"payload": {"output_text": "Your SSN is 123-45-6789"}
}'
{
"composed_verdict": {
"decision": "modify",
"modification": {
"target": "output",
"value": "Your SSN is [REDACTED]"
}
}
}
๐ How It Works
The Data Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ 1. USER INPUT 2. AGENT PROCESSES 3. AGENT RESPONDS โ
โ โโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโ โ
โ โ
โ "What's my SSN?" โโโบ Agent calls LLM โโโบ "Your SSN is 123-45-6789" โ
โ โ โ โ
โ โ โ โ
โ โผ โผ โ
โ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
โ โ APL HOOK: โ โ APL HOOK: โ โ
โ โ llm.pre_request โ โ output.pre_send โ โ
โ โโโโโโโโโโฌโโโโโโโโโ โโโโโโโโโโฌโโโโโโโโโ โ
โ โ โ โ
โ โ โ โ
โ โผ โผ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ POLICY SERVERS โ โ
โ โ โ โ
โ โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โ โ
โ โ โ Budget โ โ PII โ โ Topic โ โ โ
โ โ โ Check โ โ Filter โ โ Guard โ โ โ
โ โ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โโโโโโฌโโโโโ โ โ
โ โ โ โ โ โ โ
โ โ โผ โผ โผ โ โ
โ โ ALLOW MODIFY ALLOW โ โ
โ โ โ โ โ
โ โ โผ โ โ
โ โ Composed: MODIFY (redact SSN) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โ
โ โผ โ
โ "Your SSN is [REDACTED]" โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Event Types
APL intercepts at key moments in the agent lifecycle:
| Event | When | Use Cases |
|---|---|---|
input.received |
User message arrives | Injection detection, input validation |
llm.pre_request |
Before calling LLM | Budget checks, prompt modification |
llm.post_response |
After LLM responds | Hallucination detection |
tool.pre_invoke |
Before tool execution | Permission checks, arg validation |
tool.post_invoke |
After tool returns | Result validation |
output.pre_send |
Before sending to user | PII redaction, content filtering |
Verdict Types
Policies don't just allow or deny โ they can guide:
# โ
Allow the action
Verdict.allow()
# โ Block the action
Verdict.deny(reasoning="Contains prohibited content")
# ๐ Modify and continue
Verdict.modify(
target="output",
operation="replace",
value="[REDACTED]",
reasoning="PII detected"
)
# โ ๏ธ Require human approval
Verdict.escalate(
type="human_confirm",
prompt="Delete production database?",
options=["Proceed", "Cancel"]
)
# ๐๏ธ Just observe (for audit logging)
Verdict.observe(
reasoning="Logged for compliance",
trace={"action": "sensitive_query"}
)
๐ Examples
1. PII Filter (Redaction)
from apl import PolicyServer, Verdict
import re
server = PolicyServer("pii-filter")
PATTERNS = {
"ssn": r'\b\d{3}-\d{2}-\d{4}\b',
"credit_card": r'\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b',
"email": r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
}
@server.policy(name="redact-pii", events=["output.pre_send"])
async def redact_pii(event):
text = event.payload.output_text or ""
for name, pattern in PATTERNS.items():
text = re.sub(pattern, f'[{name.upper()} REDACTED]', text)
if text != event.payload.output_text:
return Verdict.modify(target="output", operation="replace", value=text)
return Verdict.allow()
2. Budget Limiter
from apl import PolicyServer, Verdict
server = PolicyServer("budget")
@server.policy(name="token-budget", events=["llm.pre_request"])
async def check_budget(event):
used = event.metadata.token_count
budget = event.metadata.token_budget or 100_000
if used >= budget:
return Verdict.deny(reasoning=f"Token budget exceeded: {used:,}/{budget:,}")
if used >= budget * 0.8:
return Verdict.observe(reasoning=f"Token usage at {used/budget:.0%}")
return Verdict.allow()
3. Destructive Action Confirmation
from apl import PolicyServer, Verdict
server = PolicyServer("safety")
@server.policy(name="confirm-delete", events=["tool.pre_invoke"])
async def confirm_delete(event):
tool = event.payload.tool_name or ""
if "delete" in tool.lower() or "drop" in tool.lower():
return Verdict.escalate(
type="human_confirm",
prompt=f"โ ๏ธ Destructive action: {tool}\n\nProceed?",
options=["Proceed", "Cancel"]
)
return Verdict.allow()
4. Declarative YAML Policy (No Python!)
# compliance.yaml
name: corporate-compliance
version: 1.0.0
policies:
- name: block-competitor-info
events:
- output.pre_send
rules:
- when:
payload.output_text:
contains: "competitor revenue"
then:
decision: deny
reasoning: "Cannot share competitor financial information"
- name: confirm-data-export
events:
- tool.pre_invoke
rules:
- when:
payload.tool_name:
matches: ".*export.*"
metadata.user_region:
in: [EU, EEA, UK]
then:
decision: escalate
escalation:
type: human_confirm
prompt: "๐ช๐บ GDPR: Confirm data export for EU user?"
apl serve compliance.yaml --http 8080
๐งฉ Integration Patterns
Pattern 1: Auto-Instrumentation (Recommended)
import apl
# Patches OpenAI, Anthropic, LiteLLM, LangChain automatically
apl.auto_instrument(
policy_servers=[
"stdio://./policies/pii_filter.py",
"http://compliance.internal:8080",
],
user_id="user-123",
)
# All LLM calls are now protected
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(...) # โ APL intercepts this
Pattern 2: Manual Integration
from apl import PolicyLayer, EventPayload, SessionMetadata
policies = PolicyLayer()
policies.add_server("stdio://./my_policy.py")
# Call this before sending output
verdict = await policies.evaluate(
event_type="output.pre_send",
payload=EventPayload(output_text=response_text),
metadata=SessionMetadata(session_id="...", user_id="...")
)
if verdict.decision == "modify":
response_text = verdict.modification.value
Pattern 3: LangGraph Wrapper
from langgraph.graph import StateGraph
from apl.adapters.langgraph import APLGraphWrapper
# Build your graph
graph = StateGraph(MyState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)
# Wrap it with APL
wrapper = APLGraphWrapper()
wrapper.add_server("stdio://./my_policy.py")
wrapped_graph = wrapper.wrap(graph)
# Use wrapped_graph โ policies evaluated automatically
๐ API Reference
CLI Commands
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Command Description โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ serve Run a policy server โ
โ test Test a policy with sample events โ
โ validate Validate a policy file โ
โ init Create a new policy project โ
โ info Show system information โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# Run a policy server with HTTP
apl serve ./policy.py --http 8080
# Test a policy
apl test ./policy.py -e output.pre_send
# Create a new project
apl init my-policy --template pii
# Validate without running
apl validate ./policy.yaml
HTTP API
| Endpoint | Method | Description |
|---|---|---|
/evaluate |
POST | Evaluate policies for an event |
/health |
GET | Health check |
/metrics |
GET | Prometheus metrics |
/manifest |
GET | Server manifest |
Python API
from apl import (
# Core
PolicyServer, # Create policy servers
PolicyLayer, # Connect to policy servers
Verdict, # Policy responses
# Auto-instrumentation
auto_instrument, # Patch LLM clients
uninstrument, # Remove patches
# Types
EventType, # Lifecycle events
EventPayload, # Event-specific data
SessionMetadata, # Session context
Message, # Chat message format
)
๐๏ธ Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ YOUR APPLICATION โ
โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ APL Policy Layer โ โ
โ โ โ โ
โ โ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโ โ โ
โ โ โ Client โ โ Client โ โ Client โ โ โ
โ โ โ (stdio) โ โ (HTTP) โ โ (WebSocket)โ โ โ
โ โ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โโโโโโโโฌโโโโโโโ โ โ
โ โโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโ
โ โ โ
โผ โผ โผ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
โ Policy Server โ โ Policy Server โ โ Policy Server โ
โ (Local Python) โ โ (Remote HTTP) โ โ (YAML) โ
โ โ โ โ โ โ
โ โโโโโโโโโโโโโ โ โ โโโโโโโโโโโโโ โ โ โโโโโโโโโโโโโ โ
โ โ Policy 1 โ โ โ โ Policy A โ โ โ โ Rule 1 โ โ
โ โ Policy 2 โ โ โ โ Policy B โ โ โ โ Rule 2 โ โ
โ โโโโโโโโโโโโโ โ โ โโโโโโโโโโโโโ โ โ โโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ
๐ก๏ธ
Secure your agents. Sleep better at night.
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 agent_policy_layer-0.1.0.tar.gz.
File metadata
- Download URL: agent_policy_layer-0.1.0.tar.gz
- Upload date:
- Size: 3.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
03ab923b38bf74e911bfa9f4a68db0134c7419712eccb7530492ce4db7c08a95
|
|
| MD5 |
6a871e777b10fc0e524a8bfc04c3409d
|
|
| BLAKE2b-256 |
37fb5e67ad9e0631016ff26befdf86ab89e7c9716dc7ca94b85e063de43ca69c
|
Provenance
The following attestation bundles were made for agent_policy_layer-0.1.0.tar.gz:
Publisher:
publish.yml on nimonkaranurag/agentpolicylayer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_policy_layer-0.1.0.tar.gz -
Subject digest:
03ab923b38bf74e911bfa9f4a68db0134c7419712eccb7530492ce4db7c08a95 - Sigstore transparency entry: 929119343
- Sigstore integration time:
-
Permalink:
nimonkaranurag/agentpolicylayer@3b74d908fb8e64fc1c598302b5c73585dbcccf1c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/nimonkaranurag
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3b74d908fb8e64fc1c598302b5c73585dbcccf1c -
Trigger Event:
release
-
Statement type:
File details
Details for the file agent_policy_layer-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agent_policy_layer-0.1.0-py3-none-any.whl
- Upload date:
- Size: 59.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
847465eb3bb93631693d2a257c677c8851dfb8b6ae5e9aff6901e184a8617a84
|
|
| MD5 |
eff4ac1ec459ef51faf5500b599fe845
|
|
| BLAKE2b-256 |
d0c53a2bc378032fa5bacc1a90ea085c88df00664bc60a1b303e0e2509e998fb
|
Provenance
The following attestation bundles were made for agent_policy_layer-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on nimonkaranurag/agentpolicylayer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_policy_layer-0.1.0-py3-none-any.whl -
Subject digest:
847465eb3bb93631693d2a257c677c8851dfb8b6ae5e9aff6901e184a8617a84 - Sigstore transparency entry: 929119345
- Sigstore integration time:
-
Permalink:
nimonkaranurag/agentpolicylayer@3b74d908fb8e64fc1c598302b5c73585dbcccf1c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/nimonkaranurag
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3b74d908fb8e64fc1c598302b5c73585dbcccf1c -
Trigger Event:
release
-
Statement type: