The Offer Gate

Part one

Verdict and architecture

What to build, what the parts really cost, and the honest arithmetic on whether the economics clear at your size.

The verdict

Build a deterministic offer gate over one Postgres, ship support deflection first, and log six irreversible fields from day one. Everything else defers.

The uncomfortable truth arrives in three layers, and each one is worse than the last.

First: there is no prior art to adopt. Every agentic benchmark and every memory engine verified here is single-session. GitHub search for "relationship stage trust agent LLM customer" and "customer lifetime value agent LLM" returns zero repositories. No open-source project models relationship_stage, trust_score, offer_fatigue or upsell_readiness. The thing you are buying this report to find does not exist. The good news is that it is small — roughly 300 lines of pure Python plus a state machine — and that it is therefore your actual product rather than your integration risk.

Second: the parts you can buy are worse than their star counts suggest. Graphiti's last permissively-licensed backend (Kuzu) was archived 2025-10-10 and is marked deprecated in Graphiti's own pyproject.toml, leaving GPLv3 Neo4j, SSPL FalkorDB, or proprietary Neptune. Letta carries 24,747 stars and no source code — the repo is now a README pointing at a TypeScript rewrite. Parlant has shipped nothing since v3.3.2 on 2026-04-28 and has no Postgres store. Zep's Community Edition sits in a legacy/ folder marked unsupported. text-generation-inference is archived. The 2024 playbook is largely dead code.

Third, and this is the one that should change your plan: the economics probably do not clear at your current size. Build cost ~$24,000; run cost $0.14/customer/month at 1,000 and $0.03 at 50,000. Amortised over three years, breakeven is ~2,674 customers at an optimistic 15% LTV lift, ~7,910 at 8%, and never at 3% — because per-customer run cost ($1.53/yr) exceeds per-customer margin gain ($1.04/yr). Meanwhile support deflection returns $1,800–7,200/yr at 500–2,000 customers, arrives in week two rather than year two, and generates exactly the Ukrainian/Russian conversation corpus the relationship layer later needs.

So: below ~3,000 customers, build the deflection bot carrying the full decision schema, and treat the relationship layer as an option you have already paid for. Above that, the architecture below is the one to build. Either way, the day-one logging is the same, costs about one engineer-week, and cannot be bought back later at any price.


Base: LEDGER (measurement-first sequencing, day-one schema) with WITHHOLD's gate design grafted in as plain Python and Dossier's write policy and citation rule. Two of three adversarial reviews picked LEDGER; the third picked WITHHOLD for its capability-boundary gating, which is preserved below without its two framework dependencies.

The governing constraint is not money. At 50,000 customers the entire technical bill is ~$1,597/month against $7,000–14,000/month for two engineers — infrastructure is 4–9× cheaper than the people operating it. Every component below is chosen to minimise stateful services operated, not dollars.

Store — everything, one database

  • PostgreSQL 18 + pgvectorhttps://github.com/pgvector/pgvector — PostgreSQL License (permissive). 23,020 stars, commits 2026-09-08.
  • Holds typed relationship state, bitemporal facts, embeddings, job rows, decision log. Makes GDPR erasure a transactional cascade and the gate a SELECT rather than a cross-service call that can be stale at the worst moment.
  • Runner-up: pgvectorscale (PostgreSQL License, https://github.com/timescale/pgvectorscale) added later. Choose it if filtered per-customer vector recall degrades — pgvector applies filters after the ANN scan.
  • Explicitly rejected: Graphiti, Feast, Novu, Hatchet (4 stateful services incl. two brokers), Temporal. If you ever genuinely need traversal, apache/age (Apache-2.0) puts openCypher inside the Postgres you already run.

Offer gate — the product

  • Plain Python: one decide(state, live_vetoes, candidate) -> Offer | Withheld function. ~300 lines, gate_version pinned to a git SHA, one unit test per never-rule, table-driven.
  • Runner-up: Open Policy Agenthttps://github.com/open-policy-agent/opa — Apache-2.0, CNCF graduated. Choose it if a non-engineer must change thresholds without a deploy, or policy changes outpace releases. Neither is true for two engineers, and Rego's unordered-rule semantics is a 2–4 week tax to govern one decision in one process.
  • Second runner-up: cedar-policy/cedar (Apache-2.0) — choose it if you want policy-as-data but bounce off Rego.

Conversational rails — what it may say

  • Own guidelines table: inspectable condition (SQL predicate) → constrained/canned response rows, selected per turn, with guideline id + condition + rationale logged. ~150 lines.
  • Runner-up: Parlanthttps://github.com/emcie-co/parlant — Apache-2.0, 18,287 stars. Strict-mode canned responses are genuinely the strongest anti-eagerness mechanism in OSS. Choose it if you accept a second system of record — its stores are JSON file, MongoDB or Snowflake, no Postgres — and a single vendor whose release cadence stopped in April 2026.
  • Also viable: NVIDIA NeMo Guardrailshttps://github.com/NVIDIA-NeMo/Guardrails — Apache-2.0, v0.24.0 (2026-08-26). Choose it if you want maintained predefined-response substitution plus retrieval rails over untrusted product-review text.

Durable wake-ups

  • DBOS Transacthttps://github.com/dbos-inc/dbos-transact-py — MIT, v2.31.1. A library, not a server: dependencies are psycopg, sqlalchemy, websockets, click, pyyaml. Zero new infrastructure.
  • Runner-up: pgmq (PostgreSQL License, https://github.com/pgmq/pgmq) or Graphile Worker (MIT). Choose these if you want zero new libraries — a far-future run_at row satisfies the whole requirement.

LLM — language only, never policy

  • google/gemma-4-26B-A4B-it via OpenRouter — https://huggingface.co/google/gemma-4-26B-A4B-it — Apache-2.0. $0.09/M in, $0.30/M out, $0.05/M cache-read. Only model with independent Ukrainian evidence: Global-MMLU-uk 0.866, IFEval-uk 0.819 (instruction-level 0.868). The product is a model obeying a policy in Ukrainian.
  • Runner-up: mistralai/mistral-small-2603 — Apache-2.0, $0.15/$0.60 but $0.015/M cache-read (90% off vs Gemma's 44%), with uk and ru in its declared language tags. Choose it if your measured prefix ratio is high — 17,600 of ~41,700 input tokens per conversation are a stable prefix, so the pricier headline can be cheaper in production. It has no entry on the lang-uk leaderboard; bench both on 200 real conversations.
  • Rejected: Llama 4 (non-OSI, and its official language list contains neither ru nor uk). Self-hosting vLLM: break-even is 165k–456k conversations/month; at 50,000 customers you are at ~100k.

Channels

  • Telegram — aiogramhttps://github.com/aiogram/aiogram — MIT, v3.31.0, Bot API 10.3. Free, no session window, no templates. The only channel where you can A/B withhold-vs-offer without cost confounding. Launch here. No read receipts exist — mark latency features MISSING, never impute neutral. Runner-up: python-telegram-bot (29,465 stars) — choose it if you need JobQueue, accepting GPLv3/LGPLv3 and Bot API 10.0 lag.
  • WhatsApp — pywa on Cloud API directhttps://github.com/david-lev/pywa — MIT. Meta bills nothing for non-template messages inside the 24h window, and utility templates are free in-window. Runner-up: 360dialog at ~€49/month flat. Never a per-message BSP: Twilio charges $0.005 inbound and outbound on messages Meta gives away free — $800/month at 50k customers.
  • Viber — build it. Read https://github.com/botpress/botpress/tree/master/integrations/viber (MIT) as reference; port signature logic from Viber's own dead SDK. Nothing maintained exists — Chatwoot has no viber.rb, Libredesk has only email and livechat. Runner-up: KeyCRM ($19/mo, Ukrainian, all three channels + OpenAPI) — choose it if its API can send into an existing chat and webhook on inbound; verify that before committing 2.5 weeks.

Extraction and memory

  • GLiNER2https://github.com/fastino-ai/GLiNER2 — Apache-2.0. Schema-driven zero-shot slot filling; commerce slots are a JSON config you change weekly. Runner-up: google/langextract (Apache-2.0) — choose it if specs live in messy supplier PDFs; it carries character-offset source grounding at per-document API cost.
  • Per-slot write policy, hand-built: every fact row carries update_rule enum(explicit_only, never_overwrite_append, prefer_most_recent, keep_oldest) and expiry_class enum(permanent, seasonal, decaying). ~200 lines. Design stolen from Memobase's update_description; do not adopt Memobase (one commit in all of 2026, zero releases ever).

Guards

  • Groundedness: LettuceDetecthttps://github.com/KRLabsOrg/LettuceDetect — MIT — fine-tuned on jhu-clsp/mmBERT-base (MIT, covers ukr and rus). There is no alternative: Granite Guardian 4.1 states verbatim "only trained and tested on English data", and even the paid HHEM-2.3 covers Russian but not Ukrainian. Ship on their generative fallback first.
  • Toxicity: textdetox/xlmr-large-toxicity-classifier-v2 — uk F1 0.96, ru 0.9525 — its two best languages. ⚠️ openrail++, use-restricted, not OSI — get legal sign-off. Runner-up: Qwen3Guard (Apache-2.0 weights) — choose it if openrail++ fails review, but its repo has no LICENSE file and hasn't been pushed since 2025-10-21.
  • PII: Presidiohttps://github.com/data-privacy-stack/presidio — MIT (note: repo moved from microsoft/) + spaCy uk_core_news_lg / ru_core_news_lg (MIT, NER F1 0.872 / 0.944). Phone numbers are one line (supported_regions=('UA','RU','RO','MD','PL')); only national-ID regexes need writing.

Eval and observability

  • Month 1: the offer_decision table is the observability layer. SQL your team already knows.
  • promptfoohttps://github.com/promptfoo/promptfoo — MIT — per-PR regression gate with a custom premature-offer plugin (Crescendo, Mischievous User). Report pass^4 ≥ 0.9 on offer-gate scenarios, not mean — 70% correct withholding is one pushy conversation in three, and a block is permanent.
  • DeepEvalhttps://github.com/confident-ai/deepeval — Apache-2.0 — for ConversationSimulator, specifically its stopping_controller: the hook where the simulated customer gets annoyed and leaves. Without it, every simulation monotonically rewards talking more.
  • DarkBench taxonomyhttps://github.com/apartresearch/DarkBench — MIT (repo abandoned; fork the six categories). Sneaking and user retention as a per-conversation hard veto — the only externally-grounded definition of the harm.
  • Month 4+: Opikhttps://github.com/comet-ml/opik — Apache-2.0, no ee/ carve-out. Runner-up: Langfusechoose it if you need its Sessions primitive, accepting ClickHouse ownership and that data retention policies and server-side masking sit behind the paywall.

Timing and measurement

  • lifelineshttps://github.com/CamDavidsonPilon/lifelines — MIT — category-level Weibull AFT. Handles right-censoring, which is what stops the bot pitching too early.
  • pymc-marketinghttps://github.com/pymc-labs/pymc-marketing — Apache-2.0 — BG/NBD at ~5,000 customers. Until then run it monthly as a prior-vs-posterior diagnostic; the month the posterior separates is your go/no-go. (lifetimes is archived.)
  • Holdout: sha256(customer_id + salt), 25–30%. Thirty lines. Runner-up: Flagsmith (BSD-3-Clause) for a UI; GrowthBook (MIT + three enterprise dirs) — choose it if you want CUPED with predicted LTV as covariate, at ~10,000 customers.
  • Deferred to month 12+, only above 10,000 customers: CausalML/EconML (uplift, sleeping dogs), InterpretML EBM, conformal abstention, bandits, OPE. Their data floors — ~7,064 customers for an ATE, ~28,256 for two-subgroup heterogeneity, 2,000–3,000 offer events (a three-year wait) — put them out of reach now. Vendor zr-obp's estimator math (Apache-2.0) when the time comes; the library is dead since 2022.

Request path (inbound)

 Telegram / Viber / WhatsApp
          │
          ▼
 ┌──────────────────┐   normalise to one envelope
 │ Channel adapter  │   (customer_id, channel, text, ts)
 └────────┬─────────┘
          ▼
 ┌──────────────────┐   Presidio + spaCy uk/ru → pii_scrubbed_text
 │ PII scrub        │   raw_text retained; embeddings NEVER see raw PII
 └────────┬─────────┘
          ▼
 ┌──────────────────┐   channel_identities → stable internal UUID
 │ Identity resolve │   (suppress-only linkage; never authorises reach)
 └────────┬─────────┘
          ▼
 ┌──────────────────┐   append utterance row (provenance anchor)
 │ Utterance store  │
 └────────┬─────────┘
          ▼
 ┌──────────────────┐   <50ms: valence, emotion EWMA, escalation,
 │ Signal panel (M) │   toxicity, OOS  →  shared encoder, one pass
 └────────┬─────────┘
          ▼
 ╔══════════════════════════════════════════════════════╗
 ║  ONE TRANSACTION                                     ║
 ║   read STALE: R, N, readiness_computed_at            ║
 ║   read LIVE : complaint_open, return_in_flight,      ║
 ║               consent, window_state, opt_out,        ║
 ║               suppress_offers_until, stock, budget   ║
 ║                                                      ║
 ║        ┌─────────────────────────────────┐           ║
 ║        │ 1. HARD VETOES (boolean layer)  │──────┐    ║
 ║        │    keyword-backed; fail-CLOSED  │      │    ║
 ║        └───────────┬─────────────────────┘      │    ║
 ║                    ▼ none fired                 │    ║
 ║        ┌─────────────────────────────────┐      │    ║
 ║        │ 2. HOLDOUT bucket (25–30%)      │──────┤    ║
 ║        └───────────┬─────────────────────┘      │    ║
 ║                    ▼ treatment                  │    ║
 ║        ┌─────────────────────────────────┐      │    ║
 ║        │ 3. CONJUNCTIVE thresholds       │──────┤    ║
 ║        │    R≥62 ∧ N≥55 ∧ M≥70 ∧ caps    │      │    ║
 ║        └───────────┬─────────────────────┘      │    ║
 ║                    ▼                            │    ║
 ║        ┌─────────────────────────────────┐      │    ║
 ║        │ 4. TIER: only R promotes        │      │    ║
 ║        │    soft / consultative / direct │      │    ║
 ║        │    no citable utterance         │      │    ║
 ║        │      → auto-downgrade to soft   │      │    ║
 ║        └───────────┬─────────────────────┘      │    ║
 ╚════════════════════╪════════════════════════════╪════╝
                      ▼ ALLOW                      ▼ WITHHOLD
          ┌────────────────────┐        ┌────────────────────────┐
          │ Retrieve candidates│        │ Advice-only generation │
          │ (catalogue tools)  │        │ (no offer tool bound)  │
          └─────────┬──────────┘        └───────────┬────────────┘
                    └──────────┬────────────────────┘
                               ▼
                    ┌──────────────────────┐
                    │ LLM generation       │
                    └──────────┬───────────┘
                               ▼
        ┌──────────────────────────────────────────┐
        │ POST-GENERATION FILTERS (deterministic)  │
        │  • groundedness vs catalogue row → BLOCK │
        │  • toxicity (uk/ru)                      │
        │  • pressure filter: no countdown, no     │
        │    "last chance", no unbacked scarcity   │
        │  • REQUIRE a reasoned recommendation     │
        └──────────────────┬───────────────────────┘
                           ▼
                        SEND ──────► log decision_event
                                     (incl. WITHHELD, with
                                      suppressed_intent + margin)

Proactive path (outbound)

 ┌────────────────────┐   nightly, per active customer
 │ Consolidation job  │   extract slots → apply update_rule →
 │ (DBOS scheduled)   │   close contradicted rows → regenerate
 │                    │   dossier note → recompute R, N, stage
 └─────────┬──────────┘   ⚠ WRITES readiness — DB ROLE enforced
           │              ⚠ ALERT on staleness: >7d ⇒ readiness=0
           ▼                (a half-failed batch silently mutes
 ┌────────────────────┐      the bot and looks like normal
 │ task / scheduled_  │      restraint on every dashboard)
 │ touch rows         │
 └─────────┬──────────┘
           ▼
 ┌────────────────────┐   payload = {customer_id, reason}
 │ DBOS durable timer │   ⚠ NEVER serialise a decision
 └─────────┬──────────┘
           ▼  fires (days or weeks later)
 ┌─────────────────────────────────────────────┐
 │ RE-EVALUATE AT FIRE TIME                    │
 │   re-read state → run the FULL gate above   │
 └─────────┬───────────────────────────────────┘
           ▼ allow
 ┌─────────────────────────────────────────────┐
 │ CHANNEL POLICY CHECK                        │
 │  WhatsApp: window open? → free-form         │
 │            closed?     → marketing template │
 │                          (needs consent;    │
 │                           may hit 131049)   │
 │  Viber:    subscribed? promotional class?   │
 │  Telegram: free, no window                  │
 └─────────┬───────────────────────────────────┘
           ▼
 ┌─────────────────────────────────────────────┐
 │ ATTENTION BUDGET (leaky bucket, per PERSON) │
 │  100 pts/mo, +25/wk. inbound answer = 0     │
 │  education 12 · care 15 · soft 25 · offer 60│
 │  HARD CAP: 4 proactive touches / month      │
 └─────────┬───────────────────────────────────┘
           ▼
 ┌─────────────────────────────────────────────┐
 │ WINDOW-OPENER PATTERN                       │
 │  one paid/earned opener → drain up to 3     │
 │  queued education items into the OPEN       │
 │  window at zero marginal cost (6 / 24h cap) │
 │  Legitimacy test: would we still send this  │
 │  if in-window messages cost the SAME?       │
 └─────────┬───────────────────────────────────┘
           ▼
        SEND ──────► log decision_event + channel_cost_class
                     (and on 131049 → suppress marketing 30d)

Non-negotiable invariants

  1. Readiness is computed between conversations; vetoes are read live. Enforce with a separate Postgres DB role, not a comment. "Recompute readiness each turn" is the seductive change that converts this into a pushy bot inside one conversation.
  2. Only R may promote the offer tier. Need evidence raises whether, never how hard.
  3. Confidence floors may only BLOCK, never UNBLOCK. Suppressive signals (distress, complaint, opt-out intent) veto at a lower threshold than evidence signals need to pass. A 0.4-confidence bereavement extraction must still veto. Unit-test this per signal.
  4. Every sensor fails veto-biased. Missing slot blocks; uncertain classifier blocks; OOS caps N; low confidence caps the tier.
  5. Prefer a deterministic proxy over a model on the safety path. Open ticket beats an anger classifier — the only Ukrainian emotion model scores anger at F1 0.31.
  6. Log offer_withheld_when_available as a first-class counted event. Near zero means the gate is decorative; overwhelming majority means it is strangling revenue. It is the one number that tells you which failure you have.
  7. Log action_propensity from day one (one column, unrecoverable later). Keep exploration at ε=0.02–0.05 over safe actions only — never randomise into an offer past a veto.
  8. Cross-channel identity suppresses, never authorises. A stop on any channel stops all channels; opt-in stays per-channel with its own evidence.

Metric corrections the reviews forced

  • Primary outcome is binary 90-day repeat-purchase rate, not revenue. At N=2,000 with a 30% holdout, revenue (CV=1.5) detects only a 23.5% lift; the binary metric detects ~6pp. Revenue is secondary.
  • Run a third, deliberately looser arm (~20%) — confidence veto at 2 sessions, consultative at R≥55, one extra offer per 30d, same hard vetoes and same block-rate stop. Treatment-vs-holdout can only answer "does it help"; policy-vs-policy is the only design that prices the gate's conservatism.
  • Shadow cost-of-withholding ledger: on every withhold, persist candidate SKU + margin; at 28/90d classify bought-anyway / bought-substitute / bought-nothing. Sum the third. No randomisation needed, reads out in weeks.
  • Block rate is the primary leading indicator, monitored with confidence sequences against a rule written before launch: warn 1.0%, hard stop proactive sends at 2.0%.

Compliance corrections (all three proposals got the article wrong)

  • GDPR Art. 21(2)/21(4), not Art. 22, is what binds. The right to object to direct marketing (including related profiling) is absolute and must be presented explicitly and separately at first communication. A product recommendation almost certainly does not produce "legal or similarly significant effects", so the Art. 22 framing is a misapplication.
  • AI Act Art. 50(3): deployers of emotion recognition systems must inform exposed persons. Your M-panel triggers this. Add it to /about alongside the Art. 50(1) AI disclosure.
  • Minimise vulnerability_flags. Inferred health status is GDPR Art. 9 special-category data and prohibited-category under Ukraine's Law 2297-VI. Store only an opaque suppress_offers_until + non-semantic suppression_id; keep the categorised reason in a short-TTL store, never joined to the dossier, never an input to any score.
  • Ukraine's Law on Electronic Commerce requires commercial messages be clearly identified as commercial. This inverts everyone's risk model: SOFT MENTION — a product named in passing with no CTA, no price, no link — is commercial communication deliberately shaped not to read as commercial. Either give it a persistent lightweight commercial marker, or abolish the tier and start at consultative.
  • Ukrainian is the mandatory default for consumer service (state language law Art. 30); another language only "at the customer's request", logged as a timestamped event. Auto-detect-and-mirror is a compliance failure.
  • Add a jurisdiction column at first contact and a retention schedule. Consent wording shown at capture cannot be backfilled. Many Ukrainian-language customers are physically resident in the EU.

What you must build yourself

Nothing below exists as an adoptable OSS component. Estimates assume two competent generalist engineers, not ML specialists.

#ComponentEffortWhy nobody sells it
1Decision-event schema + irreversible logging (action_propensity, candidate_actions, state_snapshot, gate_rationale, suppressed_intent, holdout_group)1 eng-week — week one, non-negotiableEvery framework writes a different schema; adopting one forecloses off-policy evaluation forever. Unrecoverable at any price later.
2The offer gate — 13 vetoes, R/N/M conjunctive scoring, graduated tier ladder, gate_version, ~60 table-driven tests1 eng-weekVerified: zero OSS projects model readiness gating. This is the product.
3Viber bridge — webhook + CA-signed cert, HMAC-SHA256 on X-Viber-Content-Signature, 7 event types, idempotency on message_token (Viber retries 10× to 900s — duplicates are guaranteed, and here a duplicate re-sends an offer), rich_media, identity verification, rate limits12.5 eng-daysEvery Viber SDK is dead (Python 2021, Node 2021, Java marked DEPRECATED). The only MIT reference validates no signatures. File the Rakuten Viber commercial application in week one — 2–4 weeks lead time, else it becomes critical path.
4Cross-channel identity resolution + verification flow3–4 eng-daysViber exposes no phone number. Without it: holdout leaks, rewards can't backfill, the attention bucket triples.
5Per-slot write policy (update_rule, expiry_class, close-and-supersede on contradiction)~200 lines, 2 eng-daysMemobase had the design; it has one commit in all of 2026.
6Ukrainian groundedness model — translate RAGTruth preserving <hal> tags, fine-tune mmBERT-base, build a 300-example human-checked audit slice1 eng-week if you've fine-tuned a token classifier; 3–4 weeks if not. ~$60–120 spot GPU + 2–3 annotator-daysHHEM-2.3 covers Russian but not Ukrainian at any price. Ship the generative fallback first and defer this until catalogue-grounding errors appear in transcripts.
7National-ID PII recognizers (РНОКПП, Ukrainian ID-card, СНИЛС, ИНН, CNP)1–2 eng-daysPresidio's country_specific tree contains exactly one file east of Germany: Poland's PESEL.
8Window-aware proactive scheduler + attention budget + inverse-response ladder1 eng-weekNo marketing tool expresses "ensure a window is open, then drain the queue".
9Shadow cost-of-withholding ledger2 eng-daysThe only fast instrument that can tell you the gate is too tight.
10Golden set + eval harness — 300–500 conversations, ≥⅓ withhold cases, offer-when-should-withhold weighted 5:18–12 person-days of native-speaker labelling (the unpriced constraint in every plan)Sampling real logs certifies a bot that offers constantly — logs are dense in reasonable offers and empty of well-formed silences.
11GDPR erasure routine + canary-token test across Postgres, embeddings, traces, channel-provider logs, LLM retention2–4 eng-weeksEthyca's Fides was archived 2026-08-28; there is no maintained OSS DSR orchestrator. LLM-written summaries retain personal data that source-level deletion misses — a documented live bug class.
12Human handoffneeds_human + thread posted to a private Telegram group with a deep link~40 lines, 1 dayAlso satisfies Viber's contractual "ensure human oversight" and AI opt-out requirements. Chatwoot at month 3 if a real inbox is needed.
13Ukrainian politeness/register lexicon (Ви/ти detection, diminutives) if you port ConvoKit's approach1 eng-week + annotator validationConvoKit ships English and Chinese only. An unvalidated rapport score gating revenue is worse than none — it looks like evidence.
14Weekly digest email — offers made, offers withheld, decline rate, deflection, block rate, holdout gap with confidence sequence, golden-set score1 dayThe cheapest defence against building for nine months and shipping nothing.

Total irreducible build: roughly 11–14 engineer-weeks before a three-channel consultative bot exists, of which items 1, 2 and 3 are the critical path and items 6, 10 and 11 are the ones teams systematically under-estimate.

Honest dates for two people: support deflection live month 1 · gate live at soft mention on Telegram month 3 · three-channel parity month 5–6 · first consultative recommendation month 6 · direct-offer tier only when the golden set shows pass^4 ≥ 0.9 on withhold scenarios. Any plan promising three-channel autonomous selling in three months is promising a date it will miss — and the relationship layer then ships under schedule pressure, which is the exact condition under which the withholding discipline gets negotiated away.