What it does
The hard-constraint filter (lib/ranking/constraints.ts) drops offers
that fully violate a budget, inventory, or frequency cap. Lagrangian
ranking runs after that filter and softly penalizes the survivors
that are close to a cap, so traffic naturally rotates toward
less-saturated offers. The penalty is computed from the dual variables
(shadow prices λ_k) of a relaxed resource-allocation problem.
When it adds value
W12 ships the cross-offer wire on the realtime
/api/v1/recommend hot
path: the rank node now combines per-offer Lagrangian constraints
(budget + inventory) with cross-offer constraints (channel_quota,
portfolio_budget, category_cap) in a single solve.
The hot-path realtime wire is the applyRealtimeRanking helper at
lib/ranking/realtime-wire.ts; the batch path (lib/batch-executor.ts)
uses the applyLagrangianAdjustment helper from W5.1. Both share the
same tenantSettings.aiAnalyzerSettings.ranking.lagrangianEnabled flag
and noOp/solverFailed semantics. The batch path also folds in cross-offer
constraints via resolveCrossOfferConstraints (gated by a separate
ranking.crossOfferEnabled flag): it models portfolio_budget and
category_cap but skips channel_quota, which has no per-candidate
channel context in the batch loop. The realtime wire models all three
cross-offer types under the single lagrangianEnabled flag.
Configuration
The flag lives insideaiAnalyzerSettings:
false is the
incident-response path.
What gets logged
Per batch run with the flag on,lib/batch-executor.ts emits a single
aggregated log line at completion:
If
solverFailed > 0 or the tenant-settings lookup itself failed, the
line is logged at error level instead of info.
When the flag is off, nothing is logged for Lagrangian — no
volume noise on tenants that haven’t opted in.
How the wire works
- The batch executor reads
tenantSettings.aiAnalyzerSettingsonce per run to check whether Lagrangian ranking is enabled. - On every customer iteration, after the hard-constraint filter and before ranking, the Lagrangian adjustment runs against the surviving candidates.
- The helper builds one Lagrangian constraint per offer with non-zero remaining budget AND/OR non-zero remaining inventory.
solveLagrangianruns dual ascent (200 iterations max, tolerance 1e-4, step size 0.1).- Returned
adjustedScoresoverwritecandidate.scorefor ranking.
Result shape
noOp and solverFailed are deliberately separate. A “solver crashed”
result is not a “no-op” — callers that conflate them will silently
mask production failures.
Benchmark
platform/perf/lagrangian-vs-weighted.ts compares the Lagrangian path
against a baseline weighted-composite ranker on 1000 synthetic
customers × 10 offers (5 inventory-constrained, 5 unconstrained).
Run:
platform/perf/baselines/2026-04-28-lagrangian-bench.json with the
following honest result:
Operational checklist
- Enable for one tenant first via the
aiAnalyzerSettings.ranking.lagrangianEnabledflag. - Watch the per-run
lagrangian.appliedlog line. Specifically:solverFailed > 0→ check the per-customerlagrangian adjustment threwerrors and file an issue.nonConverged > 0→ likely fine on small batches; investigate if it stays > 5 % of attempts.malformedConfigOffersCount > 0→ fix the offer’sbudget/inventoryJSON shape (numbers, not strings).defaultedCostOffersCount > 0→ solver ran but per-impression cost was unknown; results trustworthy only directionally.
- If a regression appears, flip the flag back to
false. The wire is designed so that flag-off behavior is bit-identical to pre-W5.1.
Cross-offer constraints (W12, realtime hot path)
A dedicated cross-offer-constraint table stores rules that span multiple offers. Three rule types ship today:
Per-pick semantics. Cross-offer cost rows are assessed per
candidate (per creative-pick), matching the
cross-offer.ts test
contract. Two creatives of the same offer each pay the cost — the
intent is “n picks toward the cap when n creatives are picked.” A
“count by offer” rule type is on the roadmap but not in the W12 scope.
Hot-path wire log
Per realtime recommend call with the flag on,lib/pipeline-runner.ts
emits a single realtime ranking applied log line at the rank node:
lagrangian.applied line, with two
additions: perOfferConstraintCount and crossOfferConstraintCount
let you distinguish “no constraints to model” from “only cross-offer
constraints active.”
Failure modes (fail-CLOSED)
The realtime hot path NEVER returns a 5xx because of the wire — the
worst case is degraded mode where scores are unchanged.
Roadmap
- W5.2 ships the EXP3-IX online-weights / budget-pacing / goal-seek
flags. They live in the same
aiAnalyzerSettings.ranking.*namespace. - A future iteration may add a “count by offer” cross-offer rule variant for tenants who want offer-pick semantics rather than creative-pick semantics. Today’s workaround is to limit candidates to one creative per offer upstream of the rank node.