The Offer Gate

Part two

The conversational strategy engine

Relationship stages, the readiness score, the veto list, fourteen operationalised selling rules, and three annotated transcripts.

This is the layer nothing in open source gives you. Verified across the entire corpus: no open-source project models relationship_stage, trust_score, offer_fatigue or upsell_readiness. Every memory system stores facts about a person; none stores a sales policy state about a relationship. Every agentic benchmark (tau2-bench, IntellAgent, CRMArena, ALMITA) evaluates a single session. GitHub search for relationship stage trust agent LLM customer returns zero repositories; customer lifetime value agent LLM returns zero. You are building this, not adopting it.

The good news: it is a small amount of deterministic code — roughly 300 lines of pure function plus a state machine — and it is the entire differentiator.


The architectural commitment everything else follows from

Three scores, three timescales, evaluated conjunctively. Never one additive number.

Every shipped open-source lead scorer is a monotonic accumulator: Mautic's PointBundle (verified: Point/Trigger/Group entities, additive deltas, no decay entity) and Apache Unomi's scoring plans (verified: evaluateScoringPlanElement.painless, resetScoringPlan.painless) both only go up with activity. Any monotonic accumulator eventually crosses any threshold. A chatty tyre-kicker who never intends to buy is classified as maximally ready after enough sessions. This is the single most common way a "relationship" bot becomes a pushy bot.

The decomposition:

R — Relationship capitalN — Need evidenceM — Moment
QuestionHave we earned the right to advise?Do we know what they need, and does a product fit?Is right now acceptable?
TimescaleRelationship-level, 45-day half-lifePer need-episode, resets when satisfiedPer-turn, live
FreshnessRecomputed nightly, may be staleRecomputed nightlyLIVE read at send time
Written byScheduled job onlyScheduled job onlyPer-turn classifier tier

An offer unlocks only if R ≥ θ_R AND N ≥ θ_N AND M ≥ θ_M AND no veto fires. Conjunctive, never additive — so a hot cart event can never numerically substitute for zero trust.

The invariant that makes this work: R and N are recomputed between conversations by a scheduled job; M and the vetoes are live reads. readiness_score is writable by exactly one module. Enforce it with a separate Postgres DB role, not a code comment — "recompute readiness after each turn" is the seductive change a team makes under pressure, and it is precisely the change that turns this into a bot that talks itself into an offer inside a single conversation. A model asked mid-chat "is this customer ready?" is primed by the immediate context and by its own helpfulness training to say yes. The in-conversation agent may READ the gate and may never WRITE it.


1. Relationship stages

Six states. Five are relationship depth; two (COOLING, DORMANT/ARCHIVED) are cadence states and must not be conflated with depth — a customer in COOLING is still TRUSTED, they are just not currently contactable.

Implement as a statechart with guards that are pure functions the LLM cannot invoke. Use fgmacedo/python-statemachine (MIT, 1,310 stars, pushed 2026-09-14) or XState (MIT, 30,114 stars, zero dependencies) for TypeScript. Do not use pytransitions — verified dead: last commit 2025-09-09, last release 2025-07-02.

The inversion that matters: the model proposes events; the machine disposes. A guard is a function; a prompt is a suggestion.


S0 · STRANGER

Entry: Default on first contact. Channel identity exists, nothing else.

May do: Answer any question fully and well. Ask clarifying questions. Disclose AI status (mandatory — see §3 veto 14). Capture language preference as a logged consent event.

Must NOT do: Mention any product unprompted. Make any offer at any tier. Ask personal questions beyond what the immediate question requires. Send any proactive message.

Exit to HELPED: ≥1 substantive advisory exchange AND ≥1 durable slot extracted at confidence ≥0.7 with a stored source_utterance_id.

Regression: N/A (floor state).


S1 · HELPED

Entry: As above.

May do: Everything S0 allows, plus: reference previously stated facts (this is the first visible memory moment and it disproportionately shapes the relationship — see §5 rule 12). Proactive educational touches inside the attention budget. SOFT MENTION only if R clears 62 (product named in passing while answering their question — no CTA, no price, no link).

Must NOT do: Consultative recommendation or direct offer regardless of need evidence. Proactive contact more than the ACTIVE cadence tier permits.

Exit to KNOWN: dossier_depth ≥ 4 verified distinct slots AND ≥2 sessions on separate calendar days AND ≥1 verbatim problem statement stored with provenance — never inferred from purchase logs.

Regression to STRANGER: Never. Stages do not reset; capital decays (see below).


S2 · KNOWN

Entry: As above.

May do: SOFT MENTION freely (budget permitting). Replenishment reminders for consumables where cycle position is in-window. Ask one diagnostic question per session.

Must NOT do: Consultative recommendation — no advice-acceptance evidence exists yet, so you do not know whether this person treats you as an advisor or a search box.

Exit to TRUSTED: S2 conditions PLUS advice-acceptance evidence — a bot recommendation followed or explicitly acknowledged within 2 turns, OR ≥1 completed purchase — AND ≥1 customer-initiated return after ≥7 days of silence. The unforced return is the cleanest advisor-status signal available and the hardest to fake.


S3 · TRUSTED

Entry: As above.

May do: CONSULTATIVE RECOMMENDATION when N ≥ 55 and M ≥ 70 — explicit recommendation, stated reasoning, must cite ≥1 stored verbatim customer utterance with its date, explicit low-pressure out. DIRECT OFFER only when R > 85 and N > 75.

Must NOT do: Exceed 2 offers per 30 days or 1 per 14 days. Re-raise a declined offer, ever. Interrupt a high-habit/low-attitude customer's routine reorder with a new-product proposal (see §5 rule 6).

Exit to ADVOCATE: ≥2 purchases, zero unresolved complaints in 90d, plus a referral or unsolicited praise utterance.

Regression to KNOWN: trust_credibility falls below the S3 floor after a wrong recommendation the customer corrected, OR two consecutive declines, OR any complaint with a resolution the customer did not accept.


S4 · ADVOCATE

Entry: As above.

May do: Everything S3 allows. Early access and genuine first-look offers. Ask for referrals — once, and never again if declined.

Must NOT do: Treat advocacy as a licence to increase frequency. The attention budget does not expand with stage. This is the single most common way a relationship bot destroys its best customers.


SX · COOLING (transient overlay, not a depth state)

Entry: post-offer (7d minimum), post-decline (14d), post-complaint (14d after resolution), post-vulnerability-signal (3 sessions).

May do: Answer inbound perfectly. Care check-ins if M is high.

Must NOT do: Any offer at any tier.

Exit: A durable timer fires — a DBOS scheduled workflow, not a read-time query. This matters: a cooldown evaluated only when the customer happens to message leaves the state invisible between conversations, so you cannot inspect it, cannot batch on it, and cannot debug it. Make it a first-class fact.


SZ · DORMANT → ARCHIVED

Entry: 3 consecutive proactive messages with no reply or read within 7 days each.

May do: Answer inbound perfectly, forever. Archived is not deleted.

Must NOT do: Anything proactive. Escalate frequency. Send "win-back" blasts.

Exit: An inbound message, never a timer. The cadence controller is monotonically inverse and has no win-back escape hatch. Any code path that increases send rate at falling engagement is the defect, not the feature.


Decay, not reset: R_t = R_peak × 0.5^(days_silent / 45), floored at 0.4 × R_peak. Relationships fade; they do not zero out. A customer who returns after four months is not a stranger.

Tenure never advances a stage. Reinartz & Kumar (JM 2003, 67(1):77-99, verified) found relationship characteristics, not duration, drive profitable lifetime — roughly half of long-tenure customers are barely profitable. Every transition above is event-driven.


2. The readiness score

R — Relationship capital (0–100)

All features percentile-normalised within channel. This is not optional: Telegram exposes no read receipts at all (verified — only message_reaction, which requires admin rights in the chat, and my_chat_member for blocks), while Viber emits delivered and seen and WhatsApp emits sent/delivered/read. A pooled cross-channel model makes every Telegram customer look systematically colder, and they will never receive an offer. Mark unavailable features missing, never impute neutral.

#FeatureComputationw
R1Advice-acceptance rateFraction of bot recommendations followed or explicitly acknowledged within 2 turns, classified {accepts, defers, rejects}0.20
R2Customer-initiated session shareWilson lower bound over 90d0.15
R3Self-disclosure depthlog1p(distinct person-slot types ever extracted) via a GLiNER2 person schema (occupation, family, pet, skin type, life event)0.12
R4CSAT / explicit thumbs-upFrom Chatwoot CSAT or Telegram message_reaction0.12
R5Customer questions directed at the botPer turn — a direct measure of being treated as an advisor0.10
R6Unprompted return latencyInverse-rank normalised0.10
R7Warmth markersPoliteness markers + positive-emoji rate per 100 tokens0.08
R8Talk-time balanceTent function peaking at 0.60 customer token share — penalises both a lecturing bot and one-word replies0.06
R9Linguistic coordinationMeasured in both directions (see caveat below)0.04
R10Explicit trust statementsZero-shot label0.03

R1 carries the heaviest weight because it is the operational definition of advisor status. Everything else is a proxy for it.

Cap any single feature at 25% of the total so no one channel can be gamed by a talkative customer.

Coordination caveat: ConvoKit's Coordination measures power asymmetry, not warmth — in "Echoes of Power" the lower-status speaker accommodates more. A customer coordinating heavily toward the bot may be deferential, confused, or mirroring your jargon. Hence w=0.04 and bidirectional measurement.

Slavic-language caveat, and it is load-bearing: ConvoKit (MIT, 649 stars, pushed 2026-07-01) ships politeness strategies for English and Chinese only — verified by listing convokit/politeness_collections/: exactly politeness_api, politeness_local, politeness_cscw_zh. Nothing Slavic. Running politeness_api over Ukrainian text does not fail loudly; it returns a plausible near-zero for every message, and your relationship model concludes every customer is equally distant forever. The port is ~1 engineer-week (three JSON lexicons plus a ~200-line regex extractor mirroring politeness_cscw_zh), must lemmatise first with spaCy uk_core_news_lg (MIT, NER F1 0.872) because Slavic inflection defeats raw n-gram matching, and must add the two markers English has no analogue for: address register (Ви/ти via 2nd-person pronoun + verb agreement — the highest-information politeness feature in these languages) and diminutives (-очк-, -еньк-, -ик-, a productive morphological warmth device). Validate against 300–500 human-rated messages before any of it moves an offer decision. An unvalidated rapport score gating revenue is worse than no score, because it looks like evidence.


N — Need evidence (0–100), with two hard sub-gates

N = min(N1_gate, N4_gate) × weighted_sum(N2, N3, N5, N7, N8)

N1 — Slot coverage. HARD GATE ≥ 0.6. From a GLiNER2 (Apache-2.0, 1,859 stars, pushed 2026-08-24) need schema: {product_category, attribute, constraint, budget, recipient, occasion, deadline}. You have no business proposing a product until you know what it is for, who it is for, and roughly what they will spend. Schema changes are a JSON config edit, not a retraining run — which is why this is iterable weekly.

N4 — Price-band fit. HARD GATE. Offer price must sit inside [p25, p75 + 1σ] of the customer's revealed band, derived from explicit budget mentions plus trimmed-mean realised order value.

FeatureNotes
N2Purchase-intent probabilityYour own SetFit head. Bootstrap from j-hartmann/purchase-intention-english-roberta-large but note: 58 downloads/month, no license tag, last modified 2023-01-02 — validate, do not trust
N3Aspect sentiment on dealbreakersMust be non-negative on every dealbreaker attribute. yangheng/deberta-v3-base-absa-v1.1 (MIT, 91,613 dl/mo) standalone — not the PyABSA framework, whose default v2 branch last committed 2025-08-11 with its last tagged release from 2023-03-21
N5Replenishment-cycle positionTent peaking at 0.8–1.3× median inter-purchase interval, zero outside 0.5–2.0×. Kumar, George & Pancras (J. Retailing 2008, 84(1):15-27) found an inverted-U — both too-soon and too-late reduce cross-buying
N6Gift-vs-selfFrom the recipient slot + shipping-address mismatch + occasion entity
N7Browse-to-chatCategory viewed in prior 24h
N8Cart / abandonment eventIn window

N9 — Out-of-scope cap. If max-softmax < τ or the OOS head fires, N is capped at 40. A bot that does not understand the question has no business recommending anything, and "I don't understand, so I'll suggest something" is the single most trust-destroying behaviour a sales bot exhibits. Set τ using the CLINC-OOS protocol (hold out whole intent classes, set τ where in-scope recall stays ≥0.95), not its data — it is banking/assistant intents, not commerce.

Gift purchases must be excluded before fitting any replenishment model. Gifts are irregular, shipped elsewhere, and consumed by someone else; leaving them in corrupts every inter-purchase-interval estimate and the bot will pitch refills for products the customer never used.


M — Moment (0–100, per-turn, <50ms budget)

Runs on every message. At ~12,000 classifier passes per 1,000 conversations on a small encoder, this costs well under half a cent per 1,000 conversations against ~$3.50 for generation — there is no economic argument for sampling.

SignalSource
M1Turn valence ≥ neutrallxyuan/distilbert-base-multilingual-cased-sentiments-student (apache-2.0, 1.1M dl/mo, 12 languages)
M26-dim emotion panel {lean_in, gratitude, confusion, disappointment, anger, distress}EWMA over last 5 customer turns, ~2-turn half-life, so one grumpy message suppresses but does not permanently veto
M3Escalation probabilityBacked by keyword rules (see veto §3)
M4Derailment forecastConvoKit Forecaster (CRAFT + LLM backbones) — the only OSS implementation of "predict where this conversation is heading"
M5Unresolved-problem flagDelivery exception, open return, unanswered customer question
M6Channel policy window stateWhatsApp 24h window, Viber subscription
M7Time-of-day appropriatenessLocal time, channel fatigue counters

Two hard warnings on the affect layer. First, the sentiment/emotion model choice is a licensing minefield and the most-downloaded options are unusable: tabularisai/multilingual-sentiment-analysis (143,490 dl/mo, 23 languages) is CC-BY-NC-4.0; cardiffnlp/twitter-xlm-roberta-base-sentiment has 1,020,636 downloads/month and no license tag at all (no rights granted by default); pysentimiento's LICENSE reads "non-commercial use and scientific research purposes only." The commercially clean set is small: lxyuan/distilbert-...-sentiments-student (apache-2.0), nlptown/bert-base-multilingual-uncased-sentiment (MIT), SamLowe/roberta-base-go_emotions (MIT), yangheng/deberta-v3-base-absa-v1.1 (MIT).

Second, do not gate anger on the Ukrainian emotion classifier. ukr-detect/ukr-emotions-classifier (EmoBench-UA, actively maintained, updated 2026-08-05) reports per-class F1: Fear 0.81, None 0.81, Joy 0.73, Sadness 0.69, Surprise 0.60, Disgust 0.35, Anger 0.31 on 99 test examples. You cannot gate "suppress the upsell, this customer is angry" on a 0.31-F1 detector — you will miss most angry customers and falsely flag calm ones, and the failure is invisible because the model returns a confident-looking label. Use deterministic behavioural proxies instead: open ticket, recent return, SLA breach, message-length collapse, repeat-question count, complaint keywords. More reliable, and explainable when you must justify an offer decision.


Unlock threshold and graduated strength

R ≥ 62  AND  N ≥ 55 (with N1 ≥ 0.6, N4 true)  AND  M ≥ 70
AND days_since_last_offer ≥ 14
AND turns_since_last_offer ≥ 12
AND offers_in_30d < 2

Offer strength is graduated, not binary — and this is the mechanism that implements "prepared by the long-term strategy":

TierConditionPermitted
SOFT MENTIONR 62–72Name the product in passing while answering their question. No CTA, no price, no link.
CONSULTATIVER 72–85 AND N ≥ 55Explicit recommendation with stated reasoning, citing ≥1 stored verbatim utterance with its date, plus an explicit low-pressure out.
DIRECT OFFERR > 85 AND N > 75Price, bundle, link.

A high N can never promote the tier. Only R can. Need evidence raises whether to act; relationship capital raises how hard. This one rule prevents the most common failure: a hot cart event buying its way past thin trust.

Citation-or-downgrade: if the CONSULTATIVE tier cannot produce a citable verbatim utterance with a source_utterance_id, the gate automatically downgrades to SOFT MENTION. This makes the memory layer load-bearing on revenue rather than decorative, and self-limiting when the dossier is thin.

Cold-start floor: fewer than 3 sessions, or fewer than 25 total customer turns, or fewer than 4 verified slots → capped at SOFT MENTION regardless of every other score. At month 1 this suppresses most of your base. That is correct.


Decay behaviour

  • R: half-life 45 days of silence, floored at 0.4 × peak.
  • self_orientation: stored as an incrementing counter, decaying only with elapsed time and unreciprocated helpful acts. Because the Trust Equation is Trust = (C + R + I) / S, self-orientation is a divisor — one mistimed offer arithmetically divides down months of accumulated credibility and reliability. This is the product's core failure mode expressed as arithmetic rather than as a prompt instruction. Require self_orientation below threshold as a precondition on the gate.
  • N: resets to zero when the need episode is satisfied (purchase made, or explicitly abandoned).
  • M: EWMA, ~2-turn half-life.
  • Declines: one decline suspends that offer permanently; two consecutive declines suspend all offers for 60 days and apply −8 to trust_credibility.

Threshold provenance — stated honestly

These numbers rest on no empirical evidence and none can exist at your scale. Computed: at N=2,000 with a 20% holdout and CV=1.5, the minimum detectable revenue lift at 80% power is 23.5%. At N=500 with a 10% holdout it is 62.6%. You cannot validate these thresholds for 6–12 months.

They are cost-asymmetry choices. A missed offer costs one order (~$16 gross margin at $45 AOV, 35% margin). A false offer costs trust, is unmeasurable, and may be permanent — on Telegram, Viber or WhatsApp, a block is irreversible and silent.

Register each threshold as a dated hypothesis with a scheduled review. And be clear about what is actually protecting you in year one: the rate limits, not the scores. offers_in_30d < 2 and days_since_last_offer ≥ 14 are doing the real safety work while R, N and M are still guesses wearing the costume of a model.

Replace with an InterpretML EBM (MIT, 6,943 stars, monotone_constraints verified present) once ~2,000–3,000 labelled offer events exist — at 2,000 customers × 2 offers/year × 20% acceptance, that is a three-year wait. Plan accordingly. When you do, clip monotone constraints so days_silent and escalation_prob are monotonically decreasing in readiness, and keep the shape plots on the ops dashboard permanently — they are your earliest drift alarm.


3. The veto list

Vetoes are a separate boolean layer evaluated after scoring, each returning its own reason code, each backed by cheap multilingual keyword rules so a model outage cannot silently un-veto. Any one firing blocks the offer regardless of score.

#VetoRationale
1Open unresolved ticket, OR any complaint resolved <14d agoDe Matos et al. (JSR 2007, 10(1):60-77) meta-analysis: the service recovery paradox is significant for satisfaction but not for repurchase intention, WOM, or corporate image — and only when harm was low. Recovery restores feeling, not behaviour. "Fix it then pitch" has no empirical support. Crolic et al. (JM 2022, 86(1):132-148, five studies incl. telecom field data) additionally found anthropomorphism actively harms satisfaction and purchase intention for angry customers via expectancy violation.
2Escalation prob > 0.35, OR derailment forecast > 0.5The worst failure in the system is pitching to someone whose parcel is lost. A false negative here destroys more LTV than ten good recommendations create.
3Current turn negative, OR ≥2 negative turns in last 5EWMA-smoothed so one terse message does not permanently veto.
4Unanswered customer question outstandingAnswering-before-selling is the minimum bar for advisor framing.
5Order in transit, delivery exception, or return in flightThe customer's attention is on an unresolved transaction.
6Price resistance detected this sessionABSA negative on the price aspect, or explicit affordability language.
7Any opt-out token, Viber unsubscribed, Telegram my_chat_member→kicked, WhatsApp stop keywordCross-channel. WhatsApp's Business Messaging Policy binds requests made "whether inside or outside WhatsApp" — verified verbatim. One suppression ledger keyed on person_id, not per channel.
8Outside WhatsApp 24h window without marketing opt-in, OR Viber not subscribedChannel policy is a veto input, never a score contributor.
9Bereavement, illness, job loss, financial distress, OR minor/age-uncertainABSOLUTE, and suppresses the next 3 sessions. EU AI Act Art. 5(1)(b) prohibits exploiting vulnerabilities arising from "age, disability or a specific social or economic situation." Penalties under Art. 99(3): up to €35,000,000 or 7% of worldwide turnover, applicable since 2 February 2025.
10Regulated product categoryJurisdiction-specific.
11Out-of-scope detector firedSee N9. Caps N at 40 rather than merely lowering it.
12offers_in_30d ≥ 2, OR offer declined <14d agoRains (HCR 2013, 39(1):47-73, verified): reactance k=53, r=.20; anger k=28, r=.21; negative cognitions k=25, r=.17 — moderated by behaviour repetitiveness. Repeated requests for the same behaviour are the worst case.
13Model-confidence veto: <3 sessions, <25 customer turns, or <4 verified slotsReadiness is not identifiable.
14AI disclosure not yet made in this threadEU AI Act Art. 50(1), enforceable since 2 August 2026, explicitly not deferred by the Digital Omnibus (which moved only Annex III high-risk to 2027-12-02). Requires disclosure "at the latest at the time of the first interaction."
15adverse_trait_profile trueShah, Kumar, Qu & Chen (JM 2012, 76(3):78-95, five firms): 10–35% of cross-buyers are unprofitable and account for 39–88% of total customer losses, identifiable by four computable traits — limited spending, excessive revenue reversals, excessive service requests, promotion-only purchasing — and their losses increase with more cross-buying.
16Readiness computed >7 days agoStale dossier → treat readiness as 0.
17Groundedness check failed on the drafted messageHard block; fall back to a templated response quoting the tool value verbatim.

Three implementation notes that determine whether these vetoes actually work:

Vulnerability flags are write-only into the veto layer. They can veto an offer; they can never raise a readiness score; and they must be excluded as features from every model that computes R or N. This is the specific mechanism keeping you on the right side of Art. 5(1)(b), because a conversion-optimised model will learn that distressed customers convert better, and "the model learned it, we did not intend it" is not a defence — Art. 5 catches effect as well as objective.

Confidence floors may only ever block, never unblock. A low-confidence evidence slot should be excluded (conservative). A low-confidence suppressive signal must still veto — because people disclose hardship obliquely, hedged, once, in passing, which is exactly the utterance shape that yields sub-threshold extraction confidence. Two separate thresholds, with a unit test per suppressive signal asserting a 0.4-confidence extraction still vetoes.

The adverse_trait_profile gates offer generation only. Never tone, never latency, never service quality, and the classification must never surface in generated text. There is a real reputational hazard in operationalising this — a suppression list keyed on return behaviour shades easily into discriminating against customers with legitimate needs (size uncertainty, disability, gifting, or simply buying clothes online).


4. Frequency and fatigue caps

Per-person attention budget — one leaky bucket keyed on person_id across all channels, not per channel. This requires cross-channel identity resolution on day one; note Viber exposes no phone number, so it needs its own verification flow.

  • 100 points/month, refilling 25/week, max carry 100.
ActionCost
Answering an inbound question0 — always answer, never budgeted
Educational touch12
Care check-in15
Soft mention25
Consultative recommendation40
Direct offer60
Exit / pause notice0

This mechanically yields ~8 educational touches per month, OR 1 direct offer plus 3 educational touches — producing the 83% educational / 15% goodwill / 2% instantaneous-promotional split that Li, Sun & Montgomery (JMR 2011, 48(4):683-700, verified via Crossref) measured, enforced by arithmetic rather than by prompt.

That paper is the business case for the whole design: solicitation value decomposes as ~83% educational, ~15% advertising/goodwill, ~2% instantaneous promotional, and their long-horizon policy improved immediate response 56%, long-term response 149%, long-term profit 177%. Ninety-eight percent of a solicitation's value is not the sale it might close today.

Hard caps:

CapValue
Offers per 30 days2
Minimum gap between offers14 days
Minimum turns since last offer12
Offers per 90 days3
Proactive messages, ACTIVE tier4–8/month
Post-offer cooling7 days minimum
Post-decline cooling14 days
Two consecutive declines60-day freeze + trust_credibility −8
Post-complaint cooling14 days after resolution
Vulnerability signal3 sessions

Back-off is history-dependent. Güneş, Akşin, Örmeci & Özden (JSR 2010, 13(2):168-183, verified) built a Markov decision model showing failed sales attempts have a persistent negative effect on future purchase probability, and that the optimal policy must count failures since last purchase. Store failed_offers_since_last_purchase as state; decay offer probability monotonically in it; reset only on purchase or unambiguous customer-initiated buying intent. Decay harder on an explicit decline than on non-response — a "no thanks" costs more than silence.

Inverse-response cadence ladder, monotonically inverse with no win-back escape hatch:

ACTIVE (4–8/mo) → COOLING (2/mo) → DORMANT (1/quarter) → ARCHIVED (0, inbound-only)

Demote after 3 consecutive proactive messages with no reply or read within 7 days each. Promote only on an inbound message, never on a timer. Any code path that increases frequency at falling engagement is the defect.

Channel-aware economics, because the caps interact with real money:

  • Telegram — free, no session window, no templates, ~30 msg/s global and 1 msg/s per chat (verified from core.telegram.org/bots/faq: "bots are able to message their users at no cost"). Because there is no price signal to restrain you, enforce the attention budget more strictly here, not less. This is the channel most likely to burn relationships precisely because it is free.
  • WhatsApp — verified from Meta's pricing page: "All non-template messages are free... Non-template messages can only be sent within an open customer service window," and utility templates are free in-window. So the education payload is free; only the window-opener is metered. Architect the cadence as (one paid or earned opener) → (drain 3 queued educational items into the open window at zero cost), capped at 3 per window-open event and 6 per 24h. This cuts an 8-touch education month by ~80%. Marketing templates are additionally capped per-recipient across all businesses (error 131049, "Per-User Marketing Template Message Limits" — verified in Meta's error-code docs), which is invisible and uncontrollable, so never make a time-sensitive offer depend on a marketing template landing. Treat 131049 as a saturation signal (suppress marketing to that contact 30 days), not a retryable error.
  • Viberbilling_status 0–4 are free (including "Free out of session 1:1 message"), only status 5 is charged. Ukrainian resellers price transactional ~€0.010–0.016 and promotional €0.027–0.035 with a **€200/month minimum**, pre-buying ~15,000 messages — so below ~1,500 active Viber contacts the marginal cost of an educational touch is zero and rationing Viber is waste, not prudence. But Viber contractually forbids promotional content (discounts, promo codes, product recommendations) in transactional messages, so any offer must be classified and billed promotional. Its Developer Agreement also requires allowing users to "opt out of AI related features and ensure human oversight" — ship /human and an AI toggle as contract compliance.

Graceful exit: send exactly once, at the DORMANT→ARCHIVED transition, on a free channel only, with a one-tap "keep me" button. Never as a paid template — you would be paying per message to tell non-responders you are leaving, the worst-ROI send in the system, and it can trip 131049 or damage quality rating on the way out. Never to a complainer (reads as retaliation). A/B it against silent archiving before rolling out.


5. The sales methodology, operationalized

Fourteen rules. Each is enforced in code, not in a prompt — prompts leak under emotional pressure, adversarial input, and model updates.


Rule 1 — Require a customer-sourced problem statement before any considered-purchase offer. Source: Rackham, SPIN Selling (1988), ~35,000 call behavioural coding. Encode as a per-thread enum: NO_PROBLEM → PROBLEM_STATED → IMPLICATION_CONFIRMED → NEED_PAYOFF_ELICITED → OFFER_PERMITTED. Each transition requires a verbatim customer utterance stored with provenance — never an inference from purchase logs. Skip the whole sequence for low-price replenishment; require it for high-ticket or high-return-risk items. Caveat, stated honestly: SPIN's evidence base is observational, B2B, high-ticket, multi-call. At a $45 AOV the implication question ("and what happens if that keeps occurring?") reads as manipulative friction. Keep the provenance requirement; drop the interrogation ritual.

Rule 2 — Allow directive pressure; ban aggressive pressure. Enforce post-generation. Source: Zboja, Clark & Haytko (JAMS 2016, 44(6):806-821, verified). Note: a formal Erratum exists at JAMS 44(6):822-823. Two empirically distinct factors: aggressive pressure (urgency, pushing, refusing "no", hard closing) significantly damages trust in both salesperson and company; directive pressure (confident recommendation) does not and may help, because customers read it as helpfulness. Implement as a deterministic filter after the LLM. Ban: countdown/urgency framing, "last chance", superlatives without evidence, re-offering inside the back-off window, any scarcity claim not backed by a live inventory field. Require: a clear recommendation with reasons — including recommending against a purchase. Withholding a clear recommendation reads as evasive, which is its own failure.

Rule 3 — In Ukrainian and Russian, be direct. Do not calque English hedging. Source: Ogiermann (J. Politeness Research 2009, DOI 10.1515/jplr.2009.011, 382 citations). "The relationship between indirectness and politeness is interpreted differently across cultures," with direct requests central in Polish and Russian where conventional indirectness dominates English and German. Zboja's directive/aggressive boundary was calibrated on US consumers for whom directness itself signals pressure. In a high-directness pragmatic culture the same utterance sits further from "aggressive." Practical consequence: hedged, permission-asking phrasing calqued into Ukrainian reads as evasive, not respectful — the opposite of the intended trust effect. Build two template variants (direct vs hedged) and A/B them; the evidence predicts direct wins.

Rule 4 — Meter self-orientation as a divisor. Source: Maister, Green & Galford, The Trusted Advisor (2000). Trust = (Credibility + Reliability + Intimacy) / Self-Orientation. Store four independent floats. Increment self-orientation on every offer, discount mention, and urgency phrase; decay only with elapsed time and unreciprocated helpful acts. Because it divides rather than subtracts, one mistimed offer mathematically wipes months of accumulation. Caveat: the Trust Equation has no published psychometric validation — it is a consulting heuristic with enormous adoption. Treat your numeric implementation as your own invention requiring its own A/B validation.

Rule 5 — Optimize for education, not conversion. Instrument it or lose it. Source: Li, Sun & Montgomery (JMR 2011) — see §4. The primary dashboard metric must be education/goodwill events per customer, with offer conversion secondary. If you only instrument conversion, gradient descent finds the 2% policy.

Rule 6 — Gate cross-sell on attitudinal loyalty, not purchase frequency. Source: Liu-Thompkins & Tam (JM 2013, 77(5):21-36, verified): "Not All Repeat Customers Are the Same." Attitudinal loyalty facilitates cross-selling; habit inhibits it. Habit strength is derivable from transaction records alone. A high-frequency habitual buyer and a high-frequency attitudinally loyal buyer look identical in RFM and require opposite treatment. Compute habit_strength from inter-purchase regularity and same-SKU share, attitude_strength from conversational signals (unsolicited praise, advice-seeking, referrals, disclosure depth). Never interrupt a high-habit/low-attitude customer's routine reorder with a new-product proposal — protect the habit, build attitude through advice.

Rule 7 — Time offers to the customer's own cycle, not a calendar. Source: Kumar, George & Pancras (J. Retailing 2008, 84(1):15-27, verified). Inverted-U on interpurchase time: there is an optimum gap, and both too-soon and too-late reduce cross-buying. Fit per customer, per category. Note Reinartz & Kumar (JM 2003) establishes association, not causation — good customers may simply cross-buy, which inverts the intervention logic if true.

Rule 8 — Make the first upsell a replenishment reminder, not a new product. A depletion-timed reorder nudge is the lowest trust-cost action available because it is unambiguously in the customer's interest. Ship it before any cross-category recommendation logic; treat successful replenishment as evidence advancing the relationship stage. Implementation: category-level Weibull AFT in lifelines (MIT, 2,609 stars) handles right-censoring correctly — customers who have not yet repurchased are censored, not negative labels. A naive binary classifier systematically underestimates for recent customers and makes the bot go quiet on exactly the newest relationships. Hard-block durables in policy, because the model will cheerfully fit a reorder curve to a sofa and never warn you.

Rule 9 — Open every proactive message with an up-front contract and an opt-out. Never re-ask after a decline. Source: Rains (HCR 2013) + Sandler's Up-Front Contract. Purpose, expected outcome, explicit permission to say no. Because behaviour repetitiveness is reactance's strongest moderator, repeated requests for the same behaviour are the worst case. One decline suspends that offer permanently; two suspend all offers.

Rule 10 — Use foot-in-the-door; refuse door-in-the-face. Source: sequential-request meta-analyses. FITD (small genuine free favour → later larger request) is delay-insensitive and ethically usable as-is. DITF requires deliberately making an inflated first request you expect refused, works only at short inter-request delays, and has weak effects. Log free-help acts with no ask attached; require a minimum count before the offer gate opens.

Rule 11 — Require a genuine follow-up question in every exploration-stage turn — except during complaints. Source: Huang, Yeomans, Brooks, Minson & Gino (JPSP 2017, 113(3):430-452). Question-asking, especially follow-up questions, causally increases liking, mediated by perceived responsiveness — which is precisely the Intimacy term in the Trust Equation. Two caveats you must carry. First, a formal Correction was published (JPSP 2025, 128(3):669, DOI 10.1037/pspi0000491, Crossref type "correction", updated 2025-03-20). Its text: "several minor errors in how some results were reported... The audit confirmed that all the conclusions in the paper are valid. The substantive results of every hypothesis test in the paper remain unchanged." The finding stands; read the correction before quoting effect sizes. Second, disable this rule whenever complaint_state is open — Customer Effort Score logic (Dixon, Freeman & Toman, HBR July–Aug 2010) says every extra question during a support episode is added effort and therefore a loyalty cost.

Rule 12 — Make memory visible and forward-looking; it is the cross-session bridge, not conversational polish. Source: Sumida et al. (arXiv 2607.14593, accepted ICMI 2026), 24 participants × 10 daily sessions. Within-session conversational quality predicts immediate enjoyment but does not carry forward; perceived memory is relationally conditioned and shapes later enjoyment indirectly via subsequent self-disclosure. Design memory as an invitation ("last time you were choosing between the two jackets — did the navy work out?"), not a recall demonstration. Optimising single-conversation polish will not build the relationship. Also from the same study: enjoyment surges persist more reliably than crashes recover, and some crashes are forecastable from prior-session behavioural drift. Run a relational early-warning model on message length, reply latency, disclosure depth, sentiment and initiation ratio; flag >1SD negative session-to-session shifts, and on a flag suspend offers and trigger a care action. The asymmetry is itself the quantitative argument for a conservative offer policy.

Rule 13 — Separate the ranker from the generator; state any commercial weighting in the text. Source: Salvi, Cuevas & Horta Ribeiro (arXiv 2604.04263), two preregistered experiments, N=2,012, five frontier models. LLM agents nearly tripled sponsored-product selection versus search placement (61.2% vs 22.4%); most participants detected no steering; explicit "Sponsored" labels did NOT significantly reduce persuasion; instructing the model to conceal intent dropped detection accuracy below 10%. Labels are not a sufficient control. Keep recommendation ranking in an auditable, fit-driven component outside the LLM, log the rationale per recommendation, and if margin influences ranking at all, say so in the message body.

Rule 14 — Disclose AI at first contact; rebuild the lost conversion through knowledge and empathy. Source: Luo, Tong, Fang & Qu (Marketing Science 2019, 38(6):937-947, N>6,200 field experiment) + EU AI Act Art. 50. Verified Table 3: undisclosed bot 0.237 purchase rate, proficient human 0.251, disclosure-before-conversation 0.048 (−79.7%, with a 56.3% hang-up rate and call length collapsing 64.2s→10.3s), disclosure-after-conversation 0.110, disclosure-after-decision 0.232. Mechanism: disclosed bots are perceived as less knowledgeable and less empathetic. The paper's own mitigation is late disclosure — which Art. 50(1) makes legally unavailable by requiring disclosure "at the latest at the time of the first interaction." So disclose once, clearly, then stop belabouring it, and engineer back the loss through the two mediators: demonstrable knowledge (cited specs, honest trade-offs, explicit "I don't know") and demonstrable empathy (visible memory, acknowledgement before advice). External-validity caveat: 2019, pre-LLM, outbound voice calls, Chinese financial services. The 79.7% is almost certainly a large overestimate today — the paper itself finds prior AI experience attenuates the effect — but the direction is robust and the legal constraint is not.


Two rules deliberately excluded

Challenger's "Take Control." Its evidence is manager-rated self-report with no peer-reviewed replication; "complexity," the moderator the headline finding depends on, is never operationalised; actual sales-vs-budget data was collected but unused. More importantly, "take control of the conversation" maps directly onto aggressive pressure, which Zboja et al. show damages trust in both the seller and the company. Take Teach and Tailor; drop Take Control.

Semantic response caching. The standard cost-saving recommendation, and precisely wrong here: two customers asking the same question must get different answers because their relationship state differs, and replaying a cached reply to a returning customer is the exact tell that destroys the "she remembers me" illusion. Cache KV prefixes (deterministic, correctness-preserving); never cache responses on the conversational path.


6. Worked dialogue examples

Register note: examples are shown in English for readability. In production these are Ukrainian by default — Article 30 of Ukraine's 2019 state language law assigns Ukrainian exclusively to consumer service, with another language permitted only "at the customer's request," enforced by a Commissioner with a 10-day complaint window and fines for business entities. Auto-detecting Russian from a first message and mirroring it is a compliance failure, not a UX preference. Open in formal Ви always; move to ти only on explicit customer signal, and prefer collectivist ми/у нас framing where the sentence allows — which sidesteps the register decision entirely and matches post-2022 Ukrainian commercial discourse (Kochkina, Katsiuba & Kanishchenko 2025, coding 90 Ukrainian commercials 2009–2025, identify a "mobilization code" of collectivist pronouns and note brands operating where "direct selling is constrained").


(a) Support interaction where the bot correctly does NOT sell

Channel: Viber · Session 4 · Day 38 of relationship

STATE ON ENTRY
  stage: KNOWN (S2)
  R: 68.4   N: 12.1   M: —
  trust: C 71 / R 66 / I 44 / S 18
  dossier_depth: 6 verified slots
  complaint_state: none
  offers_in_30d: 0    last_offer_at: null
  attention_points: 88/100

Customer (18:42): the boots you helped me pick — the left one's started letting water in at the seam. i've only had them six weeks

── M-LAYER, this turn ───────────────────────────────
  M1 valence: −0.71
  M2 emotion: disappointment 0.68 ↑, anger 0.19
  M3 escalation prob: 0.41  ← EXCEEDS 0.35
  M5 unresolved problem: TRUE (product defect)

  → complaint_state = OPEN (opened_at 18:42)
  → VETO #1 armed (open complaint)
  → VETO #2 armed (escalation 0.41 > 0.35)
  → VETO #3 armed (turn valence negative)
  → Rule 11 question requirement DISABLED
     (CES: questions during a complaint are added effort)
  → M = 22  (gate would fail on M alone)
─────────────────────────────────────────────────────

Bot (18:42): That shouldn't happen at six weeks, and I'm sorry — that's a manufacturing issue, not wear. I've opened a return under warranty, no cost to you. Two options: identical replacement, or full refund. Which would you prefer?

I'll send the pickup label within the hour either way.

── POST-GENERATION FILTER ───────────────────────────
  aggressive-pressure scan: clean
  directive requirement (Rule 2): SATISFIED
     ("that's a manufacturing issue" = confident position)
  Rule 11 follow-up question: SUPPRESSED (complaint open)
  ✓ no product mention   ✓ no upsell   ✓ no "while you're here"
  self_orientation: unchanged (18 → 18, no offer made)
─────────────────────────────────────────────────────

Customer (18:51): replacement please. and thanks for not making me argue about it

Bot (18:52): Done — replacement's reserved in your size, ships when the return scans. And for what it's worth: if the same seam goes on the replacement, tell me. That would be a pattern, not bad luck, and I'd want to steer you to a different construction rather than send you a third pair.

── STATE CHANGES ────────────────────────────────────
  trust_reliability:   66 → 71  (promise made + kept)
  trust_credibility:   71 → 74  (correct defect diagnosis)
  trust_intimacy:      44 → 47  (unprompted gratitude logged)
  self_orientation:    18 → 18  (unchanged — nothing asked for)
  R: 68.4 → 72.1

  complaint_state: OPEN → PENDING_RESOLUTION
  do_not_offer_until: 2026-10-13
     (= resolution date + 14d cooling, VETO #1)

  advice_acceptance_event: LOGGED
     ("replacement please" following a bot recommendation)
  → S3 TRUSTED exit condition partially met
─────────────────────────────────────────────────────

Why this is the hard case, not the easy one. R had already cleared 62 and a naive scorer would see high trust, high engagement, an active session — ideal conditions. The vetoes fired on M and on complaint state, not on R, which is exactly why R, N and M must be separate. Note also what the bot gave away: a commitment to recommend away from a category if the defect recurs. That is Rule 2's directive requirement doing double duty as a credibility deposit — and it is the kind of statement a conversion-optimised model will never produce.


(b) Months later — the bot correctly DOES propose something

Channel: Telegram · Session 11 · Day 147 of relationship

STATE ON ENTRY
  stage: TRUSTED (S3) since day 41
  R: 79.3   N: 31.0   M: —
  trust: C 81 / R 78 / I 62 / S 11
  dossier_depth: 14 verified slots
  habit_strength: 0.31    attitude_strength: 0.74
     → LOW habit / HIGH attitude = cross-sell RECEPTIVE (Rule 6)
  adverse_trait_profile: false
  complaint_state: resolved day 42, cooled out day 56
  offers_in_30d: 0
  last_offer_at: day 88 (SOFT MENTION, declined-as-deferred)
     → 59 days elapsed ≥ 14d gap  ✓
  offers_declined_count: 1 (not consecutive-2)
  attention_points: 74/100
  vulnerability_flags: []
  free_help_acts_since_last_offer: 4   (Rule 10 FITD threshold: 3)

Customer (09:14): morning — random question. going to the carpathians in three weeks, first proper winter trip. the boots you sorted me out with, are they going to be enough or am i kidding myself

── SLOT EXTRACTION (GLiNER2, need schema) ───────────
  product_category: winter footwear / layering   [0.91]
  constraint:       mountain, winter, 3 weeks     [0.88]
  occasion:         Carpathians trip              [0.94]
  deadline:         ~2026-10-06                   [0.89]
  recipient:        self (no mismatch signals)    [0.96]
  budget:           — MISSING —

  N1 slot coverage: 5/7 = 0.714  ≥ 0.6  ✓ HARD GATE PASS
  N4 price band:    revealed [1,400–4,200 UAH]
                    candidate 2,890 UAH  ✓ HARD GATE PASS

  N2 purchase intent:   0.77
  N3 ABSA dealbreakers: waterproofing +0.4, warmth +0.2,
                        price −0.1 (non-negative required: PASS)
  N5 cycle position:    n/a (complement, not replenishment)
  N7 browse-to-chat:    false
  N8 cart event:        false

  N = min(0.714, 1.0) × Σ(weighted) = 68.2
─────────────────────────────────────────────────────

── M-LAYER, this turn ───────────────────────────────
  M1 valence: +0.34
  M2 emotion: lean_in 0.71, confusion 0.44, distress 0.02
  M3 escalation: 0.04     M4 derailment: 0.09
  M5 unresolved: none     M6 window: Telegram (no window)
  M7 time-of-day: 09:14 local — appropriate
  M = 81
─────────────────────────────────────────────────────

── SPIN STATE MACHINE ───────────────────────────────
  PROBLEM_STATED       ✓  "am i kidding myself"
                          utterance_id 8f2c… (this turn)
  IMPLICATION_CONFIRMED ✓  day 112: "last time my feet got
                          wet on a hike i had to turn back"
                          utterance_id 4a91…
  → OFFER_PERMITTED
─────────────────────────────────────────────────────

── GATE EVALUATION ──────────────────────────────────
  VETOES 1–17: all clear
    ✓ no complaint (cooled out day 56)
    ✓ escalation 0.04    ✓ valence positive
    ✓ no unanswered question    ✓ no order in flight
    ✓ no price resistance this session
    ✓ no opt-out    ✓ Telegram: no window constraint
    ✓ vulnerability_flags empty
    ✓ not regulated    ✓ OOS not fired
    ✓ offers_in_30d 0 < 2;  59d ≥ 14d;  last decline 59d ago
    ✓ sessions 11 ≥ 3; turns 89 ≥ 25; slots 14 ≥ 4
    ✓ AI disclosed (thread opened with disclosure)
    ✓ adverse_trait_profile false
    ✓ readiness computed 14h ago < 7d
    ✓ groundedness: PASS (specs cited from catalogue row)

  CONJUNCTIVE:  R 79.3 ≥ 62  ✓
                N 68.2 ≥ 55  ✓  (N1 ✓, N4 ✓)
                M 81   ≥ 70  ✓

  TIER:  R 79.3 ∈ [72, 85)  AND  N 68.2 ≥ 55
         → CONSULTATIVE RECOMMENDATION
         (NOT direct offer: R < 85, N < 75.
          N 68.2 cannot promote the tier — only R can.)

  CITATION CHECK: utterance 4a91… (day 112) available
         → tier HOLDS
         (absent a citation → auto-downgrade to SOFT MENTION)

  GATE: ALLOW · tier=CONSULTATIVE · gate_version=a3f91c2
─────────────────────────────────────────────────────

Bot (09:15): Not kidding yourself — the boots are genuinely fine for that. They're rated to −15 and the Carpathians in early October sit well above that. Keep them.

The gap isn't the boots, it's what's between them and you. You mentioned back in July that your feet got wet on a hike and you had to turn back — that's usually a sock-and-sweat problem rather than a boot problem, and it's the thing that actually ends winter walks. A merino liner plus a proper mountaineering sock runs about 890 UAH for the pair and solves it.

If you'd rather just take the boots and see how you get on, that's a completely reasonable call — three weeks is enough time to order after a test walk.

── POST-GENERATION FILTER ───────────────────────────
  urgency framing:        none      ✓
  scarcity claim:         none      ✓
  superlative w/o evidence: none    ✓
  directive requirement:  SATISFIED
     ("Keep them" — recommends AGAINST an upsell first)
  explicit out:           SATISFIED ("completely reasonable call")
  citation present:       utterance 4a91…, dated  ✓
  commercial weighting:   none (fit-ranked, logged)  ✓
─────────────────────────────────────────────────────

── STATE CHANGES ────────────────────────────────────
  self_orientation:  11 → 15  (+4, offer made — DIVISOR)
  Trust = (81+78+62)/15 = 14.7   (was (81+78+62)/11 = 20.1)
     → one offer cost 27% of composite trust.
       This is the arithmetic that enforces restraint.

  last_offer_at:        day 147
  offers_in_30d:        0 → 1
  do_not_offer_until:   day 161  (14d hard gap)
  stage:                TRUSTED → COOLING (7d minimum)
  attention_points:     74 → 34  (consultative = 40)
  N:                    68.2 → pending episode outcome
─────────────────────────────────────────────────────

What did the work here. The offer is 12% of the message. The first move is recommending against a purchase ("Keep them"), which is Rule 2's directive requirement and simultaneously the strongest credibility signal available. The citation is dated and verbatim — Rule 12's visible-memory mechanism — and without it the gate would have silently downgraded to a mention with no price. And note the FITD precondition: four unreciprocated free-help acts preceded this, which is what earned the ask.


(c) Failure mode, and how the gate prevents it

Same customer. Rewind to day 41 — six days before the complaint cooldown expires.

STATE ON ENTRY
  stage: KNOWN (S2)
  R: 72.1   N: 71.4   M: 76
  complaint_state: PENDING_RESOLUTION (opened day 38)
  do_not_offer_until: day 56
  offers_in_30d: 0
  attention_points: 88/100

Customer (11:03): replacement arrived, fits perfectly. honestly better than the originals, the sole feels stiffer

── WHAT A NAIVE SYSTEM COMPUTES ─────────────────────
  Additive score:  0.4·R + 0.4·N + 0.2·M
                 = 0.4(72.1) + 0.4(71.4) + 0.2(76)
                 = 72.6   ← clears any single threshold

  M1 valence: +0.79  (highest of the relationship)
  M2 lean_in: 0.82
  Recent CSAT: positive
  Gratitude expressed: twice in 72 hours

  An LLM asked "is this customer ready?" mid-conversation
  returns YES with high confidence. Every surface signal
  agrees. The service recovery paradox FEELS true here.
─────────────────────────────────────────────────────

What a naive system would send:

"So glad they worked out! Since you're clearly getting on with the winter range — we've just got the matching insulated gaiters in, and I could do you 15% off as an apology for the hassle. Want me to hold a pair?"

Plausible. Warm. Reciprocal-feeling. Commercially disastrous.

── WHAT THE GATE ACTUALLY DOES ──────────────────────
  Vetoes evaluated BEFORE scoring is consulted:

  VETO #1  complaint_state = PENDING_RESOLUTION
           AND day 41 < do_not_offer_until (day 56)
           → BLOCK  reason_code = COMPLAINT_COOLING

  Conjunctive thresholds never evaluated.
  Additive score of 72.6 is IRRELEVANT — there is no
  path by which a high score overrides a veto.

  Additional blocks had #1 not fired:
  VETO #12  discount-as-recovery → would breach the
            Viber transactional/promotional classification
            AND trains complaint-for-discount behaviour

  GATE: WITHHOLD · reason=COMPLAINT_COOLING
        gate_version=a3f91c2
  EVENT LOGGED: offer_withheld_when_available
        suppressed_intent: {sku: gaiters-ins-02,
                            tier: CONSULTATIVE,
                            R: 72.1, N: 71.4, M: 76}
─────────────────────────────────────────────────────

What the bot actually sends:

Bot (11:04): Good — and the stiffer sole is the tell that you got a properly made pair this time. That's how they should have felt at six weeks.

Give them a couple of long walks before the trip so the flex point sets where your foot actually bends. If anything feels off after that, say so early rather than pushing through it.

── STATE CHANGES ────────────────────────────────────
  trust_credibility:  74 → 77
  self_orientation:   18 → 18  (unchanged — no ask)
  R: 72.1 → 74.6
  offers_in_30d:      0 (unchanged)
  attention_points:   88 (unchanged — advice costs 0
                          when the customer initiated)
─────────────────────────────────────────────────────

Three things this case demonstrates.

The veto layer must be evaluated separately from and after scoring. Every surface signal — valence, emotion, CSAT, gratitude — pointed at "offer now." Only relationship state said no, and state is the thing an in-conversation model cannot see.

The recovery paradox is a trap, and the evidence says so. De Matos et al. found the paradox significant for satisfaction but not for repurchase intention, WOM, or corporate image. The customer genuinely feels good; that feeling buys you no behavioural credit. Spending it on an offer converts a satisfied customer into a suspicious one — and "discount as apology" additionally trains complaint-for-discount, manufacturing exactly the promotion-only adverse-trait profile that Shah et al. found generates 39–88% of total customer losses.

Logging the withhold is what makes the policy improvable. offer_withheld_when_available with suppressed_intent is the only record from which you can later price the gate's conservatism. Without it, restraint is invisible to every metric you will ever build, and at the month-9 review you will have no way to distinguish "the gate is correctly protecting relationships" from "the gate is strangling revenue." That single logged event, at day 41, is what lets you answer that question at day 400.