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 this solves
Operators consistently want two related-but-different things:- “Don’t spam this customer.” A global per-day cap on total touches.
- “Don’t fatigue this category.” A per-category cap so Premium Cards doesn’t dominate every email.
frequency_cap ruleType, but the scope is what matters — global, category, subcategory, offer, channel, segment, or placement. Each scope filters a different slice of the alltime interaction summary.
The pattern
A contact policy is{ruleType, scope, scopeId?, config}. For frequency_cap, config carries:
maxPerDay/maxPerWeek/maxPerMonth(any subset; missing = no cap on that window)lookbackHours(optional, defaults to the window above)
interaction_summaries filtered by (customerId, scope, scopeId, period) and removes any candidate whose count would exceed the cap.
Recipe 1 — Global per-day cap (3 touches/day, all channels, all categories)
trace.contactPolicyResults[*].reason will read "frequency_cap exceeded: 3/day at scope=global".
Recipe 2 — Per-category cap (Cards category 5/week)
offer.categoryId === <cards-category-id> count against the cap. Loans offers are unaffected. The denormalized interaction_summaries.offerCategory column (added in fix #155) makes this query fast.
Recipe 3 — Per-channel cap (Email 2/day, push unlimited)
Recipe 4 — Per-offer cooldown (don’t show the same offer twice in 24h)
This is a different ruleType —cooldown, not frequency_cap:
cooldownHours.
What the trace will show
Gotchas
- Scope mismatches silently pass. If a frequency_cap rule has
scope: "category"butscopeId: null, it applies globally — not what you want. Validate scopeId is set when scope is anything other than"global". metric_conditionis a different beast. That ruleType reads a Compute node’s output (e.g.impression_count_30d > 10), not raw interaction history. Use it when you want windowed-aggregate-driven caps.maxPerDayresets at UTC midnight unlesslookbackHoursis set. For rolling 24h, passlookbackHours: 24.