Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kaireonai.com/llms.txt

Use this file to discover all available pages before exploring further.

What it does

Given a per-offer pacing plan (expected delivery curve across some period — e.g. 24 hourly buckets summing to 1.0) and the delivered history so far, pacing computes a score multiplier per offer:
  • Behind schedule (delivered < ideal) → multiplier > 1, boosting the offer’s rank so it catches up.
  • Ahead of schedule (delivered > ideal) → multiplier < 1, damping the offer so it falls back to plan.
  • On pace → multiplier ≈ 1, no-op.
The multiplier is bounded by [minMultiplier, maxMultiplier] (defaults [0.5, 2]) so a wildly behind offer cannot dominate the ranking and a wildly ahead one cannot disappear entirely.

Configuration

Two layers:
  1. Tenant flag under aiAnalyzerSettings.arbitration:
    { "arbitration": { "budgetPacingEnabled": true } }
    
  2. Per-offer pacing config on the Offer record under pacing:
    {
      "pacing": {
        "plan": {
          "planShape": [0.005, 0.003, /* … 24 hourly fractions … */],
          "totalBudget": 10000
        },
        "delivered": [12, 9, 8, /* … per-bucket actuals … */]
      }
    }
    
The plan shape can be flat (flatHourlyPlan(total)) or daytime-weighted (daytimeHourlyPlan(total)). The delivered array is updated by your delivery pipeline — the wire only reads it.

When pacing applies vs no-ops

  • Flag off → wire never runs.
  • Flag on AND offer has well-formed pacing.plan.planShape → multiplier applied, pacingAgg.applied++.
  • Flag on AND offer is missing pacing or has malformed shape → pacingAgg.awaitingConfig++. Score is preserved.

What gets logged

Per batch run with the flag on:
INFO pacing.applied {
  runId, tenantId,
  applied, awaitingConfig
}
When the flag is off, nothing is logged for pacing — same volume discipline as the Lagrangian runbook.

How the multiplier is computed

factor = actualDelivered / idealDelivered     (clamped to [0.01, 100])
multiplier = (1 / factor) ^ sensitivity       (default sensitivity = 0.5)
multiplier = clamp(multiplier, minMultiplier, maxMultiplier)
sensitivity controls how aggressively pacing reacts. Default 0.5 applies a square-root curve — a 2× under-pace doubles the score, a 4× under-pace triples it.

Honest limits

  • Bucket = current hour of day. V1 hard-codes currentBucket to now.getHours(). Operators that need other granularities (e.g., campaign-day or week-of-quarter) cannot configure that yet.
  • No automatic delivery tracking. The delivered array is read directly from Offer.pacing.delivered. Operators must update that array themselves; there is no built-in “on impression, increment delivered[currentHour]” hook in the batch pipeline yet.
  • Per-offer only. Cross-offer / portfolio pacing is not modeled in V1.

Operational checklist

  • Pick flatHourlyPlan for fixed-rate distribution; pick daytimeHourlyPlan for traffic-weighted distribution.
  • Watch pacing.applied per batch run. awaitingConfig should match exactly the offer count that lacks pacing config — anomalies suggest shape drift.
  • Tune sensitivity per channel: high-traffic channels (e.g. push) can tolerate aggressive sensitivity (0.7+); low-traffic (file-delivery) channels prefer conservative (0.3).

Cross-references

  • arbitration-exp3ix.mdx
  • arbitration-goal-seek.mdx
  • arbitration-lagrangian.mdx