Fences
Runtime governance for AI agents. Budget limits, loop protection, token governance, and a decision audit trail — in three lines of code.
import agentfences
from agentfences import governed, checkpoint, log_decision
agentfences.init(local_only=True)
@governed(budget_usd=0.50, max_iterations=20, max_tokens=50_000)
async def run_agent(query: str, messages: list):
while True:
log_decision(reasoning="calling LLM", action="llm_call")
response = call_llm(messages)
result = await checkpoint(
cost_delta_usd=compute_cost(response.usage),
tokens_used=response.usage.total_tokens,
)
if result.breached:
# Agent handles the limit gracefully — no exception, no crash
messages.append({"role": "system", "content": result.system_prompt})
return call_llm(messages) # LLM summarises what it found
if response.is_done():
return response
Install
pip install agentfences
How it works
checkpoint() returns a CheckpointResult after every call — not an exception. The agent reads it and decides what to do: stop, summarise, ask for more budget. No try/catch in your orchestration layer.
result = await checkpoint(cost_delta_usd=0.02, tokens_used=450)
result.ok # True if within all limits
result.breached # True if a limit was crossed
result.breach_type # "budget_exceeded" | "iteration_limit" | "time_limit" | "token_limit"
result.message # "I've reached my budget limit... I'll summarise what I found."
result.system_prompt # ready to inject into LLM conversation context
Quickstart — no backend needed
import agentfences
from agentfences import governed, checkpoint, log_decision
agentfences.init(local_only=True) # no account, no API key
@governed(budget_usd=0.10, max_iterations=5, max_tokens=2000)
async def my_agent(query: str):
for step in range(100):
log_decision(reasoning=f"step {step}: searching", action="search")
result = await checkpoint(cost_delta_usd=0.02, tokens_used=200)
if result.breached:
return result.message # or inject result.system_prompt into your LLM
return "done"
Limits
| Limit | Parameter | result.breach_type |
|---|---|---|
| Spend | budget_usd |
budget_exceeded |
| Iterations | max_iterations |
iteration_limit |
| Duration | max_duration_ms |
time_limit |
| Tokens | max_tokens |
token_limit |
A limit trips once it is exceeded, not when it is reached: a $0.10 budget allows exactly $0.10 of spend.
Legacy exception mode
If you prefer exceptions over result objects:
@governed(budget_usd=0.50, raise_on_breach=True)
async def my_agent():
...
await checkpoint(cost_delta_usd=0.02) # raises BudgetExceeded, IterationLimitReached, etc.
Sync agents and streaming
@governed works on sync functions, async functions and async generators. Sync agents use checkpoint_sync(), which takes the same arguments and returns the same CheckpointResult:
from agentfences import governed, checkpoint_sync
@governed(budget_usd=0.50)
def my_agent(query: str):
while True:
response = call_llm(query)
result = checkpoint_sync(cost_delta_usd=0.02, tokens_used=response.usage.total_tokens)
if result.breached:
return result.message
A governed async generator (a streaming agent) stays governed until the stream is exhausted. Concurrent agents in one event loop and nested @governed calls each get their own run.
Modes
Local (free) — governance runs entirely in-process. No account, no backend, no API key.
agentfences.init(local_only=True)
Cloud (coming soon) — persistent audit trails, live dashboard, server-authoritative enforcement. Same code, one line changes.
agentfences.init(api_key="fc_...", endpoint="https://your-fences-instance.com")
In cloud mode the backend is authoritative: it sees spend from every process sharing a run, and a limit raised from the dashboard lets the run's next checkpoint() pass, so a fenced agent can resume. Backend calls run off the event loop and log_decision() never blocks; decisions are sent in the background and flushed at exit (call agentfences.flush() yourself before a serverless handler returns).
If the backend can't be reached, limits are enforced locally and a warning is logged once per run. To treat an unreachable backend as a breach instead:
agentfences.init(api_key="fc_...", endpoint="https://...", fail_closed=True)
License
MIT
Metadata
Release files for agentfences 0.1.6
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agentfences-0.1.6.tar.gz | 10.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentfences-0.1.6-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 20.4 kB
Release files / agentfences-0.1.6.tar.gz
| Download URL | agentfences-0.1.6.tar.gz |
|---|---|
| Size | 10.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
56c33bf153df2713c138851549d3f52df803262b59f79124d1b12e4fc1d54985
|
|
BLAKE2b-256 checksum How to use checksums |
c285d17a0521d3ba2e2785a8dba8223c58066641263061f69dbb9095eecf34f3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / agentfences-0.1.6-py3-none-any.whl
| Download URL | agentfences-0.1.6-py3-none-any.whl |
|---|---|
| Size | 10.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7ef3b57de70c5e5a020e5d787258a3bb41606306957cd176578d0dc7d43f5f62
|
|
BLAKE2b-256 checksum How to use checksums |
d170d5e287c350d212c4bda1a57f3d8af13fb6224ed2d0ef3bea89af408a18a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|