The circuit breaker for AI agent spend
Project description
Chappie
The circuit breaker for AI agent spend.
LiteLLM gives you budget caps. Chappie gives you behavior detection.
Your agent is stuck in a loop, burning $0.03/call, 200 calls deep. LiteLLM's budget limit will eventually stop it at $50. Chappie stops it at call 4.
Call 1: ALLOWED 'What is the weather in Tokyo?'
Call 2: ALLOWED 'What is the weather in Tokyo?'
Call 3: ALLOWED 'What is the weather in Tokyo?'
Call 4: BLOCKED 'What is the weather in Tokyo?' -> hash_dedup
The Problem
AI agents loop. A lot. And every loop iteration costs money.
Common patterns that drain budgets:
- Repeated identical prompts. Agent gets a bad response, retries the exact same prompt forever.
- Cycling patterns. Agent alternates between two actions (summarize, translate, summarize, translate...) without making progress.
- Velocity spikes. Agent suddenly fires 10x more calls per minute than its baseline, usually because it's stuck.
LiteLLM's built-in budget limits work like a spending cap on a credit card. They tell you after the damage is done. Chappie watches the agent's behavior in real time and pulls the plug before the bill adds up.
Quick Start
pip install chappie
Add Chappie to your LiteLLM proxy config:
# litellm_config.yaml
litellm_settings:
callbacks:
- chappie.logger
That's it. Chappie starts in observe mode by default. It logs detected loops but does not block requests. When you're confident, switch to enforce mode:
export CHAPPIE_MODE=enforce
See It Work
No API key needed. No LLM calls. Just run the demo:
python examples/quick_test.py
=== Chappie Loop Detector Demo ===
Strategy A: Hash Dedup (same prompt repeated)
Call 1: ALLOWED 'What is the weather in Tokyo?'
Call 2: ALLOWED 'What is the weather in Tokyo?'
Call 3: ALLOWED 'What is the weather in Tokyo?'
Call 4: BLOCKED 'What is the weather in Tokyo?' -> hash_dedup
Strategy B: Cycle Detection (A-B-A-B pattern)
Call 1: ALLOWED 'Summarize the document'
Call 2: ALLOWED 'Translate to Spanish'
Call 3: ALLOWED 'Summarize the document'
Call 4: ALLOWED 'Translate to Spanish'
Call 5: ALLOWED 'Summarize the document'
Call 6: ALLOWED 'Translate to Spanish'
Call 7: BLOCKED 'Summarize the document' -> cycle
How It Works
Chappie plugs into LiteLLM as a CustomLogger. It inspects every request before it reaches the LLM and every response after it comes back.
Three detection strategies run on every call. The first match wins:
| Strategy | What It Catches | How |
|---|---|---|
| Hash Dedup | Same prompt repeated 3+ times | SHA-256 fingerprint of the last user message + model, tracked in a sliding window of 20 calls |
| Cycle Detection | A-B-A-B-A-B patterns | Looks for repeating subsequences (period 2-4) across 3 full repetitions |
| Velocity Anomaly | Sudden call-rate spikes | Compares current calls/min against an exponential moving average baseline. Flags at 5x baseline |
Detection state is kept in-memory (no Redis on the hot path). Each agent gets its own isolated window.
Circuit Breaker
Loop detection tells you something is wrong. The circuit breaker acts on it.
When Chappie detects a loop, it trips the circuit breaker for that specific agent. The agent is blocked from making further LLM calls until a cooldown period expires or an operator manually resets it. Other agents are unaffected.
The Three States
loop detected
CLOSED ─────────────────────► OPEN
▲ │
│ │ cooldown expires
│ probe call succeeds ▼
└──────────────────────── HALF_OPEN
│
probe call fails │
────────────────────► OPEN (reset cooldown)
| State | What Happens | Duration |
|---|---|---|
| CLOSED | Normal operation. All requests pass through. Chappie monitors for loops in the background. | Default state |
| OPEN | Agent is blocked. Every request returns HTTP 429 immediately, before it reaches the LLM. No tokens burned, no cost incurred. | cooldown_sec (default: 300s / 5 min) |
| HALF_OPEN | Cooldown expired. Chappie allows one probe call through to test if the agent has recovered. | 1 call |
After the probe call in HALF_OPEN:
- Probe succeeds: circuit closes, agent resumes normal operation.
- Probe fails or triggers another loop: circuit re-opens, cooldown resets.
How It Connects to Loop Detection
The full flow from detection to enforcement:
Agent sends request
│
▼
ChappieLogger.pre_call_hook()
│
├─► Is circuit breaker OPEN for this agent?
│ YES ──► Return 429 immediately (no LLM call)
│ NO ──► Continue
│
├─► Run loop detection (hash dedup, cycle, velocity)
│ LOOP DETECTED ──► Trip circuit breaker ──► Return 429
│ NO LOOP ──► Allow request through to LLM
│
▼
LLM processes request
│
▼
ChappieLogger.post_call_hook()
│
├─► Record call in loop detector
└─► Record error count for circuit breaker (failures only)
The circuit breaker also tracks raw error counts independently of loop detection. If an agent accumulates error_threshold (default: 5) LLM failures within error_window_sec (default: 60s), the breaker trips. This catches scenarios where the agent is not looping but is hammering a failing endpoint repeatedly.
Blocked Agent Response
When an agent is blocked by an open circuit breaker, the caller receives:
{
"error": "chappie_circuit_open",
"agent_id": "my-research-agent",
"state": "open",
"reason": "Loop detected via hash_dedup: Hash 3f2a... seen 3 times in last 20 calls",
"open_until": "2025-01-15T14:35:00Z",
"cooldown_remaining_sec": 287,
"message": "Chappie circuit breaker is OPEN for this agent. Retry after cooldown or request a manual reset."
}
HTTP status: 429 Too Many Requests
The open_until timestamp and cooldown_remaining_sec field tell the caller exactly when the agent will be eligible for a probe call.
Trip Event Log
When the circuit breaker trips, Chappie emits a structured event:
{
"event_type": "circuit_breaker.tripped",
"agent_id": "my-research-agent",
"data": {
"trigger": "loop_detected",
"strategy": "hash_dedup",
"previous_state": "closed",
"new_state": "open",
"error_count": 3,
"cooldown_sec": 300,
"open_until": "2025-01-15T14:35:00Z"
},
"timestamp": "2025-01-15T14:30:00Z"
}
Manual Reset
Operators can reset a tripped circuit breaker without waiting for the cooldown:
curl -X POST http://localhost:8787/api/agents/my-research-agent/circuit-breaker/reset
This immediately transitions the agent from OPEN to CLOSED, allowing requests through again. Use this when you have fixed the underlying issue (bad prompt, misconfigured tool, stuck workflow) and want the agent back online.
Budget Enforcer
Loop detection catches behavioral problems. The budget enforcer catches financial ones.
Chappie uses a reservation-based enforcement model. Before every LLM call, it estimates the cost, atomically reserves that amount against the budget, and only allows the call if the reservation succeeds. After the call, it reconciles the estimate with the actual cost.
The Reservation Flow
1. ESTIMATE Calculate expected cost from message length + model pricing
│
▼
2. RESERVE Atomically: if (spent + estimate <= limit) then hold
│
├─ FAIL ──► 429 ChappieBudgetExceeded (no LLM call)
│
▼
3. EXECUTE LLM call happens
│
▼
4. RECONCILE Adjust hold to actual cost (release diff or charge extra)
│
└─ On failure: RELEASE full amount back to budget
The reservation is atomic. A Lua script checks spent + estimated <= limit and increments in a single Redis round-trip. No race conditions, even under concurrent load.
Reservations carry a TTL (default: 120 seconds). If a process crashes mid-call, the reservation expires and the budget recovers automatically. No orphaned holds.
Budget Scopes
Budgets are enforced at four levels. Each scope is independent.
| Scope | What It Controls | Example |
|---|---|---|
agent |
Single AI agent | agent:email-drafter has a $25/month cap |
user |
Individual human operator | user:john shares $200 across all agents he runs |
team |
Department or team | team:engineering shares $1,000 across all team members |
global |
Organization-wide ceiling | Hard stop at $5,000/month, period |
All four can be active at the same time. The most restrictive budget wins. An agent can be under-budget on its own cap but still get blocked if the team or global budget is exhausted.
Threshold Alerts
Chappie fires alerts at configurable spend thresholds. Each threshold fires exactly once per budget period, so you do not get spammed.
| Threshold | Level | What It Means |
|---|---|---|
| 50% | info |
Heads up. Halfway through the budget. |
| 80% | warning |
Slow down. Time to review agent behavior. |
| 90% | urgent |
Almost out. Consider pausing non-critical agents. |
| 100% | critical |
Budget exhausted. New requests are blocked with 429. |
Budget Exceeded Response
When a reservation fails because the budget cannot absorb the estimated cost:
{
"error": "chappie_budget_exceeded",
"agent_id": "agent:email-drafter",
"spent": 24.87,
"limit": 25.00,
"message": "Budget exceeded for agent agent:email-drafter: spent $24.8700 / limit $25.0000"
}
HTTP status: 429 Too Many Requests
Budget Configuration
# Default budget when none is explicitly set for an agent/user/team
CHAPPIE_BUDGETS__DEFAULT_BUDGET=100.0
# Budget period reset cycle
CHAPPIE_BUDGETS__RESET_PERIOD=monthly # "daily", "weekly", or "monthly"
# Reservation TTL in seconds (orphan protection)
CHAPPIE_BUDGETS__RESERVATION_TTL_SEC=120
# Alert thresholds (as decimal ratios)
CHAPPIE_BUDGETS__ALERT_THRESHOLDS=[0.5, 0.8, 0.9, 1.0]
CLI (budgetctl)
Chappie ships with budgetctl, a command-line tool for managing budgets, monitoring agents, and inspecting circuit breakers.
Installed automatically with pip install chappie.
System Status
budgetctl status
╭───── Chappie Status ─────╮
│ │
│ Mode: enforce │
│ Store: redis (connected) │
│ Agents tracked: 12 │
│ Total spend: $847.32 │
│ Loops caught: 23 │
│ CB tripped: 4 │
│ │
╰───────────────────────────╯
Active Circuit Breakers
Agent State Reason Cooldown
email-drafter OPEN Loop: hash_dedup 3m 42s
code-reviewer HALF_OPEN Error threshold exceeded probing...
Budget List
budgetctl budget list
Budgets
Scope ID Spent Limit Used Status
agent email-drafter $24.87 $25.00 99% WARNING
agent code-reviewer $82.10 $200.00 41% OK
user john $142.30 $200.00 71% OK
team engineering $847.32 $1000.00 85% WARNING
global org $847.32 $5000.00 17% OK
Set a Budget
budgetctl budget set agent email-drafter 50.00
# Budget set: agent/email-drafter = $50.00
budgetctl budget set team engineering 2000.00
# Budget set: team/engineering = $2000.00
budgetctl budget set global org 10000.00
# Budget set: global/org = $10000.00
Get Budget Details
budgetctl budget get agent email-drafter
╭──── Budget: agent/email-drafter ────╮
│ │
│ Scope: agent │
│ ID: email-drafter │
│ Spent: $24.87 │
│ Limit: $50.00 │
│ Remaining: $25.13 │
│ Used: 50% │
│ Status: OK │
│ │
╰──────────────────────────────────────╯
JSON Output
Every command supports --format json for scripting and piping:
budgetctl --format json budget list | jq '.[] | select(.percentage > 80)'
Connection
budgetctl talks to the Chappie API over HTTP. Set the API URL if it is not running on localhost:
export CHAPPIE_API_URL=http://your-server:8787
Alerts
Chappie sends alerts when thresholds are crossed, circuit breakers trip, or loops are detected.
Slack
Point Chappie at a Slack incoming webhook and it posts alerts to the channel of your choice:
CHAPPIE_ALERTS__SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T00/B00/xxxx
Generic Webhook
For PagerDuty, Datadog, or your own alerting system, use the generic webhook. Chappie POSTs a JSON payload on every alert:
CHAPPIE_ALERTS__WEBHOOK_URL=https://your-system.com/alerts/chappie
The payload:
{
"event_type": "budget.threshold_crossed",
"agent_id": "email-drafter",
"data": {
"scope": "agent",
"scope_id": "email-drafter",
"threshold": 0.8,
"level": "warning",
"spent": 40.12,
"limit": 50.00,
"percentage": 80.24
},
"timestamp": "2025-01-15T14:30:00Z"
}
Alert Levels
| Level | When | Example |
|---|---|---|
info |
50% budget threshold crossed | Informational, no action needed yet |
warning |
80% budget threshold or circuit breaker trip | Review agent behavior |
urgent |
90% budget threshold | Consider pausing non-critical agents |
critical |
100% budget exhausted | Agent is now blocked |
Alert Configuration
CHAPPIE_ALERTS__SLACK_WEBHOOK_URL= # Slack incoming webhook URL
CHAPPIE_ALERTS__WEBHOOK_URL= # Generic webhook URL (receives JSON POST)
CHAPPIE_ALERTS__ENABLED=true # Kill switch for all alerts
API
Chappie exposes a REST API for programmatic access to agent state, budgets, and circuit breakers.
Start the API Server
uvicorn chappie.api:app --host 0.0.0.0 --port 8787
Or use the default port from config:
# Reads CHAPPIE_API_PORT (default: 8787)
uvicorn chappie.api:app
Endpoints
| Method | Path | Description |
|---|---|---|
GET |
/api/status |
System overview: mode, store, agent count, total spend, active breakers |
GET |
/api/budgets |
List all budgets with spend, limit, and usage percentage |
GET |
/api/budgets/{scope}/{id} |
Get budget details for a specific scope/id pair |
PUT |
/api/budgets/{scope}/{id} |
Set or update a budget limit |
POST |
/api/agents/{agent_id}/circuit-breaker/reset |
Manually reset a tripped circuit breaker |
GET |
/api/events |
SSE stream of real-time events (loops, trips, alerts) |
SSE Events Stream
Subscribe to the events endpoint for live monitoring. Events stream as Server-Sent Events:
curl -N http://localhost:8787/api/events
event: circuit_breaker.tripped
data: {"agent_id": "email-drafter", "data": {"trigger": "loop_detected", "strategy": "hash_dedup"}, "timestamp": "2025-01-15T14:30:00Z"}
event: budget.threshold_crossed
data: {"agent_id": "code-reviewer", "data": {"threshold": 0.8, "level": "warning", "spent": 160.42, "limit": 200.00}, "timestamp": "2025-01-15T14:31:12Z"}
Docker
Get Chappie running with Redis in one command:
docker compose up
This starts:
- Redis 7 on port 6379 (with persistence and health checks)
- Chappie API on port 8787 (connected to Redis, observe mode by default)
What the Compose File Does
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
chappie-api:
build: .
ports:
- "8787:8787"
environment:
- CHAPPIE_REDIS_URL=redis://redis:6379
- CHAPPIE_MODE=observe
depends_on:
redis:
condition: service_healthy
Switch to enforce mode:
CHAPPIE_MODE=enforce docker compose up
Dockerfile
The image is based on python:3.12-slim. It copies the package, installs dependencies, and starts the API server with uvicorn.
FROM python:3.12-slim
WORKDIR /app
COPY pyproject.toml README.md LICENSE ./
COPY chappie/ chappie/
COPY cli/ cli/
RUN pip install --no-cache-dir .
EXPOSE 8787
CMD ["python", "-m", "uvicorn", "chappie.api:app", "--host", "0.0.0.0", "--port", "8787"]
Two Modes
| Mode | Behavior | Use Case |
|---|---|---|
observe (default) |
Logs loops, allows all requests | Safe rollout, monitoring, tuning thresholds |
enforce |
Returns HTTP 429 when a loop is detected or budget is exceeded | Production protection |
Agent Identification
Chappie identifies agents using a fallback chain:
metadata.agent_id(explicit, preferred)metadata.session_id(LangChain / AutoGen convention)team_id:user_id(LiteLLM proxy identity)- API key suffix (last resort)
Pass your agent ID through LiteLLM metadata:
import litellm
response = litellm.completion(
model="gpt-4",
messages=[{"role": "user", "content": "Hello"}],
metadata={"agent_id": "my-research-agent"}
)
Configuration
All settings load from environment variables with the CHAPPIE_ prefix:
# Core
CHAPPIE_MODE=observe # "observe" or "enforce"
CHAPPIE_REDIS_URL=redis://localhost:6379
CHAPPIE_ON_REDIS_FAILURE=open # "open" (allow requests) or "closed" (block)
CHAPPIE_API_PORT=8787
# Loop Detection
CHAPPIE_LOOP_DETECTION__WINDOW_SIZE=20
CHAPPIE_LOOP_DETECTION__REPEAT_THRESHOLD=3
CHAPPIE_LOOP_DETECTION__CYCLE_MAX_PERIOD=4
CHAPPIE_LOOP_DETECTION__VELOCITY_WINDOW_SEC=60
CHAPPIE_LOOP_DETECTION__VELOCITY_MULTIPLIER=5.0
# Circuit Breaker
CHAPPIE_CIRCUIT_BREAKER__ERROR_THRESHOLD=5
CHAPPIE_CIRCUIT_BREAKER__ERROR_WINDOW_SEC=60
CHAPPIE_CIRCUIT_BREAKER__COOLDOWN_SEC=300
CHAPPIE_CIRCUIT_BREAKER__HALF_OPEN_MAX_CALLS=1
# Budgets
CHAPPIE_BUDGETS__DEFAULT_BUDGET=100.0
CHAPPIE_BUDGETS__RESET_PERIOD=monthly
CHAPPIE_BUDGETS__RESERVATION_TTL_SEC=120
# Alerts
CHAPPIE_ALERTS__SLACK_WEBHOOK_URL=
CHAPPIE_ALERTS__WEBHOOK_URL=
CHAPPIE_ALERTS__ENABLED=true
See .env.example for the full list.
Tuning Tips
- Lowering
REPEAT_THRESHOLDto 2 catches loops faster but risks false positives on legitimate retries. - Raising
WINDOW_SIZEabove 20 gives more context but uses more memory per agent. VELOCITY_MULTIPLIER=5.0means "5x the normal rate." Lower it for tighter control on expensive models.- The velocity detector needs 5 calls to build a baseline. It will never flag during warmup.
COOLDOWN_SEC=300keeps a tripped agent blocked for 5 minutes. Shorten it for development environments where agents restart frequently. Lengthen it for production agents that burn expensive tokens.ERROR_THRESHOLD=5controls how many LLM failures (timeouts, 500s, rate limits from the provider) trip the breaker independently of loop detection. Lower this for expensive models where even a few wasted retries matter.DEFAULT_BUDGET=100.0applies when no per-agent budget has been set. Agents running expensive models (GPT-4, Claude Opus) should get explicit budgets viabudgetctl budget setrather than relying on the default.
Architecture
┌──────────────────────────────────────────────────────┐
│ LiteLLM Proxy │
│ │
│ ┌───────────────────────────────────────────────┐ │
│ │ ChappieLogger (CustomLogger hook) │ │
│ │ │ │
│ │ pre_call ──► CircuitBreaker.check() │ │
│ │ │ │ │
│ │ ├─ OPEN? ──► 429 (blocked) │ │
│ │ │ │ │
│ │ └─ CLOSED/HALF_OPEN? │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ BudgetEnforcer.reserve() │ │
│ │ ├─ EXCEEDED? ──► 429 (blocked) │ │
│ │ └─ OK? │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ LoopDetector.check() │ │
│ │ ├─ Hash Dedup │ │
│ │ ├─ Cycle Detection │ │
│ │ └─ Velocity Anomaly │ │
│ │ │ │ │
│ │ ├─ LOOP ──► CB.trip() ──► 429│ │
│ │ └─ OK ──► allow request │ │
│ │ │ │
│ │ post_call ──► BudgetEnforcer.reconcile() │ │
│ │ ──► Record call in LoopDetector │ │
│ │ ──► Record error in CircuitBreaker │ │
│ │ ──► Check threshold alerts │ │
│ └───────────────────────────────────────────────┘ │
│ │
│ State: in-memory (loop detection) │
│ Store: Redis or MemoryStore (budgets, CB, alerts) │
│ API: FastAPI on port 8787 (budgetctl, SSE events) │
└──────────────────────────────────────────────────────┘
Design Decisions
Why in-memory instead of Redis for detection state? Loop detection runs on every single request. Adding a Redis round-trip to the hot path would add 1-5ms of latency per call. In-memory keeps it at microseconds.
Why reservation-based budget enforcement? A simple "check then charge" has a race condition: two concurrent calls could both pass the check, then both charge, pushing the total over the limit. The Lua-script reservation is atomic, so concurrent calls cannot overspend.
Why observe mode by default? Every team's "normal" agent behavior is different. Observe mode lets you see what Chappie would catch before it starts blocking. Deploy, watch the logs, tune the thresholds, then switch to enforce.
Why fail-open when Redis is down? If your state store goes offline, blocking all AI requests is worse than temporarily losing protection. Chappie logs a warning and keeps going.
Why only proxy mode can enforce?
LiteLLM's proxy sits between the caller and the LLM, so it can intercept and block. SDK mode (direct litellm.completion() calls) does not have that interception point, so it observes only.
Why Not Just Use LiteLLM Budgets?
LiteLLM has built-in budget limits. They are useful. But they solve a different problem.
| LiteLLM Budgets | Chappie | |
|---|---|---|
| What it watches | Cumulative spend ($) | Agent behavior (patterns, velocity) + spend |
| When it acts | After you hit the limit | When it detects a loop forming OR before the call if budget is tight |
| Granularity | Per-key or per-team | Per-agent, per-user, per-team, per-global |
| A 200-call loop at $0.03/call | Stops at your budget cap ($50, $100, whatever) | Stops at call 4 |
| Cost enforcement | Post-call check | Pre-call atomic reservation |
They are complementary. Use LiteLLM budgets as a hard ceiling. Use Chappie as the early warning system that prevents you from ever reaching that ceiling.
Testing
pip install -e ".[dev]"
pytest
81 tests covering:
- Loop detection (17 tests): Hash dedup, cycle detection (period 2/3/4), velocity anomaly, warmup, agent isolation, window boundaries, hash determinism
- Circuit breaker (22 tests): State transitions (CLOSED/OPEN/HALF_OPEN), error counting, cooldown timing, manual reset, trip reasons (loop/error/budget/manual), multi-agent isolation, full lifecycle
- Budget enforcer (18 tests): Reserve/reconcile/release flow, budget exceeded rejection, threshold alerts (50/80/90/100%), threshold deduplication, scope independence, spend reset, cost estimation
- Store layer (24 tests): Get/set/delete, TTL expiry, incr_float, hash operations, Lua script evaluation, ping
Build Progress
Chappie is being built in public over 7 days.
| Day | Feature | Status |
|---|---|---|
| 1 | Loop Detector (3 strategies) + LiteLLM integration + Store layer | Done |
| 2 | Circuit Breaker (CLOSED/OPEN/HALF_OPEN state machine) | Done |
| 3 | Budget Enforcer (reservation-based atomic enforcement) | Done |
| 4 | CLI (budgetctl) + Alerts (Slack/webhook) + REST API |
Done |
| 5 | Docker + CI pipeline + Docs | Done |
| 6 | Polish + dogfooding + final tests | Done |
| 7 | Launch | Done - v0.1.0 released |
Project Structure
chappie/
├── chappie/
│ ├── __init__.py # Package entry point, exports ChappieLogger
│ ├── config.py # Pydantic settings (all env var config)
│ ├── exceptions.py # ChappieError hierarchy (loop, budget, circuit)
│ ├── logger.py # ChappieLogger (LiteLLM CustomLogger hook)
│ ├── models.py # Shared Pydantic models (events, reservations, status)
│ ├── engine/
│ │ ├── __init__.py
│ │ ├── loop_detector.py # Hash dedup + cycle detection + velocity anomaly
│ │ ├── circuit_breaker.py # Per-agent CLOSED/OPEN/HALF_OPEN state machine
│ │ └── budget_enforcer.py # Atomic reservation + threshold alerts
│ └── store/
│ ├── __init__.py # Store protocol + factory
│ ├── memory.py # In-memory store (no dependencies)
│ └── redis.py # Redis store (production)
├── cli/
│ ├── __init__.py
│ └── main.py # budgetctl CLI (Click + Rich)
├── tests/
│ ├── conftest.py # Shared fixtures
│ ├── test_loop_detector.py # 17 tests
│ ├── test_circuit_breaker.py # 22 tests
│ ├── test_budget_enforcer.py # 18 tests
│ └── test_store.py # 24 tests
├── examples/
│ └── quick_test.py # Run the demo (no API key needed)
├── .github/
│ └── workflows/
│ └── ci.yml # Lint (ruff) + test (pytest) on every push
├── Dockerfile # Python 3.12-slim + uvicorn
├── docker-compose.yml # Redis + Chappie API
├── pyproject.toml # Package config, dependencies, budgetctl entry point
├── .env.example # All configuration env vars with defaults
└── LICENSE # MIT
Coming Soon: Adaptive Insights + Model Benchmarking
Adaptive Insights (v0.2)
Static thresholds are a starting point. An email-drafting agent that makes 5-8 calls per task is different from a code-reviewer that makes 15-25 calls. Chappie will learn each agent's normal behavior and auto-adjust thresholds.
budgetctl insights email-drafter
Sessions: 142
Avg calls/session: 6.3
Avg cost/session: $0.42
P95 calls: 11 (auto-threshold)
Velocity baseline: 3.2 calls/min
Loops caught: 3 (2.1% anomaly rate)
Recommendation: Threshold auto-set to 11 (default was 3)
After 10 sessions, Chappie switches from static defaults to per-agent adaptive thresholds based on the agent's actual P95 behavior. Fewer false positives, tighter detection.
Model Loop Leaderboard (v0.2)
Every loop detection is tagged with the model name. Chappie aggregates this into a per-model benchmark. Which LLMs loop the most?
budgetctl benchmark
Model Calls Loops Rate Avg Cost/Call
claude-haiku-4-5 12,450 89 0.71% $0.003
gpt-4o-mini 8,200 142 1.73% $0.008
claude-sonnet-4 5,100 18 0.35% $0.024
gpt-4o 3,800 31 0.82% $0.041
mistral-large 2,100 67 3.19% $0.015
Most loop-prone: mistral-large (3.19%)
Most cost-efficient: claude-haiku-4-5 ($0.003/call, 0.71% loop rate)
Privacy-safe: only model names, call counts, loop counts, and cost aggregates. No prompt content stored.
Contributing
This is early-stage software. Issues and PRs are welcome.
If you've had an agent burn through your budget because it got stuck in a loop, I'd like to hear about it. Open an issue describing the pattern, and it might become a new detection strategy.
License
MIT. See LICENSE.
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 budgetctl-0.1.0.tar.gz.
File metadata
- Download URL: budgetctl-0.1.0.tar.gz
- Upload date:
- Size: 90.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2ab01a7fd800fe0e45c57a79ba8c2a80eb765fcf191906006dd794c28de1d01
|
|
| MD5 |
c19cd26640eee5168a5541e1e194a411
|
|
| BLAKE2b-256 |
f332a86b7df45f79172d152d1d54666d7c5a2dc282d073d75d2bcddcb2bb949a
|
File details
Details for the file budgetctl-0.1.0-py3-none-any.whl.
File metadata
- Download URL: budgetctl-0.1.0-py3-none-any.whl
- Upload date:
- Size: 46.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
352d27d8ee142314f74c5172618cd45cdae791f01edf28266aec0d8f4c6cdc13
|
|
| MD5 |
93e3cdf4e6e6c2e18bb6445912b5ef06
|
|
| BLAKE2b-256 |
d2ee2189a21c001fa88e10e514d4f2ff7dc0c13ff88444f6ae274988057e3ccf
|