> ## 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.

# Negotiation apply-mode (realtime)

> W15 hot-path wire that promotes accepted multi-turn negotiation sessions into the /api/v1/recommend response. Default off; gated by tenantSettings.aiAnalyzerSettings.negotiation.applyModeEnabled.

## 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

```json theme={null}
{
  "aiAnalyzerSettings": {
    "negotiation": {
      "applyModeEnabled": true,
      "regulatorReviewCleared": true,
      "dailyApplyCap": 50,
      "killSwitchTenant": false,
      "killSwitchGlobal": false,
      "recentValidationFailureRate": 0.0,
      "autoKillThreshold": 0.2
    }
  }
}
```

| Field                         | Default | Meaning                                                                                                                                                                              |
| ----------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `applyModeEnabled`            | `false` | Master flag. When off, the wire short-circuits with zero I/O — no latency cost for non-opt-in tenants.                                                                               |
| `regulatorReviewCleared`      | `false` | Only flip to `true` after the 30-day eval-harness run shows zero violations across the negotiation eval set. Without this flag every apply rejects with `regulator_review_required`. |
| `dailyApplyCap`               | `50`    | Hard cap on auto-applies per tenant per day (UTC).                                                                                                                                   |
| `killSwitchTenant`            | `false` | Per-tenant emergency stop. Trips immediately when set to `true`.                                                                                                                     |
| `killSwitchGlobal`            | `false` | Org-wide emergency stop, set centrally.                                                                                                                                              |
| `recentValidationFailureRate` | `0`     | Rolling 0..1 ratio. When ≥ `autoKillThreshold` the gate auto-trips.                                                                                                                  |
| `autoKillThreshold`           | `0.2`   | Threshold for the auto-trip above.                                                                                                                                                   |

## Response shape

```jsonc theme={null}
{
  "decisions": [
    {
      "offerId": "off_premium_savings",
      "rank": 1,
      "score": 0.84,
      "appliedNegotiation": {
        "sessionId": "sess_5f0a…",
        "proposal": {
          "rationale": "loyal customer, 24mo tenure",
          "discountPct": 12,
          "termMonths": 12,
          "finalPriceCents": 8800,
          "currency": "USD"
        }
      }
    },
    {
      "offerId": "off_basic",
      "rank": 2,
      "score": 0.73,
      "appliedNegotiationReject": {
        "sessionId": "sess_a1b2…",
        "reason": "apply_budget_exceeded"
      }
    }
  ],
  "meta": {
    "negotiationApply": { "applied": 1, "rejected": 1 }
  }
}
```

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

## Reject reasons

| `reason`                    | When                                                                                                                                |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `apply_mode_disabled`       | The flag is off (rare — short-circuits earlier; surfaces only in audit rows).                                                       |
| `offer_not_negotiable`      | `Offer.negotiable !== true`.                                                                                                        |
| `kill_switch_tripped`       | One of the three kill switches fired. The audit row's `changes.reject.source` says which: `tenant`, `global`, or `auto_error_rate`. |
| `regulator_review_required` | `regulatorReviewCleared` is false.                                                                                                  |
| `apply_budget_exceeded`     | Today's apply count ≥ `dailyApplyCap`.                                                                                              |
| `guardrail_violations`      | The session's `finalProposal` re-validation failed. The audit row carries the violation list.                                       |
| `guardrails_missing`        | Offer is `negotiable=true` but has no `negotiationGuardrails` configured — fail-CLOSED.                                             |

## 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

| Condition                           | Behavior                                                                                                                                                                                                                              |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `tenantSettings` lookup throws      | Wire stays OFF; `warn`-level log surfaces the lookup failure; recommend response unchanged.                                                                                                                                           |
| Offer / session lookup throws       | Wire degrades to `noOp`; `warn`-level log surfaces it; recommend response unchanged.                                                                                                                                                  |
| `AuditLog.count` throws             | `appliesUsedToday` returns `Number.MAX_SAFE_INTEGER` so the daily-cap gate trips and every apply rejects with `apply_budget_exceeded`. **Cap fails-CLOSED** — an outage cannot silently lift the limit. Surfaces at `error` severity. |
| Apply-row `AuditLog.create` throws  | Logged at `error` (compliance teeth) — the apply decoration is still returned to the caller, but the absence of the audit row is escalated for reconciliation.                                                                        |
| Reject-row `AuditLog.create` throws | Logged at `warn` — informational.                                                                                                                                                                                                     |
| Wire helper throws                  | The route's `try/catch` returns the recommend response **without** decoration; the apply-mode failure never breaks ranking.                                                                                                           |

## 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](../ai-ml/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](/decisioning/ranking-lagrangian)), 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](../ai-ml/negotiation-eval-harness) · [Decision flows](./decision-flows)
