Skip to content
Blog

Token Budgets per Agent Run: Allocating, Tracking, and Enforcing

Manage token budgets across agent runs. Allocate, track, and enforce token limits to control costs and prevent runaway agent loops.

Published on • September 8, 2026

AI Assistant

Agents consume tokens at every step — reasoning, tool calls, retries, and context accumulation. Without budgets, a single runaway agent loop can cost hundreds of dollars. Token budgets give you control: allocate per-run limits, track usage in real-time, and enforce hard stops when limits are hit.

Why Token Budgets Matter

An agent that loops through tool calls can burn tokens exponentially:

User request: 500 tokens
Agent reasoning: 1,000 tokens
Tool call 1 + result: 2,000 tokens
Agent reasoning: 1,500 tokens
Tool call 2 + result: 2,500 tokens
... (continues for 20 iterations)
Total: 50,000+ tokens

At $10/1M tokens, that’s $0.50 per request. At scale, it’s thousands per day.

Architecture

Agent Run Request
    ↓
Budget Allocator (assign budget based on task type)
    ↓
Token Counter (track usage in real-time)
    ↓
Agent Loop
    ├── LLM Call (deduct tokens)
    ├── Tool Call (deduct tokens)
    └── Check remaining budget
    ↓
Budget Enforcer (halt if exceeded)

Implementation

Token Budget Manager

from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
import asyncio

class BudgetStatus(Enum):
    OK = "ok"
    WARNING = "warning"  # 80% used
    CRITICAL = "critical"  # 95% used
    EXCEEDED = "exceeded"

@dataclass
class TokenBudget:
    run_id: str
    allocated: int  # Total budget for this run
    used: int = 0
    warning_threshold: float = 0.8
    critical_threshold: float = 0.95
    created_at: datetime = field(default_factory=datetime.now)
    
    @property
    def remaining(self) -> int:
        return max(0, self.allocated - self.used)
    
    @property
    def usage_percent(self) -> float:
        return self.used / self.allocated if self.allocated > 0 else 0
    
    @property
    def status(self) -> BudgetStatus:
        if self.used >= self.allocated:
            return BudgetStatus.EXCEEDED
        elif self.usage_percent >= self.critical_threshold:
            return BudgetStatus.CRITICAL
        elif self.usage_percent >= self.warning_threshold:
            return BudgetStatus.WARNING
        return BudgetStatus.OK

class TokenBudgetManager:
    def __init__(self):
        self.budgets: dict[str, TokenBudget] = {}
        self.default_budgets = {
            "simple_query": 5000,
            "complex_task": 50000,
            "research": 100000,
            "unlimited": float('inf'),
        }
    
    def allocate(
        self,
        run_id: str,
        task_type: str = "complex_task",
        custom_budget: int = None
    ) -> TokenBudget:
        """Allocate a token budget for an agent run."""
        budget_amount = custom_budget or self.default_budgets.get(task_type, 50000)
        
        budget = TokenBudget(
            run_id=run_id,
            allocated=budget_amount
        )
        self.budgets[run_id] = budget
        
        return budget
    
    def track_usage(
        self,
        run_id: str,
        input_tokens: int,
        output_tokens: int,
        operation: str = "llm_call"
    ) -> TokenBudget:
        """Track token usage for a run."""
        budget = self.budgets.get(run_id)
        
        if not budget:
            raise ValueError(f"No budget found for run {run_id}")
        
        total_tokens = input_tokens + output_tokens
        budget.used += total_tokens
        
        # Log usage
        self._log_usage(run_id, input_tokens, output_tokens, operation)
        
        return budget
    
    def check_budget(self, run_id: str) -> tuple[bool, str]:
        """Check if the run is within budget."""
        budget = self.budgets.get(run_id)
        
        if not budget:
            return False, "No budget found"
        
        status = budget.status
        
        if status == BudgetStatus.EXCEEDED:
            return False, f"Budget exceeded: {budget.used}/{budget.allocated} tokens"
        elif status == BudgetStatus.CRITICAL:
            return True, f"Warning: {budget.usage_percent:.1%} of budget used"
        elif status == BudgetStatus.WARNING:
            return True, f"Notice: {budget.usage_percent:.1%} of budget used"
        
        return True, "Within budget"
    
    def _log_usage(self, run_id, input_tokens, output_tokens, operation):
        # Store in database for analysis
        pass

Budget-Aware Agent

class BudgetAwareAgent:
    def __init__(self, llm, budget_manager: TokenBudgetManager):
        self.llm = llm
        self.budget_manager = budget_manager
    
    async def execute(
        self,
        task: str,
        task_type: str = "complex_task",
        budget: int = None
    ) -> dict:
        run_id = str(uuid.uuid4())
        
        # Allocate budget
        token_budget = self.budget_manager.allocate(
            run_id=run_id,
            task_type=task_type,
            custom_budget=budget
        )
        
        messages = [{"role": "user", "content": task}]
        
        while True:
            # Check budget before each LLM call
            within_budget, message = self.budget_manager.check_budget(run_id)
            
            if not within_budget:
                return {
                    "status": "budget_exceeded",
                    "error": message,
                    "tokens_used": token_budget.used,
                    "budget": token_budget.allocated,
                }
            
            # Make LLM call
            response = await self.llm.ainvoke(messages)
            
            # Track usage
            usage = response.usage_metadata
            self.budget_manager.track_usage(
                run_id=run_id,
                input_tokens=usage["input_tokens"],
                output_tokens=usage["output_tokens"],
                operation="llm_call"
            )
            
            # Process response
            if response.tool_calls:
                # Execute tools and track their token usage
                for tool_call in response.tool_calls:
                    tool_result = await self._execute_tool(tool_call)
                    
                    # Tools may use tokens (e.g., embedding calls)
                    if "token_usage" in tool_result:
                        self.budget_manager.track_usage(
                            run_id=run_id,
                            input_tokens=tool_result["token_usage"].get("input", 0),
                            output_tokens=tool_result["token_usage"].get("output", 0),
                            operation=f"tool:{tool_call['name']}"
                        )
                    
                    messages.append({"role": "tool", "content": str(tool_result)})
                
                messages.append(response.message)
            else:
                return {
                    "status": "completed",
                    "result": response.content,
                    "tokens_used": token_budget.used,
                    "budget": token_budget.allocated,
                }

Budget Allocation Strategies

class BudgetAllocator:
    def __init__(self):
        self.task_profiles = {
            "simple_query": {"base": 3000, "multiplier": 1.0},
            "complex_task": {"base": 20000, "multiplier": 1.5},
            "research": {"base": 50000, "multiplier": 2.0},
            "code_generation": {"base": 30000, "multiplier": 1.8},
        }
    
    def allocate(
        self,
        task_type: str,
        complexity: float = 1.0,
        max_budget: int = 100000
    ) -> int:
        """Allocate budget based on task type and complexity."""
        profile = self.task_profiles.get(task_type, {"base": 20000, "multiplier": 1.0})
        
        budget = int(profile["base"] * complexity * profile["multiplier"])
        
        return min(budget, max_budget)
    
    def dynamic_adjustment(
        self,
        run_id: str,
        current_budget: TokenBudget,
        performance_metrics: dict
    ) -> int:
        """Dynamically adjust budget based on performance."""
        # If agent is making progress, allow more tokens
        if performance_metrics.get("steps_completed", 0) > 0:
            progress_rate = (
                performance_metrics["steps_completed"] /
                performance_metrics.get("estimated_steps", 10)
            )
            
            if progress_rate > 0.5:
                # Agent is progressing well, allow 20% more
                return int(current_budget.allocated * 1.2)
        
        # If agent is stuck, reduce budget
        if performance_metrics.get("repeated_failures", 0) > 2:
            return int(current_budget.allocated * 0.8)
        
        return current_budget.allocated

Cost Tracking Dashboard

from fastapi import FastAPI
from datetime import datetime, timedelta

app = FastAPI()

@app.get("/budgets/{run_id}")
async def get_budget_status(run_id: str):
    budget = budget_manager.budgets.get(run_id)
    
    if not budget:
        return {"error": "Run not found"}
    
    return {
        "run_id": run_id,
        "allocated": budget.allocated,
        "used": budget.used,
        "remaining": budget.remaining,
        "usage_percent": f"{budget.usage_percent:.1%}",
        "status": budget.status.value,
        "cost_estimate": self._estimate_cost(budget.used),
    }

@app.get("/costs/summary")
async def get_cost_summary(timeframe: str = "24h"):
    """Get cost summary for the timeframe."""
    since = datetime.now() - timedelta(hours=int(timeframe.replace("h", "")))
    
    usage = await get_usage_since(since)
    
    total_tokens = sum(u["tokens"] for u in usage)
    total_cost = sum(u["cost"] for u in usage)
    
    by_task_type = {}
    for u in usage:
        task_type = u.get("task_type", "unknown")
        if task_type not in by_task_type:
            by_task_type[task_type] = {"tokens": 0, "cost": 0}
        by_task_type[task_type]["tokens"] += u["tokens"]
        by_task_type[task_type]["cost"] += u["cost"]
    
    return {
        "timeframe": timeframe,
        "total_tokens": total_tokens,
        "total_cost": f"${total_cost:.2f}",
        "by_task_type": by_task_type,
        "avg_tokens_per_run": total_tokens / max(len(usage), 1),
    }

def _estimate_cost(self, tokens: int) -> str:
    """Estimate cost based on token count."""
    # GPT-4o pricing
    cost_per_1k = 0.005  # $5/1M tokens
    cost = (tokens / 1000) * cost_per_1k
    return f"${cost:.4f}"

Budget Policies

class BudgetPolicy:
    def __init__(self):
        self.policies = {
            "default": {"daily_limit": 1000000, "per_run_limit": 100000},
            "premium": {"daily_limit": 5000000, "per_run_limit": 500000},
            "free": {"daily_limit": 100000, "per_run_limit": 10000},
        }
    
    def check_policy(self, user_tier: str, run_id: str) -> tuple[bool, str]:
        policy = self.policies.get(user_tier, self.policies["default"])
        
        # Check per-run limit
        budget = budget_manager.budgets.get(run_id)
        if budget and budget.allocated > policy["per_run_limit"]:
            return False, f"Exceeds {user_tier} per-run limit of {policy['per_run_limit']} tokens"
        
        # Check daily limit
        daily_usage = get_daily_usage(user_tier)
        if daily_usage >= policy["daily_limit"]:
            return False, f"Exceeds {user_tier} daily limit of {policy['daily_limit']} tokens"
        
        return True, "Within policy limits"

Best Practices

  • Set per-task budgets — Different tasks need different limits
  • Monitor in real-time — Track usage as it happens
  • Implement warnings — Alert before hitting hard limits
  • Log everything — For cost analysis and optimization
  • Use tiered policies — Different limits for different user levels
  • Auto-adjust — Increase budgets for progressing agents, decrease for stuck ones

Conclusion

Token budgets transform unbounded agent costs into predictable expenses. By allocating budgets per run, tracking usage in real-time, and enforcing hard limits, you maintain control over your AI spend. Start with conservative budgets, adjust based on actual usage patterns, and implement tiered policies for different user levels.