Skip to content
OmniRoute source

Quota Sharing Engine

Doc reference: docs/routing/QUOTA_SHARE.md Part of Group B (plans 16 + 22).


The Quota Sharing Engine distributes a provider’s time-based quota (e.g. Codex 5-hour window, Kimi 1500 req/h) fairly across multiple API keys that share the same connection.

Problem it solves: OmniRoute proxies many API keys against the same upstream provider account. Without sharing logic, a burst from key A can exhaust the provider quota for the hour, leaving keys B and C blocked until the window resets. The engine prevents this by:

  1. Tracking each key’s rolling consumption per dimension (%, requests, tokens, $).
  2. Applying a work-conserving fair-share algorithm: a key may borrow from idle shares while the global pool is not saturated.
  3. Enforcing the result in the hot path (chatCore.ts) before the request reaches the upstream executor.

Implemented in src/lib/quota/fairShare.ts.

Condition Mode Behaviour
globalUsedPercent < saturationThreshold Generous Key may borrow up to global limit minus consumed-total
globalUsedPercent >= saturationThreshold Strict Enforce individual fair share strictly

Default saturationThreshold = 0.5 (env QUOTA_SATURATION_THRESHOLD).

For each active dimension in the pool, the engine computes:

fairShareAllowed = poolLimit × (allocationWeight / 100)
consumed = current rolling value for this key (from QuotaStore.peek)
remaining = fairShareAllowed - consumed

Then:

  • policy = hard: if consumed > fairShareAllowed and mode is strict → block.
  • policy = soft: if consumed > fairShareAllowed and mode is strict → penalize (deprioritize in combo; never hard-block).
  • policy = burst: allow while global headroom exists regardless of fair share.

capValue + capUnit on an allocation is a hard ceiling independent of mode or policy. Any dimension where consumed >= capValue always blocks the request.

A request is blocked if any dimension in the pool would block it. Dimensions are independent — a 5h% exhaustion does not affect the weekly% dimension.

In generous mode, a key whose allocation is under-consumed can use surplus from other keys’ unallocated shares. The formula is:

maxAllowed = globalLimit - consumedByOtherKeys

where consumedByOtherKeys = consumedTotal - consumedByThisKey. The teto global (pool limit for that dimension) is always the hard ceiling.


Implemented in src/lib/quota/sqliteQuotaStore.ts and redisQuotaStore.ts.

Two buckets per (apiKeyId, dimensionKey):

  • curr: current bucket (floor(nowMs / windowMs))
  • prev: previous bucket (curr - 1)

Effective rolling value:

effectiveBucketIndex = floor(nowMs / windowMs)
bucketStartMs = effectiveBucketIndex × windowMs
elapsed = nowMs - bucketStartMs
weight = 1 - elapsed / windowMs
effective = prev × weight + curr

Precision: ~99% accurate. The error is at most 1% of the window size at the boundary between buckets (inherent to the 2-bucket approximation).

SQLite driver: in-memory mutex per (apiKeyId | dimensionKey) key prevents the read-modify-write race. Pattern mirrors src/sse/services/auth.ts anti-thundering-herd.

Redis driver: Lua EVAL script for atomic increment — runs as a single Redis command.


  • Table: quota_consumption (see migration 073_quota_pools.sql / 074_quota_consumption.sql).
  • Best for single-instance deployments.
  • All persistence is in the existing OmniRoute SQLite DB (DATA_DIR/storage.sqlite).
  • Requires ioredis npm package.
  • Counters stored in Redis; metadata (pools/allocations) still in SQLite.
  • Best for multi-replica deployments where counters must be shared.

Via settings UI (/dashboard/settings → Quota Store), or via env vars:

Terminal window
QUOTA_STORE_DRIVER=redis
QUOTA_STORE_REDIS_URL=redis://localhost:6379

DB setting has precedence over env. If driver=redis but URL is absent or ioredis is not installed, the factory falls back to SQLite and logs a warning.

Driver selection order:

  1. DB setting quotaStore.driver
  2. Env QUOTA_STORE_DRIVER
  3. Default: sqlite

A pool can have multiple dimensions. Each dimension is independent:

QuotaDimension {
unit: "percent" | "requests" | "tokens" | "usd",
window: "5h" | "hourly" | "daily" | "weekly" | "monthly",
limit: number, // global pool ceiling for this dimension
}

Example: Codex plan (5h% + weekly%):

[
{ "unit": "percent", "window": "5h", "limit": 100 },
{ "unit": "percent", "window": "weekly", "limit": 100 }
]

A request must satisfy all dimensions to be allowed.


Implemented in src/lib/quota/planResolver.ts.

Precedence (highest to lowest):

  1. Manual DB override — provider_plans table, per connectionId.
  2. Known catalog — src/lib/quota/planRegistry.ts (data-only).
  3. Empty plan — no dimensions, manual configuration required.
Provider Dimensions
codex percent/5h/100, percent/weekly/100
glm tokens/5h (limit=0, unknown), tokens/weekly
minimax tokens/5h, tokens/weekly
bailian percent/5h/100, percent/weekly/100, percent/monthly/100
kimi requests/hourly/1500
alibaba requests/monthly/90000
openai, anthropic No default — manual configuration required

Runs before the upstream executor, after auth and policy checks:

resolveComboTargets / handleSingleModel
→ enforceQuotaShare(apiKeyId, connectionId, provider, estimatedCost)
→ getQuotaStore().peek() per dimension
→ fairShare.decideFairShare()
→ if block → return 429 (buildErrorBody, Hard Rule #12)
→ if allow + deprioritize → set quotaSoftPenalty=true on candidate
→ executor.execute()

Fail-open: if enforceQuotaShare throws, the request is allowed through with a pino.warn log. This prevents a quota-engine bug from blocking all traffic.

After a successful response:

executor returns success
→ spendRecorder.recordConsumption(apiKeyId, connectionId, provider, actualCost)
→ getQuotaStore().consume() per dimension
→ fail-open: errors logged as pino.warn, never propagated to client

Drift note: if consume fails post-response, the rolling counter under-counts. The saturation signal from the provider (e.g. anthropic-ratelimit-unified-5h-utilization) corrects the global estimate on the next request.

Combo soft penalty (open-sse/services/combo.ts)

Section titled “Combo soft penalty (open-sse/services/combo.ts)”

When decision.deprioritize === true:

if (candidate.quotaSoftPenalty) {
score *= QUOTA_SOFT_DEPRIORITIZE_FACTOR; // default 0.7
}

The penalty is applied after all other scoring factors. It lowers the auto-combo probability of selecting a saturated key without hard-blocking it.


/dashboard/costs/quota-share — Main pools page

Section titled “/dashboard/costs/quota-share — Main pools page”

Components (all in src/app/(dashboard)/dashboard/costs/quota-share/):

Component Purpose
QuotaConceptCard Introductory card explaining quota sharing to new users
CreatePoolModal Create a new quota pool (connection + name + initial allocations)
PoolCard Per-pool summary: name, connection, allocation count
DimensionBar Per-dimension stacked bar: each key’s share + global usage
AllocationTable Table with consumed, fair share, deficit/surplus, borrowing flag
BurnRateChart EMA burn-rate line chart (lazy Recharts via dynamic())
EditAllocationsModal Edit allocation weights, caps, and policies for a pool

The page hooks:

  • usePools — fetches GET /api/quota/pools every 30s.
  • usePoolUsage — fetches GET /api/quota/pools/[id]/usage on demand.
  • useLocalStoragePoolMigration — runs once on mount to migrate legacy LS data.

/dashboard/costs/quota-share/plans — Provider plan config

Section titled “/dashboard/costs/quota-share/plans — Provider plan config”
  • ProviderPlanConfigClient.tsx: dropdown to select a provider, view resolved plan (auto from catalog or manual override), and edit dimensions.
  • Changes write to PUT /api/quota/plans/[connectionId].
  • Deletion reverts to catalog or empty plan.

Variable Default Description
QUOTA_STORE_DRIVER sqlite Driver to use: sqlite or redis
QUOTA_STORE_REDIS_URL (empty) Redis URL, e.g. redis://localhost:6379
QUOTA_SATURATION_THRESHOLD 0.5 0..1; >= threshold activates strict mode
QUOTA_SOFT_DEPRIORITIZE_FACTOR 0.7 0..1; multiplier for soft-policy combo score
QUOTA_CONSUMPTION_RETENTION_DAYS 14 Days before GC removes old quota_consumption buckets

DB settings (quotaStore.*) override env vars.


Check that ioredis is installed (npm ls ioredis) and QUOTA_STORE_REDIS_URL is reachable. On connection failure the factory falls back to SQLite (logged at warn).

If peek throws, enforceQuotaShare treats the result as “allow” (fail-open). Check pino logs for quota:enforce and quota:factory entries to identify the root cause.

If the actual provider usage differs from the counters, it is expected — the 2-bucket sliding window has ~1% error at window boundaries, and consume is fire-and-forget post-response. The saturation signal (saturationSignals.ts) reads the real provider utilization with a 30s TTL and adjusts globalUsedPercent accordingly.

computeBurnRate requires at least 2 historical samples. New pools without prior consume calls will show tokensPerSecond: 0 and timeToExhaustionMs: null.


When /dashboard/costs/quota-share first loads, the hook useLocalStoragePoolMigration checks:

  1. localStorage.getItem("omniroute:quota-share:pools") is non-empty.
  2. GET /api/quota/pools returns [] (DB is empty).

If both are true, it posts each legacy pool to POST /api/quota/pools in batch, then removes the localStorage key. The migration is idempotent: condition 2 prevents re-migration.


quota-share is an internal-only routing strategy (INTERNAL_ROUTING_STRATEGY_VALUES in src/shared/constants/routingStrategies.ts). It is used exclusively by system-minted qtSd/ pool combos and is deliberately excluded from ROUTING_STRATEGY_VALUES so it never appears as a user-selectable option in the UI or API.


Two layers of automated coverage ship with the quota-share engine:

Suite Command What it covers
Unit (29 tests) node --import tsx/esm --test tests/unit/quota-share-strategy.test.ts DRR scheduler, saturation gating, concurrency caps, fairShare math, backlog queueing
Integration matrix npm run test:combo:matrix End-to-end routing decision through the real combo pipeline; DRR fairness + saturation deprioritization via live seams (registerQuotaFetcher, setLKGP, __setHeadroomSaturationFetcherForTests)

The integration matrix runs in CI alongside all 19 public strategies. The unit suite can be run standalone.


Three tables added by migrations 078, 079 and 085:

  • quota_pools + quota_allocations — pool definitions and per-key allocations.
  • quota_consumption — rolling 2-bucket counters per (apiKeyId, dimensionKey).
  • provider_plans — manual provider plan overrides (dimensions JSON per connectionId).

All tables added via idempotent CREATE TABLE IF NOT EXISTS migrations.


OmniRoute source repository (a58000c7685f)

HagiCode

HagiCode is an agentic coding workspace: structured workflows, multi-agent execution, and Hero Dungeon views turn ideas into shipped software.

Turn ideas into polished, usable software with a smarter, faster, and more enjoyable agentic coding workflow.

HagiCode light theme main interface screenshot
  • SmartStructured workflows turn intent into an executable path from idea to shipped change.
  • EfficientMulti-agent workflows keep research, implementation, and review moving in parallel.
  • FunHero Dungeon interfaces make long coding sessions visual, collaborative, and rewarding.
Visit HagiCode