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.