Runtime policy enforcement, human approval, and observability for AI agent tool calls
Project description
ardenpy
Policy enforcement and human approval for AI agent tool calls.
Arden sits between your agent and its tools. Every call is checked against policies you configure in the dashboard — automatically allowed, blocked, or held for a human to approve.
Install
pip install ardenpy
Claude Code skill
Let Claude Code integrate Arden into your project automatically — it detects your framework, adds the dependency, inserts configure(), and optionally sets up session tracking:
# Add the skill to your project
mkdir -p .claude/commands
curl -o .claude/commands/arden-setup.md \
https://raw.githubusercontent.com/ardenhq/adenpy/main/skills/arden-setup.md
Then in Claude Code, run:
/arden-setup
Quick start
1. Get your API key from app.arden.sh. You'll get two keys:
arden_test_...— development, hitsapi-test.arden.sharden_live_...— production, hitsapi.arden.sh
2. Configure once
import ardenpy as arden
arden.configure(api_key="arden_live_...")
3. Call your tools
For LangChain, CrewAI, and OpenAI Agents SDK — that's it. Arden auto-patches all three frameworks at configure-time, so every tool call is intercepted without any wrapping:
# LangChain, CrewAI, or OpenAI Agents SDK — use tools normally
arden.configure(api_key="arden_live_...")
agent = create_react_agent(llm, tools, prompt) # no wrapping needed
# Every tool call is now enforced and logged automatically
For custom agents with no framework, wrap functions explicitly:
def issue_refund(amount: float, customer_id: str) -> dict:
return {"refund_id": "re_123", "amount": amount}
safe_refund = arden.guard_tool("stripe.issue_refund", issue_refund)
result = safe_refund(150.0, customer_id="cus_abc")
# Arden checks policy first — allow, block, or wait for human approval
How it works
Every tool call goes through a policy check before executing:
| Policy decision | What happens |
|---|---|
allow |
Function executes immediately |
block |
PolicyDeniedError raised, function never runs |
requires_approval |
Pauses until a human approves or denies on the dashboard |
No policy configured? The call is allowed automatically and logged — you get a full audit trail from day one and can add policies incrementally.
Approval modes
When a tool requires approval, you choose how your code waits:
wait (default) — blocks until a human acts, then executes or raises PolicyDeniedError.
async — returns a PendingApproval immediately, background thread polls and calls your callback.
webhook — returns PendingApproval immediately, no polling. Arden POSTs to your endpoint when an admin decides.
# wait (default)
safe_refund = arden.guard_tool("stripe.issue_refund", issue_refund)
# async
safe_refund = arden.guard_tool("stripe.issue_refund", issue_refund,
approval_mode="async", on_approval=handle_approval, on_denial=handle_denial)
# webhook
safe_refund = arden.guard_tool("stripe.issue_refund", issue_refund,
approval_mode="webhook", on_approval=on_approval, on_denial=on_denial)
For webhook setup (FastAPI, Flask, Django examples) see the Library Reference.
Framework integrations
LangChain, CrewAI, and OpenAI Agents SDK — zero wrapping required
Arden automatically patches all three frameworks when configure() is called. Every tool call in the process is intercepted — including tools created after configure is called.
import ardenpy as arden
arden.configure(api_key="arden_live_...")
# All tool calls are now intercepted, enforced, and logged.
# No protect_tools(), no guard_tool(), no boilerplate.
| Framework | What gets patched |
|---|---|
| LangChain | BaseTool.run at the class level |
| CrewAI | BaseTool.run at the class level |
| OpenAI Agents SDK | FunctionTool.__init__ — wraps on_invoke_tool per instance |
Tool names in the dashboard match each tool's .name attribute. If you don't already have the framework installed, install it alongside ardenpy:
pip install ardenpy langchain-core # LangChain
pip install ardenpy crewai # CrewAI
pip install ardenpy openai-agents # OpenAI Agents SDK
OpenAI Chat Completions (raw loop)
The Chat Completions API has no patchable base class. Use ArdenToolExecutor to register and dispatch tool calls:
from ardenpy.integrations.openai import ArdenToolExecutor
executor = ArdenToolExecutor()
executor.register("issue_refund", issue_refund_fn)
executor.register("send_email", send_email_fn)
# In your tool-call loop:
result = executor.run(tc.function.name, json.loads(tc.function.arguments))
See examples/ for runnable code for every integration.
Session tracking
Attach a session ID to group all tool calls from a single conversation in the action log:
import ardenpy as arden
import uuid
arden.configure(api_key="arden_live_...")
# Set once per request — all guard_tool and auto-patched calls carry it automatically
arden.set_session(str(uuid.uuid4()))
Uses contextvars.ContextVar — safe for concurrent async requests. Fully optional.
Token usage tracking
Arden automatically tracks token usage and estimated cost for every LLM call — no extra setup needed. For LangChain, CrewAI, and the OpenAI Agents SDK, configure() captures usage from the framework at runtime and sends it to the dashboard in a background thread (zero latency impact).
import ardenpy as arden
arden.configure(api_key="arden_live_...")
# Token usage is now tracked automatically alongside tool calls.
# View cost breakdowns by model, day, and session in the dashboard.
For custom agent loops where you call the LLM directly, log usage manually:
response = openai_client.chat.completions.create(
model="gpt-4o",
messages=messages,
)
arden.log_token_usage(
model="gpt-4o",
prompt_tokens=response.usage.prompt_tokens,
completion_tokens=response.usage.completion_tokens,
)
| What gets tracked | Automatically? |
|---|---|
LangChain (BaseChatModel) |
Yes — auto-patched by configure() |
| CrewAI | Yes — uses LangChain under the hood |
OpenAI Agents SDK (Runner.run) |
Yes — auto-patched by configure() |
| OpenAI Chat Completions (raw loop) | Manual — call arden.log_token_usage() |
| Any other LLM | Manual — call arden.log_token_usage() |
The dashboard shows cost broken down by model, day, and session — all scoped per agent.
Error handling
try:
result = safe_refund(150.0, customer_id="cus_abc")
except arden.PolicyDeniedError:
# blocked by policy, or denied by a human
except arden.ApprovalTimeoutError:
# nobody approved within max_poll_time (wait mode)
except arden.ArdenError:
# API/configuration error
Links
- Library Reference — full API docs
- Examples — runnable code
- Dashboard
- PyPI
- Support
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 ardenpy-0.5.4.tar.gz.
File metadata
- Download URL: ardenpy-0.5.4.tar.gz
- Upload date:
- Size: 47.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
65d13e7998c3867c2781bdd0d69c4173e8243a8069f6ae963d47a6ce5ebf4c2d
|
|
| MD5 |
3da69909fd1d5543bfae0d5ce1cc99a6
|
|
| BLAKE2b-256 |
501d8542750b4a42b6b5bec395079c07a510dd739672b812803a2b372498dc8f
|
File details
Details for the file ardenpy-0.5.4-py3-none-any.whl.
File metadata
- Download URL: ardenpy-0.5.4-py3-none-any.whl
- Upload date:
- Size: 31.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ce74b67fc0ad8b327cb2b3fe9a12f31fa38338320e802d6a29565bc6d7dd28f
|
|
| MD5 |
abd06e0fec1cc869756d819dbe0a49bd
|
|
| BLAKE2b-256 |
6c506e29da9c6b36f49d463d5994cc0425e466a890d219af3140e4ab6d62641f
|