Skip to content
prune.

How to stop an AI agent loop from running up your API bill

Four guards, enforced outside the agent, so a stuck run fails loudly instead of quietly spending.

Quick answer

Give every agent run an ID, then enforce four limits outside the model: a dollar budget per run, a maximum number of steps, a breaker that stops the run when the same tool call repeats, and a daily spend cap per API key. Provider spend limits are monthly and project-wide, so they are a backstop, not a brake.

Why agent loops are expensive

A stuck agent does not crash. It keeps doing what it was built to do: call a tool, read the error, try again. Each call returns 200 OK, so nothing alerts. Worse, many agents append the error to the conversation before retrying, so every step sends more tokens than the last. Developers on r/SaaS and r/LangChain describe the same pattern: a support agent stuck on a CRM call for six hours, a LangGraph agent retrying a broken tool all weekend. The logs explained it the next morning. Nothing stopped it at 2am.

1. Give every run an ID

You cannot budget what you cannot group. Generate one ID per agent run (one user task, one cron job, one ticket) and attach it to every model call that run makes. Every guard below keys on it.

2. Budget dollars per run, not just steps

max_retries and LangGraph's recursion_limit cap depth, not money. Track cost per call from the usage object and stop the run when it crosses a ceiling you would be comfortable losing. Check the budget before each call, not after the run finishes.

3. Set a step ceiling

Most real tasks finish in a predictable number of steps. Pick a ceiling at roughly two to three times your normal run and fail loudly when it is hit. A run on its 40th tool call with no final answer is a loop, even if every call looks fine on its own.

4. Break on repeated tool calls

Fingerprint each step on the tool name plus normalized arguments (strip timestamps and request IDs). If the same fingerprint shows up three times in one run, stop and surface the exact call to a human. Do not let two agents review each other without a parent that owns the budget.

agent_guard.py
MAX_STEPS = 25
RUN_BUDGET_USD = 2.00

def run_agent(task):
    spent, seen = 0.0, {}
    for step in range(MAX_STEPS):
        action = plan_next_action(task)          # your agent's next tool call

        # Breaker: same tool + same args three times means it is stuck.
        key = (action.tool, normalize(action.args))
        seen[key] = seen.get(key, 0) + 1
        if seen[key] >= 3:
            raise RuntimeError(f"loop: {action.tool} repeated 3x")

        resp = call_model(task, action)
        spent += cost_of(resp.usage)              # tokens x your model's price
        if spent >= RUN_BUDGET_USD:
            raise RuntimeError(f"run budget hit: ${spent:.2f}")

        if action.is_final:
            return resp
    raise RuntimeError("step ceiling hit")

5. Put a daily cap outside your code

Your own guard lives in the same process as the bug. Add a cap that sits in front of the provider: a per-key daily spend limit that rejects calls once hit. Keep the provider's own hard spend limit on as the final backstop, knowing it is monthly and can overshoot slightly.

Doing this with Prune

Prune is an AI gateway. Point your SDK at it and these guards run before the provider call, outside your agent process:

  • X-Prune-Run-Id groups a run. The same step repeated three times in a run is rejected with 429 and X-Prune-Shield-Code: agent_loop.
  • X-Prune-Run-Budget-Usd rejects further calls once the run has spent that much (run_budget).
  • X-Prune-Max-Steps sets the step ceiling (default 50 for tool-using calls).
  • A per-key daily spend cap and spike detection: in the dashboard, open Shield and pick Balanced ($200/day) or Locked down ($50/day), or set your own number. New accounts start on Balanced. Accounts created before October 2026 should open Shield and pick a mode.
agent_through_prune.py
import os, uuid
from openai import OpenAI

client = OpenAI(
    base_url="https://api.withprune.com/v1",
    api_key=os.environ["PRUNE_API_KEY"],   # provider key stays in the Prune vault
)

run_id = f"support-agent-{uuid.uuid4().hex[:8]}"

resp = client.chat.completions.create(
    model="gpt-4o",
    messages=messages,
    tools=tools,
    extra_headers={
        "X-Prune-Run-Id": run_id,          # groups every call of this run
        "X-Prune-Run-Budget-Usd": "2.00",  # 429 once the run has spent $2
        "X-Prune-Max-Steps": "25",         # 429 on step 26
    },
)

One limit to know: the repeat breaker matches identical steps. A loop that grows the conversation on every retry is caught by the run budget and step ceiling instead, which is why you want all three.

With a single line of code, Prune cuts your AI bill and protects your team from runaway spend, leaked keys and dangerous calls.

Related

Have an account? Sign in · PyPI.