KaireonAI’s arbitration runs in two stages: a hard-constraint filter that drops any offer that can’t be served right now, then the weighted composite score (PRIE / multi-objective) that ranks the survivors. This page covers both stages plus the optional agent-in-the-loop negotiation that can propose bounded tweaks after a winner is selected.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.
Hard constraints — what gets dropped before ranking
Three constraint types are enforced on every recommendation, before scoring:Budget caps
Offer.budget.dailyCapCents— daily spend ceiling (auto-resets at day boundary)Offer.budget.lifetimeCapCents— lifetime spend ceiling (never resets)- Running totals (
currentDailySpentCents,currentLifetimeSpentCents) update automatically on positive outcomes viaconsumeBudgetin the/respondflow lastDailyResetDatebookkeeping is handled by the platform
Inventory counters
Offer.inventory.totalStock— starting stockOffer.inventory.remainingStock— decrements on positive outcomes; offer drops from candidate set at 0- Unset = untracked (no inventory enforcement)
Frequency caps
Offer.frequencyCaps.perCustomer.{daily,weekly,monthly}— max impressions per customer per window- Counts are read from
InteractionHistorywith a single batched query per window (not N+1 across candidate offers) - Fail-open: if the counter query throws, the offer is allowed through and the failure is logged
Example offer with constraints
recommend runs for this offer, it will be dropped from candidates if:
- Today’s spend has hit $500.00
- Lifetime spend has hit $50,000.00
- Remaining stock is 0
- This customer has already seen this offer today / 3 times this week / 6 times this month
What is NOT supported in V1
- Lagrangian relaxation for multi-constraint soft-optimization — everything here is a hard binary filter.
- Cross-offer frequency caps (e.g., “show this customer at most 5 offers total per day across all offers”) — only per-offer.
- Budget alerting / refill workflows — you have to update
dailyCapCents/lifetimeCapCentsmanually. - Time-of-day or seasonality modulation — constraints are evaluated at decision time with current state.
Agent-in-the-loop negotiation (shadow-mode)
After arbitration selects a winner, some offers support a negotiation pass where an LLM agent proposes bounded tweaks — a small discount, a different term length, an add-on — within hard guardrails.Shadow-mode only in V1. Every proposal is validated against the guardrails in code (not just the prompt), and recorded in the audit log. The proposal is NEVER applied to a real customer in V1. Apply-mode will ship only after an offline eval harness proves zero guardrail violations over a production-quality sample.
Enabling negotiation
Three gates must pass, all fail-closed:-
Tenant opt-in:
-
Per-offer flag:
Offer.negotiable = true -
Per-offer guardrails:
Offer.negotiationGuardrailswith explicit bounds.
Guardrail schema
discount is absent, the agent MUST NOT propose a discount.
Calling the agent
NegotiationSession with proposals: ValidatedProposal[]. Each proposal has:
valid: booleanproposal: { rationale, discountPct?, termMonths?, bundleAddons?, finalPriceCents?, currency? } | nullviolations: GuardrailViolation[]— a typed list from 9 possible violation codes
AuditLog entry with action="negotiate_shadow" and entityType="decision_trace" — so DPO and audit teams can see exactly what the agent considered, even when nothing was applied.
What the agent cannot do
Enforced by code (platform/src/lib/negotiation/guardrails.ts):
- Propose a discount outside
[discount.minPct, discount.maxPct]→discount_below_floor/discount_above_ceiling - Propose a discount when none is permitted →
discount_not_permitted - Propose a term length outside the band →
term_below_floor/term_above_ceiling - Propose a final price below
priceFloorCents→price_below_floor - Use a currency not in
allowedCurrencies→currency_not_allowed - Bundle an add-on not in
bundleableAddons→addon_not_permitted - Omit the
rationalefield →rationale_missing - Exceed
maxProposalsin a single session → trailing proposals markedschema_invalid
valid: false and proposal: null. There is no code path that stores or surfaces a rejected proposal — the validation happens before any mutation.
Rate limits + cost
- Negotiation endpoint: 10 requests / minute / tenant (LLM cost control)
- No negotiation call ever runs on the
/recommendhot path - Configured AI provider is used (Claude / GPT / Gemini / Bedrock / Ollama / LM Studio — whatever is set in
AiConfig)
Promotion path to apply-mode
Not in V1. When we ship it, the gate will be:- Offline eval harness runs the agent against a ≥1000-session dataset
- Zero guardrail violations (hard requirement)
- Regulatory review on the proposal distribution
- Tenant-level admin explicitly opts in, per-offer
- Real-time kill switch wired in tenant settings
mode: "apply" become reachable.