How to Build a Dynamic Rate Limiting and Backpressure Management Layer for Enterprise Multi-Agent Systems That Call Competing LLM Providers Under Shared Token Budget Constraints

How to Build a Dynamic Rate Limiting and Backpressure Management Layer for Enterprise Multi-Agent Systems That Call Competing LLM Providers Under Shared Token Budget Constraints

Enterprise agentic AI has crossed a critical threshold in 2026. Multi-agent systems are no longer experimental curiosities; they are production infrastructure. A single enterprise workflow might now involve a planner agent, a retrieval agent, a code-generation agent, a validation agent, and a summarization agent, all firing concurrently, all calling different LLM providers (OpenAI, Anthropic, Google Gemini, Mistral, and others), and all drawing from a shared monthly token budget that finance approved in Q1 and will not renegotiate until Q4.

The problem is not that these systems are complex. The problem is that most teams treat rate limiting as an afterthought, bolting on a simple token counter after the architecture is already in production. When a burst of agent activity exhausts the budget at 2:00 AM, every downstream workflow stalls. When a single provider throttles your requests, agents pile up in a queue with no awareness of the congestion spreading through the system. The result is cascading failure, wasted spend, and very unhappy stakeholders.

This tutorial walks you through building a dynamic rate limiting and backpressure management layer specifically designed for enterprise multi-agent systems that compete for shared token budgets across multiple LLM providers. We will cover architecture design, implementation patterns, provider-aware routing, budget partitioning, and real-time backpressure signaling, with code examples throughout.

Understanding the Problem Space Before Writing a Single Line of Code

Before reaching for a library or spinning up a Redis instance, it is worth being precise about the three distinct problems you are actually solving:

  • Rate limiting: Enforcing the requests-per-minute and tokens-per-minute ceilings that LLM providers impose. Violating these results in HTTP 429 errors and exponential back-off delays.
  • Budget governance: Enforcing the organizational token budget across all agents so that no single agent or workflow can exhaust the shared pool. This is a business constraint, not a technical one, and it requires different controls.
  • Backpressure propagation: Communicating congestion signals upstream through the agent graph so that agents slow their own request generation rather than queuing endlessly. Without this, you get memory-bloating queues and stale results delivered long after they are useful.

A naive solution addresses only the first problem. A robust enterprise layer addresses all three simultaneously and dynamically, adjusting in real time as load patterns shift.

Architecture Overview: The Token Budget Arbiter

The central component in this design is what we call the Token Budget Arbiter (TBA). Every agent routes all LLM calls through the TBA rather than calling providers directly. The TBA is responsible for:

  • Maintaining a real-time view of token consumption per provider, per agent class, and per budget period
  • Selecting the optimal provider for each request based on current rate-limit headroom and cost
  • Issuing or denying "token leases" to agents before they make calls
  • Propagating backpressure signals to the agent orchestrator when thresholds are breached

Here is the high-level topology:


[Agent A] [Agent B] [Agent C]
     \        |        /
      \       |       /
    [Token Budget Arbiter]
     /    |    \    \
    /     |     \    \
[OpenAI][Anthropic][Gemini][Mistral]

The TBA is stateful and sits in the hot path, so it must be low-latency. We implement it as a lightweight gRPC service backed by Redis for shared state, making it horizontally scalable and resilient to single-node failures.

Step 1: Model Your Budget Partitions

The first concrete implementation step is defining your budget schema. A flat global token counter is not enough for enterprise use. You need hierarchical partitions that reflect organizational priorities.

A recommended partition model looks like this:

  • Global period budget: Total tokens available across all providers for the billing period (e.g., 500 million tokens per month)
  • Provider sub-budgets: Allocated slices per provider, informed by pricing and capability (e.g., 200M for OpenAI, 150M for Anthropic, 100M for Gemini, 50M for Mistral)
  • Agent-class quotas: Soft limits per agent type (e.g., planner agents get 20% of the pool, code agents get 40%)
  • Priority tiers: High, medium, and low priority lanes so that critical production workflows always have headroom

Encode this in a configuration schema your team can version-control:


budget:
  period: monthly
  global_tokens: 500_000_000
  providers:
    openai:
      allocated_tokens: 200_000_000
      rpm_ceiling: 10000
      tpm_ceiling: 2_000_000
    anthropic:
      allocated_tokens: 150_000_000
      rpm_ceiling: 5000
      tpm_ceiling: 1_000_000
    gemini:
      allocated_tokens: 100_000_000
      rpm_ceiling: 8000
      tpm_ceiling: 1_500_000
    mistral:
      allocated_tokens: 50_000_000
      rpm_ceiling: 3000
      tpm_ceiling: 500_000
  agent_classes:
    planner:
      share: 0.20
      priority: high
    code_generator:
      share: 0.40
      priority: high
    retrieval:
      share: 0.20
      priority: medium
    summarizer:
      share: 0.10
      priority: low
    validator:
      share: 0.10
      priority: medium

Step 2: Implement the Token Lease Protocol

Rather than letting agents call LLMs freely and then checking after the fact, the TBA uses a pre-flight lease system. An agent must request a token lease before making any LLM call. The lease reserves a token allotment, specifies which provider to use, and carries a TTL. If the agent does not consume the lease within the TTL (because the call failed or was cancelled), the tokens are returned to the pool.

Here is the gRPC service definition for the TBA:


syntax = "proto3";

service TokenBudgetArbiter {
  rpc RequestLease(LeaseRequest) returns (LeaseResponse);
  rpc CommitLease(LeaseCommit) returns (CommitResponse);
  rpc ReleaseLease(LeaseRelease) returns (ReleaseResponse);
  rpc StreamBackpressure(BackpressureSubscription) returns (stream BackpressureSignal);
}

message LeaseRequest {
  string agent_id = 1;
  string agent_class = 2;
  int32 estimated_tokens = 3;
  string preferred_provider = 4;
  Priority priority = 5;
  string task_id = 6;
}

message LeaseResponse {
  string lease_id = 1;
  string assigned_provider = 2;
  int32 granted_tokens = 3;
  int32 ttl_seconds = 4;
  LeaseStatus status = 5;
  BackpressureLevel backpressure = 6;
}

enum Priority {
  LOW = 0;
  MEDIUM = 1;
  HIGH = 2;
  CRITICAL = 3;
}

enum LeaseStatus {
  GRANTED = 0;
  DEFERRED = 1;
  DENIED = 2;
}

enum BackpressureLevel {
  NONE = 0;
  MILD = 1;
  MODERATE = 2;
  SEVERE = 3;
  CRITICAL = 4;
}

The key insight here is the BackpressureLevel field embedded in every lease response. Even when a lease is granted, the TBA tells the agent how congested the system currently is. A well-behaved agent uses this signal to throttle its own future request generation, which is the essence of cooperative backpressure.

Step 3: Build the Rate Limiter with Sliding Window Counters

For per-provider rate limiting, a sliding window counter implemented in Redis is the right tool. Token bucket and fixed window algorithms both have edge cases that cause burst spikes at window boundaries, which is exactly the behavior that triggers provider throttling.

Here is a Python implementation of the sliding window rate limiter using Redis:


import time
import redis
from dataclasses import dataclass
from typing import Optional

@dataclass
class RateLimitResult:
    allowed: bool
    remaining_tokens: int
    remaining_requests: int
    retry_after_ms: Optional[int]

class SlidingWindowRateLimiter:
    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client
        self.window_seconds = 60  # 1-minute sliding window

    def check_and_consume(
        self,
        provider: str,
        token_count: int,
        tpm_limit: int,
        rpm_limit: int
    ) -> RateLimitResult:
        now_ms = int(time.time() * 1000)
        window_start_ms = now_ms - (self.window_seconds * 1000)

        token_key = f"rl:tokens:{provider}"
        request_key = f"rl:requests:{provider}"

        pipe = self.redis.pipeline()

        # Remove expired entries from the sliding window
        pipe.zremrangebyscore(token_key, 0, window_start_ms)
        pipe.zremrangebyscore(request_key, 0, window_start_ms)

        # Count current usage in the window
        pipe.zrangebyscore(token_key, window_start_ms, "+inf", withscores=True)
        pipe.zcount(request_key, window_start_ms, "+inf")

        results = pipe.execute()
        token_entries = results[2]
        request_count = results[3]

        # Sum token usage from entries (score = timestamp, member = "count:uuid")
        current_tokens = sum(
            int(entry[0].decode().split(":")[0])
            for entry in token_entries
        )

        if current_tokens + token_count > tpm_limit:
            # Calculate when enough tokens will free up
            oldest_entry_ms = min(float(e[1]) for e in token_entries) if token_entries else now_ms
            retry_after = int(oldest_entry_ms + (self.window_seconds * 1000) - now_ms)
            return RateLimitResult(
                allowed=False,
                remaining_tokens=max(0, tpm_limit - current_tokens),
                remaining_requests=max(0, rpm_limit - request_count),
                retry_after_ms=retry_after
            )

        if request_count >= rpm_limit:
            return RateLimitResult(
                allowed=False,
                remaining_tokens=tpm_limit - current_tokens,
                remaining_requests=0,
                retry_after_ms=self.window_seconds * 1000 // rpm_limit
            )

        # Consume: add this request to the sliding window
        import uuid
        member = f"{token_count}:{uuid.uuid4()}"
        pipe = self.redis.pipeline()
        pipe.zadd(token_key, {member: now_ms})
        pipe.zadd(request_key, {f"req:{uuid.uuid4()}": now_ms})
        pipe.expire(token_key, self.window_seconds * 2)
        pipe.expire(request_key, self.window_seconds * 2)
        pipe.execute()

        return RateLimitResult(
            allowed=True,
            remaining_tokens=tpm_limit - current_tokens - token_count,
            remaining_requests=rpm_limit - request_count - 1,
            retry_after_ms=None
        )

Step 4: Implement Dynamic Provider Selection

When an agent requests a lease and specifies a preferred provider, the TBA does not blindly honor that preference. Instead, it runs a provider scoring algorithm that weighs current headroom, cost efficiency, and latency to select the best available provider for that specific request.


from dataclasses import dataclass
from typing import List, Dict
import math

@dataclass
class ProviderScore:
    provider: str
    score: float
    headroom_ratio: float
    estimated_cost_usd: float

class ProviderSelector:
    # Cost per million tokens (output), as of early 2026
    PROVIDER_COSTS = {
        "openai":    {"input": 2.50,  "output": 10.00},
        "anthropic": {"input": 3.00,  "output": 15.00},
        "gemini":    {"input": 1.25,  "output": 5.00},
        "mistral":   {"input": 0.70,  "output": 2.80},
    }

    def select_provider(
        self,
        preferred: str,
        estimated_tokens: int,
        rate_limit_headrooms: Dict[str, float],  # provider -> 0.0 to 1.0
        budget_headrooms: Dict[str, float],       # provider -> 0.0 to 1.0
        priority: str
    ) -> str:
        scores = []

        for provider, rl_headroom in rate_limit_headrooms.items():
            budget_headroom = budget_headrooms.get(provider, 0.0)

            # A provider with no headroom on either dimension is excluded
            if rl_headroom < 0.05 or budget_headroom < 0.05:
                continue

            cost_per_million = self.PROVIDER_COSTS[provider]["output"]
            # Normalize cost: lower cost = higher score component
            cost_score = 1.0 / math.log1p(cost_per_million)

            # Preference bonus: slight boost for the preferred provider
            preference_bonus = 0.15 if provider == preferred else 0.0

            # Priority modifier: high-priority requests favor headroom over cost
            if priority == "high":
                headroom_weight, cost_weight = 0.70, 0.30
            elif priority == "medium":
                headroom_weight, cost_weight = 0.50, 0.50
            else:
                headroom_weight, cost_weight = 0.30, 0.70

            combined_headroom = min(rl_headroom, budget_headroom)
            score = (
                combined_headroom * headroom_weight
                + cost_score * cost_weight
                + preference_bonus
            )

            scores.append(ProviderScore(
                provider=provider,
                score=score,
                headroom_ratio=combined_headroom,
                estimated_cost_usd=(estimated_tokens / 1_000_000) * cost_per_million
            ))

        if not scores:
            return None  # All providers exhausted; issue DEFERRED lease

        scores.sort(key=lambda x: x.score, reverse=True)
        return scores[0].provider

Step 5: Build the Backpressure Propagation Engine

Backpressure is the most underbuilt component in most enterprise agent systems. The goal is to make congestion visible and actionable to the agents and orchestrators that are generating load, so they can self-regulate rather than waiting for hard failures.

The TBA continuously computes a system-wide backpressure level and streams it to all subscribed agents and orchestrators via a Server-Sent Events (SSE) or gRPC streaming endpoint. Here is the backpressure computation logic:


from enum import IntEnum
from dataclasses import dataclass
from typing import Dict

class BackpressureLevel(IntEnum):
    NONE = 0
    MILD = 1
    MODERATE = 2
    SEVERE = 3
    CRITICAL = 4

@dataclass
class BackpressureSignal:
    level: BackpressureLevel
    global_budget_consumed_pct: float
    provider_headrooms: Dict[str, float]
    recommended_delay_ms: int
    shed_low_priority: bool
    message: str

class BackpressureEngine:
    THRESHOLDS = {
        # (global_budget_pct, min_provider_headroom) -> BackpressureLevel
        (0.95, 0.0):  BackpressureLevel.CRITICAL,
        (0.85, 0.10): BackpressureLevel.SEVERE,
        (0.70, 0.20): BackpressureLevel.MODERATE,
        (0.50, 0.30): BackpressureLevel.MILD,
    }

    def compute(
        self,
        global_consumed_pct: float,
        provider_headrooms: Dict[str, float]
    ) -> BackpressureSignal:
        min_headroom = min(provider_headrooms.values()) if provider_headrooms else 0.0
        level = BackpressureLevel.NONE

        for (budget_threshold, headroom_threshold), bp_level in sorted(
            self.THRESHOLDS.items(), key=lambda x: x[1], reverse=True
        ):
            if global_consumed_pct >= budget_threshold or min_headroom <= headroom_threshold:
                level = bp_level
                break

        delay_map = {
            BackpressureLevel.NONE:     0,
            BackpressureLevel.MILD:     200,
            BackpressureLevel.MODERATE: 800,
            BackpressureLevel.SEVERE:   3000,
            BackpressureLevel.CRITICAL: 10000,
        }

        return BackpressureSignal(
            level=level,
            global_budget_consumed_pct=global_consumed_pct,
            provider_headrooms=provider_headrooms,
            recommended_delay_ms=delay_map[level],
            shed_low_priority=level >= BackpressureLevel.SEVERE,
            message=self._describe(level, global_consumed_pct, min_headroom)
        )

    def _describe(self, level, budget_pct, headroom):
        if level == BackpressureLevel.CRITICAL:
            return f"CRITICAL: {budget_pct:.1%} budget consumed. Halt non-critical agents immediately."
        elif level == BackpressureLevel.SEVERE:
            return f"SEVERE: Dropping low-priority tasks. Min provider headroom at {headroom:.1%}."
        elif level == BackpressureLevel.MODERATE:
            return f"MODERATE: Add {800}ms delay between agent requests."
        elif level == BackpressureLevel.MILD:
            return f"MILD: Light congestion detected. Prefer cost-efficient providers."
        return "NONE: System operating normally."

Step 6: Make Your Agents Backpressure-Aware

The TBA is only half the solution. Agents must be designed to react to backpressure signals. A backpressure-aware agent base class should do the following:

  • Subscribe to the TBA's backpressure stream on startup
  • Maintain a local copy of the current backpressure level
  • Apply the recommended delay before each LLM call
  • Drop or defer low-priority subtasks when the level reaches SEVERE or CRITICAL
  • Emit its own telemetry so the TBA can see per-agent consumption patterns

import asyncio
import logging
from typing import Optional

logger = logging.getLogger(__name__)

class BackpressureAwareAgent:
    def __init__(self, agent_id: str, agent_class: str, tba_client, priority: str = "medium"):
        self.agent_id = agent_id
        self.agent_class = agent_class
        self.tba = tba_client
        self.priority = priority
        self.current_backpressure = BackpressureLevel.NONE
        self._bp_listener_task: Optional[asyncio.Task] = None

    async def start(self):
        self._bp_listener_task = asyncio.create_task(self._listen_backpressure())

    async def _listen_backpressure(self):
        async for signal in self.tba.stream_backpressure(self.agent_id):
            self.current_backpressure = signal.level
            if signal.shed_low_priority and self.priority == "low":
                logger.warning(
                    f"Agent {self.agent_id}: Backpressure SEVERE. Pausing task generation."
                )
                await self._pause_task_generation()

    async def call_llm(self, prompt: str, estimated_tokens: int, task_priority: str = None) -> str:
        priority = task_priority or self.priority

        # Apply backpressure delay before even requesting a lease
        delay_ms = self._get_recommended_delay()
        if delay_ms > 0:
            await asyncio.sleep(delay_ms / 1000)

        # Request a token lease from the TBA
        lease = await self.tba.request_lease(
            agent_id=self.agent_id,
            agent_class=self.agent_class,
            estimated_tokens=estimated_tokens,
            priority=priority
        )

        if lease.status == "DENIED":
            raise BudgetExhaustedException(f"Token budget exhausted for agent {self.agent_id}")

        if lease.status == "DEFERRED":
            # Wait and retry once
            await asyncio.sleep(lease.retry_after_seconds)
            lease = await self.tba.request_lease(
                agent_id=self.agent_id,
                agent_class=self.agent_class,
                estimated_tokens=estimated_tokens,
                priority=priority
            )

        try:
            result = await self._call_provider(lease.assigned_provider, prompt)
            await self.tba.commit_lease(lease.lease_id, actual_tokens=result.token_count)
            return result.content
        except Exception as e:
            await self.tba.release_lease(lease.lease_id)
            raise

    def _get_recommended_delay(self) -> int:
        delay_map = {
            BackpressureLevel.NONE:     0,
            BackpressureLevel.MILD:     200,
            BackpressureLevel.MODERATE: 800,
            BackpressureLevel.SEVERE:   3000,
            BackpressureLevel.CRITICAL: 10000,
        }
        return delay_map.get(self.current_backpressure, 0)

    async def _pause_task_generation(self):
        # Subclasses override this to pause their internal task queues
        pass

    async def _call_provider(self, provider: str, prompt: str):
        # Subclasses implement the actual provider SDK call here
        raise NotImplementedError

Step 7: Add Observability and Budget Burn Alerts

A dynamic rate limiting layer without observability is just a black box with feelings. You need three categories of metrics flowing into your observability stack (Prometheus, Grafana, or your preferred platform):

Key Metrics to Instrument

  • Token burn rate: Tokens consumed per minute, per provider, per agent class. Alert when the burn rate implies budget exhaustion before the period ends.
  • Lease denial rate: The percentage of lease requests that result in DEFERRED or DENIED. A rising denial rate is your earliest warning signal.
  • Provider headroom distribution: A gauge per provider showing remaining TPM and RPM capacity. Helps you spot when one provider is consistently saturated while others have slack.
  • Backpressure level histogram: How much time the system spends at each backpressure level. If you are spending more than 5% of time at MODERATE or above, your budget allocation needs rebalancing.
  • Lease TTL expiry rate: Leases that expire without being committed indicate agents that are crashing or timing out after acquiring resources, which is a sign of a different class of bug.

Set a budget burn alert that fires when projected end-of-period consumption, based on the current 24-hour rolling average burn rate, exceeds 100% of the allocated budget. This gives you days, not hours, to respond.

Step 8: Handle Provider Outages with Graceful Degradation

In 2026, even the largest LLM providers experience partial outages, regional degradations, and model-specific throttling events. Your TBA must treat provider health as a first-class concern. Implement a lightweight circuit breaker per provider that tracks consecutive error rates and temporarily removes a degraded provider from the selection pool:


from collections import deque
from datetime import datetime, timedelta

class ProviderCircuitBreaker:
    def __init__(self, provider: str, failure_threshold: int = 5, recovery_seconds: int = 60):
        self.provider = provider
        self.failure_threshold = failure_threshold
        self.recovery_seconds = recovery_seconds
        self.failures = deque(maxlen=failure_threshold)
        self.open_until: Optional[datetime] = None

    def record_success(self):
        self.failures.clear()
        self.open_until = None

    def record_failure(self):
        self.failures.append(datetime.utcnow())
        if len(self.failures) >= self.failure_threshold:
            self.open_until = datetime.utcnow() + timedelta(seconds=self.recovery_seconds)
            logger.warning(f"Circuit OPEN for provider {self.provider} until {self.open_until}")

    @property
    def is_open(self) -> bool:
        if self.open_until and datetime.utcnow() < self.open_until:
            return True
        if self.open_until and datetime.utcnow() >= self.open_until:
            self.open_until = None  # Half-open: allow one probe request
        return False

When a provider's circuit is open, the TBA excludes it from provider selection and redistributes its budget headroom proportionally to the remaining healthy providers, ensuring your agent workflows continue with minimal disruption.

Putting It All Together: Deployment Checklist

Before shipping this to production, run through the following checklist:

  • Redis cluster mode: Ensure your Redis instance is clustered or uses Redis Sentinel for HA. The TBA's state is your system's source of truth for budget consumption.
  • Lease TTL tuning: Set lease TTLs conservatively (2x your p99 LLM call latency) to avoid both premature expiry and long-lived orphaned leases.
  • Budget reset automation: Automate the monthly budget reset with a scheduled job that also archives the previous period's consumption data for cost reporting.
  • Shadow mode testing: Run the TBA in shadow mode (observe but do not block) for one week before enabling enforcement, so you can calibrate thresholds against real traffic.
  • Graceful shutdown: The TBA must drain in-flight leases cleanly on shutdown. Use a shutdown hook that waits for all active leases to commit or expire before terminating.
  • Multi-region replication: If your agents run across regions, replicate budget state with a small consistency lag rather than routing all lease requests to a single region, which creates a latency bottleneck.

Common Pitfalls to Avoid

  • Underestimating token counts: Agents often underestimate the tokens they will consume because they do not account for system prompts, few-shot examples, or tool call outputs. Build a 20% estimation buffer into your lease requests.
  • Treating all agents as equal: A summarization agent that runs 1,000 times per day and a planner agent that runs 10 times per day have very different budget impact profiles. Model them separately.
  • Ignoring output tokens in cost modeling: Output tokens are typically 4 to 10 times more expensive than input tokens across all major providers. Your budget tracking must account for both separately.
  • Static backpressure thresholds: Thresholds that work in January will not work in March when your agent fleet has grown. Build in quarterly threshold reviews or, better yet, use a simple adaptive algorithm that adjusts thresholds based on rolling consumption trends.

Conclusion

Building a dynamic rate limiting and backpressure management layer for enterprise multi-agent systems is not glamorous work, but it is the difference between an agentic AI platform that scales reliably and one that implodes the first time a quarterly report triggers a burst of concurrent agent activity.

The architecture described here, centered on the Token Budget Arbiter with sliding window rate limiting, pre-flight lease protocols, dynamic provider selection, and cooperative backpressure propagation, gives you the control plane your agents need to operate safely within real-world budget and capacity constraints.

The most important mindset shift is this: backpressure is not an error condition; it is a communication protocol. When your system is under load, you want that information to flow upstream as fast as possible so that agents can self-regulate intelligently rather than waiting for a hard wall. Build that communication channel first, and everything else becomes easier to tune.

In 2026, the teams winning with agentic AI are not necessarily the ones with the most capable models. They are the ones whose infrastructure is disciplined enough to use those models efficiently, at scale, without burning through budgets or paging on-call engineers at 2:00 AM. Build the plumbing. The intelligence will follow.

Read more

5 Ways Enterprise Backend Teams Must Restructure AI Agent Observability Dashboards as OpenTelemetry's GenAI Semantic Conventions Hit Stable Status

5 Ways Enterprise Backend Teams Must Restructure AI Agent Observability Dashboards as OpenTelemetry's GenAI Semantic Conventions Hit Stable Status

Something quietly seismic happened in the observability world heading into H2 2026: OpenTelemetry's Semantic Conventions for Generative AI crossed the threshold from experimental to stable status. For most engineering teams buried in sprint cycles and on-call rotations, this milestone barely registered as a calendar event. But it should

By Scott Miller
Centralized AI Agent Schema Registry vs. Decentralized Tool Manifest Versioning: The Enterprise Backend Decision That Determines Whether Your Multi-Agent Workflows Survive Breaking API Contract Changes

Centralized AI Agent Schema Registry vs. Decentralized Tool Manifest Versioning: The Enterprise Backend Decision That Determines Whether Your Multi-Agent Workflows Survive Breaking API Contract Changes

It is mid-2026, and enterprise engineering teams are staring down a problem that nobody on the vendor roadmap fully warned them about. Multi-agent AI workflows, the ones orchestrating dozens of specialized agents across payment services, inventory systems, CRM platforms, and compliance engines, are breaking in production. Not because the models

By Scott Miller