Skip to main content

What it does

When a multi-turn negotiation session reaches status: "accepted", its finalProposal represents the agreed terms (discount, term, bundle, final price). Apply-mode promotes that proposal into the realtime /api/v1/recommend response so downstream callers see the negotiated terms alongside the next-best-action ranking. Each ranked decision is decorated with one of:
  • appliedNegotiation: { sessionId, proposal } — every gate passed; the caller can present the negotiated terms.
  • appliedNegotiationReject: { sessionId, reason } — a session existed but a gate blocked the apply (e.g., daily cap exceeded, regulator review still required, kill switch tripped).
When no session exists for a given (tenant, customer, offer), the decision is returned unchanged.

Configuration

Response shape

meta.negotiationApply is omitted when the wire is in noOp (flag off or no relevant sessions found).

Reject reasons

Audit trail

Every apply and every reject writes one audit-log row with:
  • action: "negotiate_apply_realtime" (apply) or "negotiate_apply_realtime_reject" (reject).
  • entityType: "negotiation_session", entityId: the session id.
  • changes: { sessionId, applied, proposal | reject }.
The route also runs appliesUsedToday from a count of these rows, so the audit chain is the source of truth for daily-cap accounting.

Failure modes — fail-CLOSED

Honest limits

  1. Concurrency under-counting. appliesUsedToday is read-once + per-call increment in memory; the audit row is written fire-and-forget. Two simultaneous /recommend calls for the same (tenant, day) can both read N and both apply up to cap, so the day total can exceed cap by (concurrency - 1) * applies_per_call before audit rows materialize. Acceptable for V1 because (a) the apply decoration is informational — no irreversible side effect — and (b) compliance retains the audit-log rows for retroactive reconciliation. A Redis atomic-counter increment with a TTL keyed negotiation:applies:{tenantId}:{YYYY-MM-DD} is on the roadmap and closes the gap.
  2. appliesUsedToday cost. AuditLog.count is correct but slow under very high tenant volume. Same Redis follow-up addresses both this and #1.
  3. Placements path not wired. The multi-placement /recommend response shape is decorated separately. Today the apply-mode wire only runs on the single-flow + auto-resolve paths. Wiring placements is a small follow-up.

Operational checklist

  • Enable applyModeEnabled for one tenant first. Watch the meta.negotiationApply counter on /recommend responses and the AuditLog rows.
  • Run the negotiation eval harness for 30 nights. When zeroViolationClearance >= 0.95 for the full window, flip regulatorReviewCleared to true.
  • A regression in any of: solverFailed > 0 on the realtime ranking applied log line (see Lagrangian Ranking), sustained kill_switch_tripped: { source: "auto_error_rate" } rejects, or AuditLog write failures at error severity → flip applyModeEnabled to false immediately. The wire is designed so flag-off is bit-identical to the pre-W15 response.
See also: Negotiation eval harness · Decision flows