The Offer Gate

Part three

The open-source landscape, by layer

Every project verified against live GitHub, Hugging Face and vendor docs on 15 September 2026, with the licence traps named.

Everything below was verified against the GitHub/HuggingFace/npm APIs or the vendor's own docs on 2026-09-15. "Fit" is 1–5 against this problem (long-horizon, offer-withholding, Ukrainian/Russian, Telegram+Viber+WhatsApp, 2–4 engineers) — not general quality. A project can be excellent and score 1.

Licence flags used throughout: OSI = genuinely open source · open-core = permissive core with a proprietary ee/ or enterprise/ tree · source-available = BSL/SSPL/Elastic/FSL/Commons Clause, not open source · non-commercial = CC-BY-NC or equivalent, unusable in a revenue product · use-restricted = RAIL/Llama/Gemma community licences with acceptable-use policies that flow down.


1. Memory & the persistent customer model

ProjectWhat it isLicenceMaturity (verified 2026-09-15)FitVerdict
pgvector + PostgresVector search as a Postgres extension; the substrate for a hand-rolled typed relationship recordPostgreSQL Licence (BSD-style) — OSI, cleanest in the dimension23,020★, last commit 2026-09-085The only place trust_score, last_offer_at, offer_fatigue can safely live. Erasure is one transactional cascade; multi-tenancy is a FK + RLS.
GraphitiBi-temporal knowledge graph; facts carry valid_at/invalid_at, contradictions invalidate rather than deleteApache-2.0 on the library30,892★, v0.30.2 2026-09-08, commit 2026-09-113Best temporal model in OSS — but every supported backend is encumbered: Neo4j GPLv3, FalkorDB SSPL, Neptune proprietary, Kuzu archived 2025-10-10 and marked deprecated in Graphiti's own pyproject. Also open GDPR bug #1837: remove_episode leaves deleted facts inside surviving entity summaries.
HindsightEpisodic + consolidated-belief memory; "observations" and "mental models" synthesised across historyMIT23,662★, v0.10.0 2026-09-14 — most actively released in the layer4One embedded Postgres container or fully in-process. Has temporal extraction and time-range filtering but not bi-temporal validity — it cannot assert a fact became false.
mem0Fact-extraction memory with vector retrievalApache-2.0 SDK — open-core trap65,326★, commit 2026-09-113Graph Memory, Memory Decay, Temporal Reasoning, Dream, custom categories, batch delete, org scoping are all Platform-only. The OSS SDK doesn't even accept the decay, timestamp or reference_date parameters. The features you're shopping for are the upsell.
Mastra memoryTS agent framework; resource-scoped working memory with Zod schema + merge semanticsApache-2.0 except ee/ (Mastra EE License v1.0, eff. 2026-08-24)28,057★, commit 2026-09-15, @mastra/core@1.66.04Audited: ee/ covers auth/ee, agent-builder/ee, editor/eememory and storage are Apache-2.0. stores/pg confirmed in-tree. Pin versions and diff ee/ on upgrade.
MemMachineThree-tier memory: working / episodic (graph) / profile (SQL)Apache-2.03,220★, commit 2026-09-03, release v0.3.9 2026-05-18 (4-mo lag)4Only project that hard-splits storage engines along the episodic/semantic line. Ships GOVERNANCE.md + maintainers dir. Red flag: sample_configs/ has episodic and server samples but no profile schema sample — configurable profile slots unconfirmed.
CogneeECL pipeline → queryable knowledge graph, custom ontologies, SearchType.TEMPORALApache-2.0 — but README line 235: "Using Postgres as a graph store is currently released as a demo feature. The production ready feature is available as a licenced product."30,697★, v1.5.4 2026-09-043The single-Postgres consolidation that makes it cheap is the paid tier. Embedded default is Kuzu — same unmaintained-upstream problem.
LettaMemGPT successor; self-editing memory blocks, sleep-time computeApache-2.024,747★ but the Python repo now contains no source — only .github, LICENSE and 7 markdown files; README redirects to letta-code (3,347★, TypeScript, commit 2026-09-15)2Steal two ideas: an always-in-context relationship block, and sleep-time consolidation between conversations as the place to recompute readiness. letta-code ships Slack/Telegram/Discord channels — the only OSS reference wiring persistent memory to a messenger.
MemobaseStructured user profile (YAML slots) + separate event timeline; per-slot update_description write policyApache-2.02,895★, last commit 2026-01-11 — one commit in all of 2026, zero releases ever, PRs unmerged since June2Steal the design, never the dependency. Per-slot write rules (explicit_only, never_overwrite_append, prefer_most_recent, keep_oldest) are ~200 lines in your own Postgres.
LangMem + LangGraph StoreMemory primitives over BaseStore; AsyncPostgresStore keeps everything in your own DBMIT1,666★, commit 2026-09-09, pre-1.0, 153 commits3Only sensible if already on LangGraph. You're choosing the DIY path with extraction prompts supplied.
SupermemoryMemory + context API with user profiles, forgetting, temporal contradictionsMIT badge — misleading29,677★, commit 2026-09-112apps/ and packages/ contain no server or engine directory. Self-host is curl … | bash, "one binary, zero config", single machine single process. Connectors and best models cloud-only. Evaluate as a SaaS vendor.
txtaiEmbeddings DB unioning sparse+dense+graph+SQL filteringApache-2.012,949★, v9.13.0 2026-08-27, commit 2026-09-142Clean retrieval substrate, zero memory semantics. If you adopt it, make Postgres the system of record and txtai a rebuildable index.
memUMarkdown/wiki memory with scheduled background consolidationApache-2.0 (LICENSE.txt read in full — unmodified)14,411★, commit 2026-09-10, v2.0.0-beta.02Two transferable patterns: human-readable memory a support agent can correct, and scheduled background consolidation.
MemoryOSFour-layer memory with heat-based promotion into the long-term profileApache-2.01,577★, commit 2026-07-07, release V1.2 from 2025-07-182Heat (access_count + recency → promotion) is genuinely good and is three columns in your own table. Eviction policy at your cardinality is undocumented.
EngramBi-temporal memory engine, valid-time + transaction-time, point-in-time queriesAGPL-3.0 dual-licensed — viral over a network17★, 244 commits, v0.1.0 beta 2026-07-142Cleanest statement of bi-temporality anywhere. Take the schema (valid_from, valid_to, asserted_at, superseded_by), not the dependency. Also steal its benchmarking discipline: always report the full-context baseline.
crm-llmMarkdown-native relationship CRM; Contact / Account / Activity / Note / Task schemaNO LICENCE — GitHub licence API returns 404, all rights reserved5★, 69 commits, commit 2026-08-302Read docs/schema-spec.md, write your own. It is the only artefact that makes Task a first-class object with a due date — the primitive every memory library lacks and your outbound scheduler must read from.
RushDBGraph+vector DB, schemaless JSON ingestionNo root LICENSE (API 404s); platform/LICENSE = Elastic License 2.0 with licence-key clause323★, 5 contributors, commit 2026-08-311Costs an Elastic licence and Neo4j ops while supplying no memory semantics. Worst quadrant.
mem-agent-mcp4B Qwen3 fine-tune for memory extract/update/retrieve over markdownApache-2.0 (server); weights licence unverified628★, last commit 2025-11-17 (10 mo)1Don't adopt. Do run the experiment: cheap small model on the write path, frontier model on the customer-facing reply. Extraction cost scales with volume; quality scales with the reply.
A-MEMZettelkasten memory; new facts rewrite neighboursMIT1,179★, 31 commits, last commit 2025-12-121Read the evolution mechanism, then invert it: close-and-supersede, don't rewrite in place. Rewriting destroys the audit trail an offer decision needs.
ReMe / MemoryScopeLocal-first consolidation ladder (raw → daily note → long-term), proactive topic discoveryApache-2.0, ModelScope-backed3,460★, commit 2026-09-15, v0.4.1.111Local-first markdown-per-user doesn't survive multi-tenancy. Steal the three-stage consolidation ladder and the "what should we talk to this person about next" idea.
Zep CESelf-hostable memory serverApache-2.0 — irrelevant4,917★, commits 2026-09-11 and releases 2026-08-28 — but the README's own contents table reads legacy/ | Deprecated Zep Community Edition (unsupported)1Trap. Repo looks healthy by every crude metric; the self-hostable product inside it is dead. README redirects self-hosters to Graphiti.
MotorheadSession-window + rolling-summary memory serverApache-2.0917★, last commit 2025-07-22 (14 mo), not archived1Landmark only. If a memory project's pitch is "session window plus summary", it is 2023 technology.

How to choose here. Split the layer in two and do not let them blur. The authoritative half — relationship_stage, trust_score, readiness_score, last_offer_at, offers_in_30d, consent, channel-window expiry — goes in typed Postgres columns written only by deterministic code, because offer gating must be unit-testable, atomically updated and auditable after a complaint. The fuzzy half — "what did she say about her sister's wedding in June" — goes in an engine. For the fuzzy half the real decision is temporal invalidation vs operational simplicity: Graphiti wins on invalidation and loses on backends (there is no permissively-licensed production graph store left); Hindsight wins on one-container MIT simplicity and loses on bi-temporal validity. At 500–50,000 customers, take Hindsight or roll bitemporal columns yourself (valid_from, valid_to, asserted_at, superseded_by — four columns and a close-and-supersede rule) and skip the graph database entirely. Whatever you pick, add an explicit expiry class at write time (permanent: allergies, sizes · seasonal: pregnancy, injury, relocation · decaying: purchase recency), because decay and invalidation are different mechanisms and you need both.

Things that sound right but aren't (memory). "Self-host Zep" — dead, moved to legacy/, and the repo still commits daily so a liveness check gives a false green. "Mem0 has decay and temporal reasoning" — Platform-only, and the OSS SDK dropped external graph stores in the April 2026 rewrite. "Supermemory is MIT and self-hostable" — the MIT repo is SDK surface; the engine ships as a closed single-machine binary. "PowerMem gives you Apache-2.0 Ebbinghaus decay" — it rebranded to PowerContext, restarted at v0.1.0 on 2026-08-31, pivoted to coding-agent work handoff, and its README now contains zero mentions of decay, profiles or forgetting. "Memobase is actively maintained" — one commit in all of 2026. "lifetimes for CLV" — archived 2024-06-28.


2. Conversational policy / guideline / journey engines

ProjectWhat it isLicenceMaturityFitVerdict
Open Policy AgentCNCF-graduated policy engine; Rego rules over JSON input, versioned as files, unit-testableApache-2.0, CNCF governance — OSI, no vendor12,234★, v1.20.2 2026-09-03, commit 2026-09-155The only thing that can guarantee "no offer before stage 3 and not within 14 days", because it's code evaluating counters. Gate the tool call, not the text. Cost: Rego's unordered-rule semantics are a real week of pain for a small team.
CedarAWS's formally-verified policy languageApache-2.01,735★, commit 2026-09-144Same guarantee as OPA, ordered and typed, materially gentler. Bake-off before committing the team to Rego.
ParlantGuidelines (condition→action), Journeys, Context Variables, strict-mode Canned ResponsesApache-2.0, no ee/18,287★ — releases stalled: v3.3.2 2026-04-28 after a monthly cadence; last develop commit 2026-06-26; issues and PRs still arriving4Strict canned responses are the strongest anti-eagerness mechanism in OSS: a premature pitch isn't in the candidate set, so it can't be spoken. Per-turn logs of guideline id + condition + rationale + applies_score answer "why did it pitch on turn 40" as a query. No Postgres store — adapters are json_file, mongo_db, snowflake_db, transient (0 hits for "postgres" repo-wide). No channel connectors. Single vendor. Pin a version and keep guidelines in your own table.
LangGraphGraph state machine with checkpointers and a separate cross-thread StoreMIT, clean41,688★, commit 2026-09-14, checkpoint-postgres a separately-versioned package5The checkpointer-vs-Store distinction is the one detail most people miss: checkpointers are thread-scoped, so relationship state in checkpointed state silently resets on a new conversation. Conditional edges give deterministic gating for free.
python-statemachineLive Python statechart library — guards, nested states, async, diagram exportMIT1,310★, commit 2026-09-144stranger → helped → trusted → offer_eligible → cooling_off is a statechart. Guards are functions the LLM cannot invoke; the model proposes, the machine disposes. Diagram export renders your live sales strategy as a picture stakeholders can argue about.
XStateStatecharts + actor model, zero deps, persisted snapshotsMIT30,114★, v5.33.0 2026-09-12 — note v6.0.0-alpha.54 in flight4TypeScript equivalent. Pin v5.
NeMo GuardrailsInput/output/dialog/retrieval/execution rails in ColangApache-2.0 (SPDX header in LICENSE.md; API says NOASSERTION)7,124★, v0.24.0 2026-08-26, ~6-week cadence, commit 2026-09-14. Repo moved: NVIDIA/NeMo-GuardrailsNVIDIA-NeMo/Guardrails3Retrieval rails are the piece everyone skips and you need — product reviews and supplier copy are untrusted text reaching a tool-calling planner, and your memory persists the injection. Dialog-rail matching is embedding/LLM-driven: strong-advisory, not a guarantee. Two incompatible Colang versions.
Microsoft Agent FrameworkMerged AutoGen + Semantic Kernel successor; checkpointing, HITL, time-travelMIT13,531★, python-1.18.0 / dotnet-1.21.0, commit 2026-09-15 titled [BREAKING]3Right answer only if you're .NET/Azure. "Stable APIs" is aspirational — breaking changes still landing five months after 1.0.
MastraTS workflows with .branch() and suspend/resumeApache-2.0 + ee/28,057★, commit 2026-09-154Suspend/resume snapshots persisted to Postgres (stores/pg confirmed) is the primitive for "the journey waits weeks". Built-in scorers matter: you need "did it pitch too early" as a tracked metric.
AgnoAgentOS runtime: 50+ endpoints, sessions/memory in your DB, RBAC, human-approval pauses, schedulingApache-2.0, no ee/42,184★, v3.0.9 2026-09-08, commit 2026-09-15 (a Slack event-dedupe fix)4db/ contains postgres and async_postgres; 107 code hits for "scheduler". "Block tools that require admin approval" is a built-in hook for the readiness gate. Parlant names Agno as a composable pairing.
Pydantic AITyped agent loop + first-party durable executionMIT19,949★, commit 2026-09-154Durable timers are the clean way to implement "no offer for 14 days" as a scheduled state transition rather than a read-time query. Backend licence trap: of four co-maintained backends only Temporal (MIT) and DBOS (MIT) are OSI — Restate is BSL 1.1, Inngest is SSPL.
Guardrails AIComposable input/output validators, on-fail reask/refrain/filterApache-2.07,415★, v0.11.0 2026-08-14, commit 2026-08-26 titled update-hub-retirement-date2Useful for one job: a typed, validated OfferProposal object so a malformed or unauthorised offer fails at the boundary. Hub hosted-inference retirement is an in-flight migration with a moving date.
OpenAI Agents SDKAgents, handoffs, tripwire guardrails, sessionsMIT29,451★, commit 2026-09-153Guardrails are post-hoc tripwires; a fired output guardrail has already burned a generation while the customer watches a typing indicator. Export traces to your own OTel collector — free hosted tracing pulls months of customer relationship history to a third party.
Rasa OSS / CALMIntent NLU (OSS) vs flow guards + deterministic Flow Policy (Pro)Apache-2.0 on the wrong product; CALM is proprietary Rasa Pro21,325★ but frozen: last commit 2025-12-18, last release 3.6.21 2025-01-14. README: "Rasa Open Source is currently in maintenance mode."3CALM's flow guards (pypred conditions, deterministic action selection) are the best mechanism-level match in existence — and you can't have them free. Free-tier caps (1,000 conversations/mo) could not be verified; get them in writing. Cheapest move: copy the pattern (LLM for understanding, deterministic guards for action) onto LangGraph.
CrewAIMulti-agent crews + Flows with @router()MIT58,583★, commit 2026-09-15; memory package now has unified_memory.py, memory_scope.py with pre-1.14.6 migration handling2The old "no cross-thread memory" objection is dead. The real objection stands: its design centre is agent autonomy, which is the failure mode you're engineering against.
DifyVisual LLM app platformModified Apache-2.0 — verbatim: no multi-tenant operation without written authorisation; may not remove the LOGO/copyright in web/155,795★, commit 2026-09-152Will dominate every search. No stage model, no journey entry/exit conditions, branching is per-run not per-customer-over-months.
n8nWorkflow automation, 400+ integrationsSustainable Use Licence — plus: non-master branches are not licensed at all, and files with .ee. need an Enterprise licence204,356★, commit 2026-09-152Excellent cron + connector hub; keep it strictly outside the policy boundary.
BotpressIntegrations + SDK monorepoMIT on the repo only — runtime is proprietary Botpress Cloud; legacy v12 is AGPL, last push 2025-04-1014,917★, commit 2026-09-142Reject as a platform (you cannot self-host the engine). Adopt as source: integrations/ has telegram, whatsapp and viber — the only maintained MIT Viber connector anywhere.
TypebotVisual flow builderFSL-1.1-Apache-2.0 — source-available, competing-use restricted, converts to Apache after 2 years10,324★, commit 2026-09-141Corrected: converts to Apache, not AGPL. Still not OSI, and a deterministic flowchart cannot give genuine advice.
LangflowVisual agent builderMIT — cleanest of the visual builders154,818★, commit 2026-09-151Legitimate for one day of prototyping the advisor's voice, then re-implement. Session persistence semantics unconfirmed.
FlowiseVisual agent builderNOASSERTIONARCHIVED 2026-08-13, 55,460★, README: "Flowise has been archived."1Dating tool: any source recommending it predates August 2026 — re-verify everything else it says.
Semantic Kernel / AutoGenSuperseded frameworksSK MIT; autogen root LICENSE is CC-BY-4.0 — a content licence on a code repoSK 28,559★ maintenance-mode banner, commit 2026-09-11; AutoGen 60,989★, last push 2026-04-151Migrate to MAF. Don't vendor AutoGen code without per-package licence checks.
AG2Community AutoGen forkApache-2.04,926★, commit 2026-09-14; ag2-classic is a 2★ stub1Squeezed between MAF and LangGraph. Multi-agent autonomy is the wrong shape.
smolagentsCode-writing ReAct agentsApache-2.029,335★, push 2026-08-251Maximum autonomy — the exact inverse of a policy-constrained advisor. Fine for offline readiness batch jobs; never in the customer loop.
Atomic AgentsPydantic schema-as-contract agentsMIT6,241★, v2.10.2 2026-08-24. Repo moved from BrainBlend-AI2Steal the pattern: every offer is a validated typed object, never a free-text sentence. That turns "the model mentioned a product" from an unbounded prose problem into a gateable schema problem.
TockFull conversational platform with connectorsApache-2.0, institutionally maintained611★, tock-26.3.4 2026-09-01, disciplined ~6-week train2Connector list verified: whatsapp, whatsapp-cloud, messenger, teams, slack… no telegram, no viber. Value is the Apache-2.0 WhatsApp Cloud connector as readable reference.
BotSharp.NET bot platformApache-2.03,101★, commit 2026-09-151Only if .NET is mandatory — and then MAF is better-supported.

How to choose here. Three layers, and conflating them is the standard mistake. (a) Deterministic enforcement — OPA/Cedar, or a plain Python function with a gate_version constant and a table-driven test suite. Anything countable (caps, cooldowns, quiet hours, window state, complaint status) lives here, gating the tool call. (b) Durable orchestration — LangGraph (Postgres checkpointer plus the cross-thread Store) or Pydantic AI on DBOS/Temporal, so a relationship survives a three-week gap and a deploy. (c) Conversational shaping — Parlant strict canned responses or NeMo predefined-response substitution, so the model's words are constrained on any turn that mentions a price, spec, stock level or delivery date. For a 2–4 person team the honest month-one answer is: skip (a)'s DSL, write the gate as ~300 lines of Python with gate_version pinned to a git SHA, and keep the migration to Rego mechanical by keeping the gate I/O-free (state in, decision object out).

Things that sound right but aren't (policy). "Use Rasa for intent + flow guards" — Apache repo is frozen and in maintenance mode; the guards are in proprietary Rasa Pro. "Flowise for a quick visual prototype" — archived. "Semantic Kernel's Process Framework models business processes" — maintenance mode; the concept survives in MAF. "Hatchet is the lightweight Temporal" — its own docker-compose.yml needs Postgres + pgbouncer + RabbitMQ + NATS, four stateful services including two brokers; minimal Temporal runs on plain Postgres with no Elasticsearch. "Challenger's Take Control" — its evidence is manager-rated self-report with no peer-reviewed replication, and "take control" maps onto aggressive sales pressure, which the JAMS work shows damages trust in both seller and company. Take Teach and Tailor, drop Take Control.


3. Existing sales / SDR / conversational-commerce prior art

ProjectWhat it isLicenceMaturityFitVerdict
SalesGPTThe canonical 8-stage sales agent: a cheap "stage analyzer" chain emits a stage digit, the generator is re-prompted per stageMIT2,758★, 604 forks, last commit 2024-09-17; last substantive code change 2024-08-05 — ~25 months, zero maintained forks among 6043Read salesgpt/prompts.py, install nothing. The two-chain decomposition (cheap classifier emits state, generator conditioned on state) is exactly right; the stage analyzer is stateless, re-deriving stage from the current transcript, so it resets to Introduction every new conversation. It also has no "advise only" terminal state — every path ends at Close. That gap is your product.
b2b-sdr-agent-template10-stage SDR pipeline, 4-layer memory protocol, 14 cron jobs, WhatsApp+Telegram+emailMIT180★, push 2026-08-20, primary language Shell — substance is Markdown skills + shell + two .mjs files4The only living repo with an explicit stage pipeline + per-customer isolated memory + scheduled proactive outbound. Steal verbatim: the cron row "Every 30 min | WhatsApp 72h window expiry check → auto-switch to Telegram", and the L1–L4 memory split. Invert: its crons exist to push every lead forward daily; you need jobs that evaluate readiness and usually send nothing.
OneWave-AI/claude-skills — sales-methodology-implementer7 sales frameworks; per-component 0–10 evidence-scored rubric → 0–100 → Qualified / Pursue With Caution / Disqualify; Tier 1/2/3 discovery ladders with red/green flagsMIT292★, push 2026-08-114The upsell-readiness gate already exists as a design, in a coaching skill pack. Verbatim operating principles: "Score on evidence, not assumptions; reward disqualifying bad deals early" and "Implement one methodology at a time." Re-derive components for B2C: Problem Understood, Advice Accepted, Fit Confirmed, Budget Signalled, Trust Demonstrated.
Hermes AgentLong-lived per-user memory (SQLite + FTS5 + summarisation), agent-curated memory, cron, unified Gateway across 30+ messaging surfacesMIT245,686★, created 2025-07-22, push 2026-09-15; 43,307 open issues4Ships both an official WhatsApp Cloud API path and an unofficial bridge; Telegram, Signal, LINE, WeCom, SMS, email. No Viber (0 paths repo-wide). Memory is agent-curated free text — keep the gate in typed Postgres beside it.
OpenClawAgent runtime, ~29 documented channels, Markdown skills, documented channel-plugin SDKMIT (LICENSE read; API says NOASSERTION), 501(c)(3) foundation389,737★, push 2026-09-154No Viber — issue #4457 closed unimplemented. But docs/plugins/sdk-channel-{inbound,ingress,outbound}, message-adapter, sessions-and-bindings, durable-ingress, approvals make writing one bounded work.
tau2-benchDual-control agent benchmark with a retail domain, configurable reward_basis, and pass^kMIT2,037★, push 2026-09-114The only mature mechanism for scoring "did the right thing and did not do the forbidden thing" as separable axes. pass^k is the metric you want: a bot that correctly withholds 70% of the time is pushy in one conversation in three, and on messaging that's a permanent block. Single-episode by construction — you write the multi-episode driver.
agent-squadMulti-agent routing + ecommerce-support-simulator and mcp-shop-server examplesApache-2.0, no carve-out7,762★, push 2026-09-133Best idea: make the Offer agent reachable only when the gate opens. Architectural gating beats prompt gating — a prompt saying "don't upsell yet" gets talked around; an agent the router can't reach doesn't.
HonchoPeer-representation memory: models what one peer knows about another, reasoning-first rather than chunk-matchingAGPL-3.0 — network copyleft; managed cloud at api.honcho.dev is the moat7,174★, push 2026-09-143Closest substrate to "what does the bot believe about this customer, and how confident is it". Documented integrations with OpenClaw and Hermes. AGPL is a real constraint for a hosted commercial bot. Apache-2.0 fallbacks: mem0, Letta, Graphiti.
NVIDIA Retail Shopping AssistantLangGraph multi-agent: Chain Server / Catalog Retriever / Memory Retriever / GuardrailsNVIDIA SLA + Community Model Licence + a separate Data Licence on the catalog — not OSI97★, push 2026-09-152Read the node topology; run nothing. 4× H100 preferred / 4× A100 minimum. Best idea: memory promoted to a first-class agent node beside catalog and guardrails — add a Relationship-State node in that position.
Google ADK — personalized-shoppingConversational shopping agent over WebShopApache-2.0adk-samples 10,315★, push 2026-09-15; WebShop dependency last push 2024-09-062Included as evidence: Google's flagship shopping sample calls create_session(user_id='new_user') per interaction. When the best-resourced vendor sample has no cross-session model, that's your proof the readiness gate is genuinely unbuilt. Pair it with the repo's separate memory-bank sample — combining them is roughly milestone one.
AiogramShopBotProduction Telegram commerce: cart, orders, reviews, referrals, coupons, SQLAdmin, multibot modeMIT238★, push 2026-08-312Mine the schema (user, cart, cartItem, buy, buyItem, item, review, referral, coupon, shipping_option). Purchase history joined to conversation history is the substrate your readiness scorer needs. Zero AI; crypto-payment orientation you'd strip.
Streamer-SalesChinese livestream-sales LLM with a full synthetic persuasion data-generation pipelineAGPL-3.03,772★, last push 2025-03-082Highest-leverage idea in the landscape, and it must be inverted: no open dataset of restraint in selling exists, which is why every off-the-shelf sales agent sounds pushy. Read the pipeline; generate advisory and withholding examples where the correct answer is "don't buy that one yet".
langgraph-sales-agentMulti-tenant LangGraph, per-tenant YAML personality/catalog, Telegram+Instagram+WhatsApp+webMIT65★ — created 2026-03-02T20:58:30Z, last push 2026-03-02T21:01:01Z: the entire repo history spans 150 seconds2Read the config-over-code idea (one graph, YAML-injected tenant) in one sitting; rebuild from scratch. Multi-tenancy in a three-minute repo is an aspiration.
UCPGoogle+Shopify agentic-commerce standard; Capabilities + Extensions, REST/MCP/A2AApache-2.03,376★, push 2026-09-12, 194 open issues. Shopify's own AI toolkit now ships a ucp skill2Design your internal catalog interface to resemble UCP capabilities so an adapter is cheap; don't block on adoption. Assumes the transaction happens inside a third-party AI surface — a different distribution model from your own branded bot.
ACP + AP2OpenAI/Stripe checkout standard; Google AP2 payment mandates (donated to FIDO)Apache-2.0ACP 1,543★, push 2026-07-18, still beta with 140 open issues1Payments plumbing. Contributes nothing to timing. AP2's delegated-mandate concept is worth reading as a model for "the customer has pre-authorised offers of this type".
CRMArena / ProCRM agent benchmark with confidentiality-awareness (does the agent correctly withhold?) and multi-turn personasCC-BY-NC-4.0 — non-commercial, hard blocker148★, push 2026-09-10 (actively maintained)1Structurally the same test as "did the bot resist pitching to someone not ready". Read the paper; re-implement the task design in tau2-bench (MIT). Do not vendor, run, or copy the data.
Shopify AI Toolkit22 developer-side skills for coding agentsMIT544★, push 2026-09-11; .mcp.json is literally {"mcpServers": {}}1Misconception correction: "Shopify open-sourced their agent" is false. Sidekick is not open source in any form.

How to choose here. The blunt finding: no open-source project does what this user wants. Every piece of prior art is a single-session closer. GitHub repo search on 2026-09-15 returns total_count=0 for "customer lifetime value agent LLM" and "proactive re-engagement agent messaging", and exactly one 1-star result for "sales conversation stage llm". So assemble from three unrelated places: SalesGPT's two-chain decomposition (but with persisted state instead of a recomputed stage), the MEDDIC/SPIN skill pack's evidence-scored rubric (re-derived for B2C), and b2b-sdr-agent-template's channel-window cron and memory layering. Then build the readiness gate yourself — it is the product, and it is a small amount of deterministic code.

Things that sound right but aren't (prior art). "Start from SalesGPT" — 25 months dead, pinned to a 2024-era LangChain, and architecturally incapable of cross-session state. "CRMArena is the benchmark for withholding" — CC-BY-NC, legally unusable in a commercial pipeline, mirrors on HuggingFace carry the same terms. "Shopify's agent is open source" — it isn't. "Use the retail domain in tau2-bench as-is" — it's returns and order modification, i.e. customer service, not advisory selling, and it's single-episode. "antiwork/helper is the leading OSS AI helpdesk" — github.com/antiwork/helper returns HTTP 404 and is absent from the org's 67 repos, yet it's still recommended by 2026 blog posts and has a live Play Store listing.


4. Customer care platforms / omnichannel inbox

ProjectWhat it isLicenceMaturityFitVerdict
ChatwootOmnichannel inbox, contact record with typed custom attributes, Agent Bot webhook API, campaigns, bot→human handoffSplit: root LICENSE is MIT Expat; enterprise/LICENSE requires a paid subscription for production and forbids copy/distribute/sell (dev+test copying allowed). API reports NOASSERTION36,821★, v4.17.1 2026-08-27, commit 2026-09-15, 1,408 open issues4The open-core split falls exactly right: config/features.yml marks 19 features premium: true (captain_integration, captain_integration_v2, custom_tools, sla, custom_roles, audit_logs, saml, advanced_search, channel_voice, companies, advanced_assignment…), while agent_bots, automations, campaigns, crm, canned_responses are not premium. 252 captain* files live under enterprise/. No Viberapp/models/channel/ has exactly 12 models and viber isn't one; issues #3148 and #13231 both closed unimplemented.
Chatwoot Agent Bot API + API channelRegister a bot user with a webhook; drive conversations over REST; api channel for arbitrary transportsMIT core (app/models/agent_bot.rb and all five bot_handoff code paths are outside enterprise/)Stable since 1.x; handoff UX still in flux5The single most important architectural fact: Chatwoot does not force you into its flow builder. Conversation status is your ownership state machine — pending = brain, open = human, resolved = episode closed. bot_handoff! fires only when pending and the caller is the bot. ⚠ PR #15763 (dashboard alerts on handoff) is OPEN and unmerged — handoff does not notify a human in shipped code. Build that yourself or you get silent escalations.
LibredeskSingle Go binary omnichannel desk; free Agent Copilot (internal/ai/copilot.go), custom attributes, webhooks, SLA, CSAT, macrosAGPL-3.0, no ee/, no paid tiers2,924★, v2.8.0 2026-08-22, commit 2026-09-15, monthly releases3The clean counter-example to Chatwoot's open-core. But internal/inbox/channel/ contains exactly two entries: email and livechat. Roadmap: WhatsApp WIP, Telegram TODO, Viber absent. Right brain, no mouths. Re-evaluate when WhatsApp ships.
ZammadMature ticketing with native telegram.rb and whatsapp.rb drivers in the AGPL codebase; BYO-LLM AI incl. local OllamaAGPL-3.0, single SPDX, no ee/5,915★, commit 2026-09-153Best "hard AGPL, no open-core anxiety" option with two of three channels free. No AgentBot equivalent — driving it externally means webhooks/polling against a ticket API, clumsy for real-time chat. Ticketing grain fights a longitudinal relationship. AI add-on is plan-gated and metered.
FreeScoutShared-mailbox desk with a module ecosystemAGPL-3.0 core — but Modules/ in the repo contains only .gitkeep; Telegram, WhatsApp, Custom Fields, CRM, AI are paid proprietary modules sold separately4,534★, commit 2026-09-15, only 22 open issues2Structural proof of the paywall. Module licences are one-time per instance (better shape than per-seat), and the AI module is free — but every component you need is on the paid side, and it's email-thread-shaped. No Viber.
Rocket.ChatTeam chat + OmnichannelMIT core + proprietary ee/Omnichannel is concentrated in ee/ (21 code hits vs 6 in core); Starter/Pro capped at 100 Monthly Active Contacts46,119★, commit 2026-09-15, 4,038 open issues2The MAC cap is disqualifying for e-commerce: pricing scales with exactly the metric your LTV strategy exists to grow. Roadmap has drifted toward government/defence comms.
Erxes"Experience OS" fusing CRM + support in one contact modelAGPLv3 + a proprietary ee/ tier + a field-of-use clause, verbatim: "erxes is not permitted to be hosted as a SaaS version to compete with erxes Inc." — incompatible with the OSD4,079★, commit 2026-09-15, 671 open issues2Conceptually the closest (CRM+support unification is the right instinct). Worst licence in the layer; no Viber, no evidence of native Telegram/WhatsApp; mid-rewrite (erxes-next). Implement the idea on Chatwoot contact attributes instead.
TiledeskConversational AI platform with HITL, WhatsApp + TelegramMIT across components318★ (dashboard 15★, chatbot 6★), all pushed ~2026-09-142Clean MIT and HITL, but forces you into its bot builder — the opposite of Chatwoot's AgentBot model. Adoption fragmented across tiny repos.
ChaskiqIntercom-alike: segmentation + triggered campaigns + in-app messagingAGPL + Commons Clause — verbatim: "Sell means … to provide to third parties, for a fee … (including … hosting or consulting/support services …)". Not OSI3,569★, last push 2026-06-30 (~2.5 mo, decelerating)2Its segment+trigger+message lifecycle model is the best-thought-through in OSS — read the design, don't run the code. No native Telegram/WhatsApp/Viber.
antiwork/skills ("Gumclaw")Redacted architecture of the agent Antiwork says runs Gumroad: 273 skills, 78 cron loops, 2 memory files, AUTONOMOUS/GATED/HUMAN action tiersNO LICENCE (API: license: NONE) — all rights reserved10★, push 2026-07-014Highest signal-to-noise document in this layer. README verbatim: "The brain doesn't wait to be asked. 78 scheduled jobs give it a heartbeat", most "run silent, speaking up only when something is worth a human's attention." Three transferable ideas: the MEMORY.md/USER.md split (what it knows vs who it serves), silent-by-default cron cadence, and offer-making as a GATED action tier. Re-implement; you have no licence to copy.

How to choose here. Adopt the inbox, build the brain, and never let the helpdesk own your relationship model. Chatwoot's MIT core gives you three genuinely expensive things — native Telegram+WhatsApp transport with media and delivery semantics, a human agent workspace with assignment and escalation, and a contact record that survives across conversations — and gives you nothing on relationship stage, trust or offer timing, which is fine because those belong in your Postgres anyway. Keep relationship_stage/trust_level/upsell_readiness authoritative in your DB and mirror them into Chatwoot custom_attributes (which require a CustomAttributeDefinition to become filterable in automations and campaign audiences — pay that schema cost). If you ever leave, you lose a projection, not your brain. For a month-one team the even smaller answer: a needs_human flag plus posting the thread into a private Telegram group with a deep link is ~40 lines and zero new services.

Things that sound right but aren't (care platforms). "Chatwoot gives you a free self-hosted AI agent" — Captain's backend is 252 files under enterprise/ and needs a paid subscription in production. "Chatwoot has Viber" — it doesn't, and two issues asking for it were closed unimplemented. "lib/captain/follow_up_service.rb is re-engagement" — it's an agent-facing editor refinement loop; Captain does no proactive customer nurture. "Papercups is a live OSS option" — 6,104★, MIT, never formally archived, but its default-branch last commit is 2022-05-10. "antiwork/helper" — 404.


5. Channel libraries & gateways (Telegram · WhatsApp · Viber)

ProjectChannelLicenceMaturityFitVerdict
aiogramTelegramMIT5,865★, v3.31.0 2026-08-25 titled "Telegram Bot API 10.3"5Async, router-based, built-in FSM with Redis storage, middleware layer = the right seam for a "may I offer right now?" gate on every outbound send. Fastest to track new Bot API versions.
python-telegram-botTelegramDual GPLv3 or LGPLv3LICENSE.dual: "You may choose either license"29,465★, commit 2026-09-15, but latest release v22.8 (2026-06-12) and README declares Bot API 10.0 — three versions behind4JobQueue (run_daily/run_once/run_repeating) and a built-in AIORateLimiter are genuinely useful. JobQueue is in-process and not durable — a three-week follow-up evaporates on deploy. Under the LGPL election you may link from proprietary code.
grammYTelegram (TS)MIT3,742★, v1.46.0 2026-08-264Correct choice if TypeScript. grammyjs/conversations (MIT, 65★, push 2026-07-23) expresses long advisory dialogues as async functions. Cautions: grammyjs/runner is 27★ and last pushed 2024-09-27; grammyjs/storages has no declared licence.
go-telegram/botTelegram (Go)MIT1,838★, commit 2026-09-113The live Go option — go-telegram-bot-api (6,403★) has been dead since 2024-08-14. Relevant only if you commit to Go to match whatsmeow/GOWA.
pyTelegramBotAPITelegramGPL-2.0 — no linking exception8,778★, commit 2026-09-12, only 5 open issues3Remarkably well-tended; fine for a spike. Migrate to aiogram before commercialising.
pywaWhatsApp (official Cloud API)MIT591★, v4.4.0 2026-08-11, 5 open issues4Fills the gap nobody names: Meta's own WhatsApp/WhatsApp-Nodejs-SDK has been archived since 2023-06-07, so every serious Cloud API integration is a hand-written HTTP client. Typed handlers, Flows, webhook server, dev CLI. Single-maintainer — vendor it.
whatsapp-api-jsWhatsApp (official)MIT354★, commit 2026-09-143Dependency-free TypeScript client — which matters in a neighbourhood with a documented supply-chain problem. Thin client; you still own template lifecycle, idempotency and retries.
whatsmeowWhatsApp (unofficial, protocol-level)MPL-2.07,310★, commit 2026-09-14 — zero tagged releases, pin by commit SHA3Technically the soundest unofficial base (no browser), by the mautrix author. Engine under WAHA's GOWS, wuzapi and GOWA. ToS-violating; bans are permanent.
GOWAWhatsApp (unofficial)MIT, no riders4,784★, commit 2026-09-15; 70 code hits for "chatwoot" — a real integration3Best licence-to-architecture ratio in the unofficial category: single Go binary, REST + MCP, Chatwoot built in. Strictly better licence than Evolution API for the same capability.
WAHAWhatsApp (unofficial)Apache-2.0 on the core branch LICENSEbut the README still advertises docker pull devlikeapro/waha-plus and gates multi-session "in Plus", contradicting the docs site's "everything is free since 2026.6.1"7,398★, 2026.8.2 2026-09-01, 461 open issues3Three swappable engines (WEBJS/NOWEB/GOWS). Verify what the free image actually contains before building on it. Pick GOWS to avoid headless Chrome per session.
BaileysWhatsApp (unofficial)MIT11,044★, commit 2026-09-15, 337 open issues, latest release v7.0.0-rc14 — a release candidate2De-facto substrate of the whole unofficial ecosystem. Ban evidence is real and current: issues #2309 (permanent ban from a production server, 2026-01-30), #2658 ("Your account may soon be subject to restrictions", 2026-06-24), #1869 ("High number of bans").
Evolution APIWhatsApp (dual official + unofficial)"Apache-2.0 with the following additional conditions" — you may not remove the LOGO/copyright, and must display a clear in-system notification that Evolution API is in use, visible to admins and in docs/settings; non-compliance "may result in the necessity for a commercial license"9,615★, last push 2026-07-14 (~2 mo); repo moved EvolutionAPI/evolution-foundation/3Genuinely useful dual-mode abstraction + Chatwoot wiring. The attribution rider is enforceable and applies even to closed proprietary systems. Ships slower than Baileys.
whatsapp-web.jsWhatsApp (unofficial, Puppeteer)Apache-2.022,560★, commit 2026-09-13; repo moved from pedroslopez/2Most popular, worst architecture at scale — a headless Chrome per session, fragile against DOM changes. Prefer whatsmeow or Baileys.
WPPConnectWhatsApp (unofficial)Inconsistent: wppconnect is LGPL-3.0, wppconnect-server and wa-js are Apache-2.03,453★ / 1,050★ / 802★, all pushed ~2026-09-152Check each of the three repos' LICENSE individually. Portuguese-first docs. Browser-injection fragility.
wuzapiWhatsApp (unofficial)MIT1,019★, push 2026-08-12, 88 open issues2Smallest auditable surface over whatsmeow — a real advantage if you're accepting ToS risk and want to read what touches your session credentials.
open-wa/wa-automate-nodejsWhatsApp (unofficial)Hippocratic + Do Not Harm (H-DNH) v1.1 — non-OSI, use-restricted; plus paid licence-key feature gating3,654★, push 2026-09-10, 188 open issues1Reject on licensing before evaluating technically. Python sibling dead since 2022-03-28.
botpress integrations/viberViberMITv1.0.6, repo pushed 2026-09-14; the folder itself only touched by repo-wide chores since 2026-033The only actively-maintained OSS Viber channel code in existence. But 419 lines in one src/index.ts: grep for crypto/hmac/x-viber-content-signature returns zero hits — it validates nothing; actions: {} and events: {} (no receipts); no retry/idempotency; its own description field reads "Send and receive SMS messages." Read it, never depend on it.
Viber official SDKs — python · node · javaViberNOASSERTION on python/node (audit before vendoring); java titled "[DEPRECATED]"python 185★ last real commit 2021-01-28; node 109★ 2021-08-24; java 42★ 2019-11-282Dead across the board. Community options are worse: Bogdaan/viber-bot-php 237★ last push 2022-01-28; viberroo 10★ last real commit 2025-01-07 (a dependabot bump). Port the signature logic from viberbot/api/api.py; write your own client.
mautrix-whatsapp / -telegramProtocol unificationAGPL-3.0 both; matrix-org/synapse is archived, successor element-hq/synapse is AGPL1,886★ / 1,737★, both pushed 2026-09-14/152One Matrix event stream across channels is conceptually attractive — but AGPL down the whole path, no Viber bridge, mautrix-whatsapp carries whatsmeow's ban risk, and the bridge degrades exactly the rich commerce UI (inline keyboards, interactive templates) your conversion flow needs.
TelethonTelegram MTProto user accountsMIT12,057★, ARCHIVED (archived=true), last push 2026-02-21; README: "Moved to codeberg.org/Lonami/Telethon. The GitHub repository may be deleted in the future."1Archived on GitHub, and more fundamentally: driving a user account for commercial outreach is the pattern Telegram bans for. gram-js/gramjs is also archived. TDLib (Boost licence — permissive, not Business Source) is the only maintained MTProto path.

How to choose here. Telegram first, always: free, no window, no templates, and therefore the only channel where you can A/B "withhold vs offer" without the result being confounded by per-message cost or template-approval latency. Ship aiogram (Python) or grammY (TypeScript). WhatsApp second, via Cloud API direct or a flat-fee BSP — never a per-message BSP, because Twilio's verified $0.005 applies inbound and outbound, including on messages Meta bills at zero, which is exactly the traffic a chatty advisor generates ($800/month at 50k customers vs ~€49/month flat on 360dialog). Viber last and hardest: nothing exists, so budget ~12.5 engineer-days — webhook + CA-signed cert (self-signed explicitly unsupported; issuer must be on the Sun Java trusted root list) 1d · HMAC-SHA256 X-Viber-Content-Signature validation 0.5d · all 7 event types 2d · idempotency on message_token 1d (mandatory — Viber retries 10× with exponential intervals from 10s to 900s, so duplicates are guaranteed, and a duplicate here means re-sending an offer) · outbound text/picture/rich_media 2.5d · user-id↔contact mapping and a verification flow 2.5d (Viber exposes no phone numbers) · rate limiting 1d · tests and monitoring 2d. And start the Rakuten Viber commercial application in week one: bots have been commercial-terms-only since 2024-02-05, partner onboarding plus Viber's own 2–3 day review runs 2–4 weeks, and it becomes the critical path otherwise.

Things that sound right but aren't (channels). "Meta publishes a Node SDK" — archived since June 2023. "Telethon is the Python Telegram library" — archived, moved to Codeberg, and it's a user-account library anyway. "tdlib is BSL" — it's the Boost Software License, permissive and OSI-approved; rejecting it on the abbreviation is a mistake in the other direction. "Botpress is MIT and self-hostable" — the runtime is proprietary cloud; the self-hostable v12 is AGPL and last pushed 2025-04-10. "Evolution API is Apache-2.0" — it's Apache with additional conditions including a mandatory in-product attribution notice. And a process finding worth internalising: GitHub's pushed_at is not a liveness signal — it counts branch and tag pushes. It overstated viber-bot-python by 3+ years, viberroo by ~18 months and Papercups by ~21 months in this corpus alone. Always check the default branch's last commit.


6. Product grounding, RAG & recommenders

ProjectWhat it isLicenceMaturityFitVerdict
CornacRecsys library covering next-basket/replenishment (TIFUKNN, DNNTSP, Beacon, UPCF, FPMC), sequential (SASRec/BERT4Rec/GRU4Rec) and aspect-level explainable models (EFM, MTER, TriRank, ComparER, HypAR), plus a serving layerApache-2.0, academic (SMU), no open-core1,055★, v3.0.1 2026-09-14 — five releases in under four months5Systematically under-recommended because it isn't famous. Next-basket targets repeat purchase, which is where e-commerce LTV lives; the explainable family gives the bot a truthful "why" derived from review text rather than one the LLM invented. Highest-value output isn't a top-N list — it's a replenishment-due date per (user, SKU).
QdrantVector DB with a first-class Recommendation API (positive/negative example IDs) and Discovery/Context APIApache-2.0, engine genuinely not feature-gated34,569★, v1.19.1 2026-09-04, dev commits 2026-09-155The OSS answer to critiquing: "like that jacket but warmer and under €200" is one context pair + a payload range filter. Because Recommend accepts stored point IDs, purchase history becomes the positive set and returns the negative set — and that accumulating example set is the durable cross-conversation preference model, with no retraining. Raise ef above 32 (default 16 is too low for Recommend/Discovery).
implicitFast ALS / BPR / item-item kNN on implicit feedbackMIT3,821★, commit 2026-05-08, v0.7.3 was the first release since v0.7.2 in 2023-094Honest baseline: ALS trains in under a minute on this data, no GPU. Its co-purchase item-item similarity is the cheapest legitimate source of genuine complements — unlike vector similarity, which detects substitutes. Episodic single-maintainer; MIT so forkable.
GLiNER2Schema-driven zero-shot extraction (entities, classification, structured records, relations) on CPUApache-2.0 (repo and fastino/gliner2-multi-v1 / gliner2.5-multi-v1 checkpoints)1,859★, push 2026-08-24, 100+ commits since March; 194k + 144k monthly downloads5Turns "what the bot listens for" into a JSON config you change weekly. Declare commerce slots explicitly (product_category, attribute, constraint, budget, recipient, occasion, deadline, objection). recipient is the gift-vs-self discriminator, which matters because gift purchases corrupt every replenishment estimate. Language tags are thin (6 and 2) despite "multi" — measure Ukrainian, don't assume.
TypesenseTypo-tolerant search with per-query facet counts, filtering, hybrid, curationGPL-3.0 (daemon boundary; clients Apache-2.0)26,558★ — main last committed 2024-09-28; real work is on version branches (v31 commits 2026-09-15), v30.2 released 2026-04-194Facet counts are the sleeper feature: preference elicitation has no maintained OSS library, but it has a cheap algorithm — ask about the facet whose value distribution most evenly splits the current candidate set — and Typesense hands you that distribution free on every query. Typo tolerance matters disproportionately on thumb-typed messengers.
MeilisearchRust search, facets, hybrid, experimental Dynamic Search RulesMIT AND BUSL-1.1enterprise_edition modules (sharding, multi-node network/federated, S3 snapshots) are BUSL, non-production without an agreement, converting to MIT after 4 years59,293★, v1.53.2 2026-09-07, commit 2026-09-15 — fortnightly releases3The non-GPL alternative — but it is now open-core, which most write-ups still miss. Every feature you need single-node is MIT; scale-out is a commercial conversation. Dynamic Search Rules is pin-only and query-triggered — it cannot read user state, so it can't carry eligibility logic.
GorseSelf-hosted recommender service (not a library): REST + dashboard, multi-source recall, RecFlow visual pipeline with an LLM reranker node and a fallback nodeApache-2.0, no paid tier9,828★, v0.5.11 2026-07-14, commit 2026-08-28. Confirmed at source: common/reranker/client.go; v0.5.3 notes "Support large language model based ranker", v0.5.5 "Replace LLM ranker with reranker"4Only OSS recommender that is a product, and the only one shipping an LLM reranker with graceful degradation already wired. On messaging a two-second stall reads as being ignored, so the fallback node is the difference between alive and broken. logics/chat.go exists — read it before building a parallel layer. Bus factor: 9.8k stars conceal a largely one-maintainer project.
MetarankLow-code LTR: auto feature generation, LambdaMART, unbiased LTR with position-bias correction, ~10–20ms servingApache-2.02,441★, 0.8.1 2026-09-07 — but 0.7.10 (2024-07) → 0.7.11 (2025-06) → 0.8.0 (2026-08): two years of near-dormancy before the recent burst3Position-bias correction is the standard defence against the feedback loop that collapses a 5k-SKU catalogue to ~80 SKUs — worse for a bot than a website, because the same customer watches the narrowing. Adopt the event schema on day one even if the ranker is a phase-2 project; retrofitting position-annotated logs is far harder than emitting them.
LettuceDetectSpan-level groundedness/hallucination detection for RAGMIT (repo, translated RAGTruth datasets, and checkpoints)606★, commit 2026-09-07, roadmap naming 0.2.4/0.3.05Closes the anti-hallucination hole for Ukrainian, and publishes the whole recipe including a fidelity audit: tag hallucinated spans with <hal>, translate preserving tags (gemma-3-27b-it under vLLM, ~12h/language on one A100), fine-tune token classification (AdamW, lr 1e-5, 6 epochs, batch 8, 4096-token cap). Polish F1 73.05 vs GPT-4.1-mini's 59.27. German 300-example manual audit: 74.95 → 71.79, so translate-then-train costs ~3 F1. Span-level means you strip one clause instead of rerolling the whole reply.
mmBERT-baseMultilingual ModernBERT encoder, 1,811 language tags including ukr and rusMIT (GitHub repo JHU-CLSP/mmBERT has no licence file — cite the model card)375,474 downloads/mo, last modified 2025-10-075The base for every custom Slavic classifier you must build, because off-the-shelf stops at Polish. EuroBERT does not cover Ukrainian (verified 15-language list has ru, pl, no uk), so: Russian → EuroBERT-610m, Ukrainian → mmBERT; or mmBERT for both and eat a few F1 on Russian.
DoclingLayout-aware PDF/DOCX/HTML/scan → structured output with real table extractionMIT, LF AI & Data66,437★, v2.127.0 2026-09-14 — four releases in three weeks4Sits before extraction on the critical path and is usually skipped: feed a naive PDF-to-text dump into GLiNER2 and spec tables become word soup, losing "5 ATM" from its row.
langextractLLM-backed structured extraction with character-offset source groundingApache-2.038,581★, v1.7.0 2026-09-134The peer to GLiNER2. Trade-off: GLiNER2 is local CPU (free per call, weaker on messy prose, Ukrainian unmeasured); LangExtract is API-backed (per-document cost, stronger on messy supplier PDFs, multilingual, source offsets you can audit). Benchmark on 100 SKUs.
FlagEmbeddingBGE embeddings + cross-encoder rerankersMIT12,161★, v1.4.2 2026-08-244The missing piece everyone's advice points at without naming: BGE-reranker runs locally in single-digit ms and plugs straight into Gorse's Jina-compatible reranker node. Try it before listwise LLM reranking. BGE-M3 also gives multilingual embeddings.
RecToolsConsistent recsys API; implicit models, EASE, SASRec/BERT4Rec/UniSRec, and a 10–15× faster LightFM wrapperApache-2.0457★, v0.19.0 2026-06-12, main commit 2026-06-133Pragmatic route to hybrid MF with item features — LightFM's sweet spot, without LightFM's corpse. But it wraps an unmaintained C extension (upstream LightFM's last master commit is 2023-04-30; the maintained fork feldlime/rectools-lightfm is 3★, last shipped 2024-12-08). Russian-telecom origin may be a procurement consideration.
RankLLMListwise LLM reranking (RankZephyr, RankVicuna, RankGPT, MonoT5…)Apache-2.0656★, commit 2026-09-07, zero GitHub releases/tags — version via PyPI (rank-llm 0.25.7)3Reranking is the correct and only safe place to put an LLM in the recommendation path: it permutes a fixed candidate list, so it structurally cannot hallucinate a product. Models are trained on document relevance, not purchase propensity. Rerank 20–30 candidates, not 50.
HaystackExplicit pipeline graphs for retrieval + agentsApache-2.0 — watch: most recent commit documents "Haystack Enterprise Platform"26,512★, v3.1.1 2026-09-03, commit 2026-09-15, only 142 open issues3Explicitness is the feature here: validation against retrieved catalog data becomes a visible, unit-testable node. Split retrieval into three branches — structured attribute lookup, semantic RAG over reviews/how-tos, recommender call. That separation is what prevents spec hallucination.
RAGFlowBatteries-included RAG with document parsing and citationsApache-2.090,729★, v0.27.2 2026-09-10, commit 2026-09-153The deploy-rather-than-build option for the reviews/policy RAG branch.
pgvectorVector search in PostgresPostgreSQL Licence23,020★, commit 2026-09-08, only 17 open issues4The phase-one alternative to Qdrant: zero new infrastructure, transactional consistency with product and customer tables, and the positive/negative arithmetic Qdrant gives free is ~40 lines of SQL. You lose Discovery/Context critiquing.
Microsoft Recommenders~30 algorithms + best-practice notebooksMIT, Linux Foundation21,892★, commit 2026-08-17 — but last release 1.2.1 was 2024-12-243A curriculum, not a runtime. Read the evaluation notebooks first: time-based splitting is the discipline that prevents shipping a model that looked great on a random split. SAR (interpretable item similarity with explicit time decay) is the underrated baseline. Ignore the Azure/Spark deployment notebooks.
RecBole94 algorithms under one config-driven APIMIT4,554★, last commit AND last release both 2025-02-24 — 19 months, pushed_at matches, so nothing is secretly alive2Use once, for one week, in a throwaway venv: SASRec vs BERT4Rec vs GRU4Rec vs personal-top-frequency vs session-kNN on a time-based split of your own data. Expect the baseline to win.
U-NEED7,698 annotated e-commerce pre-sales dialogues, per-utterance action/attribute/product labelsMIT code; dataset is access-gated, research-only22★, 2 forks, last commit 2023-02-252The only public data genuinely shaped like this product. Use it to answer two design questions — how many clarifying questions do real salespeople ask before recommending, and which attributes first — then stop. Chinese/Taobao norms; weak baselines (~36% F1) are themselves a useful expectation-setter.
Transformers4RecSession-based recommendation with HuggingFace transformersApache-2.01,281★ — last release v23.12.00 on 2024-01-11; all five commits in the last six months are fixes, two explicitly fix(security)1Explicit warning. Wrong on two counts: unmaintained (a security-maintenance signature, invisible to star counts and pushed_at), and the wrong model class — session-based assumes short anonymous sessions with no cross-session identity, which is the exact opposite of your problem.
CRSLabConversational recsys toolkit: recommender + conversation + policy modules, 6 datasetsMIT556★, last commit 2023-05-16; pushed_at reads 2024-04-12, overstating health by 11 months1Retained despite being dead, because its death is the finding: conversational-recsys OSS died right as LLMs made the problem tractable. Its three-module decomposition is the right mental model; elicitation and critiquing must be built.
LightFMHybrid MF with user/item featuresMIT5,100★ — master last commit 2023-04-30, release 1.17 2023-03-20, pushed_at reads 2024-07-241The most dangerous corpse here: theoretically the perfect model for a 5k-SKU cold-start catalogue, recommended in nearly every pre-2024 article, and a C extension — so Python/NumPy upgrades break it irreparably from outside.

How to choose here. At 5k SKUs / 50k customers the binding constraint is data volume, not model class — Ludewig & Jannach's repeated studies found heuristic session-kNN matches or beats GRU4Rec/NARM/STAMP/SR-GNN across most configurations, and the Next-Basket Reality Check found most neural gains come from the repetition component rather than genuine exploration. So budget one week to prove a neural model beats a time-decayed item-similarity baseline on a time-based holdout, expect it not to, and be pleased. Two architectural rules matter more than any model choice. First: vector similarity is a substitute detector, not a complement detector — embedding "more like this" recommends a rival jacket to someone who just bought a jacket, which reads as not listening; build two item-item matrices (co-purchase → complements, co-view-without-co-purchase → substitutes) and serve only complements post-purchase. Second: free-text RAG is the wrong primitive for specs — embedding similarity cannot reliably distinguish "waterproof to 5 ATM" from "3 ATM", and the business consequence is opposite. Extract specs once into typed attributes (Docling → GLiNER2/LangExtract), store them filterable, and let the model read fields. Reserve RAG for reviews, how-tos and returns policy.

Things that sound right but aren't (recsys/grounding). "Use Transformers4Rec for session-based recommendation" — no release since January 2024, all recent commits are security fixes, and it's the wrong model class anyway. "LightFM is perfect for small catalogues with cold-start" — master dead since April 2023, and it's a C extension. "Typesense looks abandoned" — main is stale; v31 ships weekly. "R2R for RAG" — ~8.0k★ but last commit 2025-11-07. "Superlinked for recsys-aware embeddings" — the Python framework is deprecated in favour of an inference server. "Anti-hallucination is handled, we have a guard model" — Granite Guardian 4.1's own card says "The model is only trained and tested on English data", and HHEM-2.1-Open is language: en while the paid HHEM-2.3 covers Russian but not Ukrainian at any price. A guard that silently scores out-of-distribution input is worse than no guard: it paints a green dashboard while the bot invents specs in Ukrainian.


7. Offer-timing models — CLV/BTYD, survival, uplift, bandits

ProjectWhat it isLicenceMaturityFitVerdict
PyMC-MarketingBayesian CLV: BG/NBD, Pareto/NBD, MBG/NBD, BG/BB, Shifted-BG, Gamma-Gamma — full posteriorsApache-2.01,259★, commit 2026-09-15; 442 open issues5The only maintained BTYD library — lifetimes archived 2024-06-28 and btyd archived 2024-05-14, and both READMEs redirect here. Posterior width is what should stop the bot pitching a customer it barely knows. Data floor: needs ≥300 customers with frequency ≥1 and ≥100 with ≥2; below that (a,b) are prior-dominated. Run it monthly as a prior-vs-posterior diagnostic — the month they separate is your go/no-go for the whole quantitative roadmap.
lifelinesSurvival analysis: Kaplan-Meier, Cox PH, parametric AFT (Weibull/LogNormal/LogLogistic), Schoenfeld testMIT2,609★, commit 2026-03-07, v0.30.3 2026-03-05 — ~6 months quiet, 301 open issues, single maintainer4The correct cold-start substitute for BG/NBD. Treats not-yet-repurchased customers as censored, not negative — the single most common technical error in time-to-next-purchase work, and the one that makes a bot go quiet on its newest relationships. proportional_hazard_test is present; actually run it, seasonal retail violates PH routinely.
XGBoost survival:aftInterval-censored AFT objective (label_lower_bound/label_upper_bound, aft_loss_distribution)Apache-2.028,765★, commit 2026-09-15; AFT in-tree since 1.2.0 (2020)4The Apache-2.0 answer to everything scikit-survival's boosting offers — and the same XGBoost you already run for propensity, so no new dependency, no new licence. Trees extrapolate poorly past your observed time range, which matters for slow-cycle products.
CausalMLUplift/CATE: S/T/X/R-learners, uplift forests, causal forests, Qini/AUUC, synthetic generators with known ground truthApache-2.0 (LICENSE text read; API says NOASSERTION)5,999★, commit 2026-08-20, only 21 open issues4Answers the question that actually matters: not "will this customer buy" but "does my message change whether they buy". Separates Persuadables / Sure Things / Lost Causes / Do-Not-Disturbs — and that last quadrant is exactly the customer who must never be pitched. Needs randomised treatment; you cannot compute honest uplift from logs where the bot already chose whom to message.
EconMLDML, DR-learners, orthogonal forests, IV — and DRPolicyTree/DRPolicyForestMIT (LICENSE text; API says NOASSERTION)4,789★, default-branch commit 2026-07-31 (pushed_at 2026-09-14 is a non-default ref), v0.17.0 2026-08-04, 419 open issues4DRPolicyTree emits a small human-readable tree — "offer only if days_since_purchase > 45 AND p_alive > 0.7 AND no offer in 21 days" — directly transcribable into the readiness rules. Far more robust at small n than individual CATE: a depth-3 tree estimates a handful of group averages. If it returns a single root node, the honest conclusion is "we cannot yet personalise", which is itself worth knowing.
contextualbanditsLinUCB/LinTS/Epsilon-Greedy/SoftMax/Adaptive-Greedy plus off-policy evaluation (Rejection Sampling, DR, NCIS)BSD-2-Clause839★, only 2 open issues, commit 2026-06-28, PyPI 0.3.30 2026-02-225The only maintained library bundling policies and OPE — which is how you test a more aggressive offer policy against logged history without messaging anyone. Correction to common belief: it is not pure Python — Cython since v0.2.0, and setup.py injects -march=native, so a CI-built wheel can crash on a different CPU. Set DONT_SET_MARCH=1.
bayesianbanditsConjugate-prior Bayesian bandits on sklearn/scipy; ContextualAgent, Thompson/UCB/IDS/EXP3AMIT148★, v1.4.0 2026-08-01, commit 2026-09-134Closed-form conjugate updates mean an agent's state is a handful of numbers that serialise into a Postgres row per customer — exactly what a bot resuming a months-old relationship needs, where batch-refit libraries force you to replay history. Pure Python, no compiler, no -march trap. No OPE — pair with contextualbandits.
Vowpal Wabbit--cb_explore_adf contextual bandits with varying action sets, IPS/DR built inBSD-3-Clause (LICENSE read; API says NOASSERTION)8,718★, 1 open issue, 9.11.4 2026-09-143ADF handles a changing action set natively — and your sensible-offer set genuinely changes per conversation with stock, channel eligibility and prior ownership. Overkill below thousands of decisions/day, and its failures are silent: VW minimises cost, so a conversion is a negative cost and a sign error trains a confidently bad policy with no exception raised.
RiverOnline/incremental ML: bandits, streaming models, concept-drift detectors (ADWIN, Page-Hinkley)BSD-3-Clause6,095★, 0.26.1 2026-08-21, commit 2026-09-144Underrated second role: a months-long relationship bot must notice when a customer changes regime — new life stage, seasonality shift, a bad support experience. ADWIN on an engagement signal is a principled "reset my assumptions" trigger, and nothing else in this layer covers it. Every other library here is batch and implicitly assumes stationarity.
FeastFeature store: define once, materialise online, point-in-time-correct training joinsApache-2.0, LF AI & Data, no ee/7,272★, v0.66.0 2026-08-21, commit 2026-09-15, 440 open issues2Right shape, wrong first purchase. Below ~20 features a customer_features materialised view with an as_of column plus one get_features(customer_id) function gives the identical correctness guarantee for a day's work. Adopt the discipline now, the software later — stamp as_of on every feature row from day one, because no feature store can reconstruct what you knew at decision time if it was never written down.
Open Bandit PipelineThe canonical OPE library: IPS, SNIPS, DM, DR, Switch-DR + the ZOZO datasetApache-2.0 (dataset has separate terms)709★ — default-branch last commit 2022-11-05, last PyPI 0.5.7 2023-04-14; the 2024-06 pushed_at is dependabot branches2Nearly four years dead — vendor the ~200 lines of estimator math, don't pip-install. But the docs remain the best specification of your logging schema, and the day-one action is one line: log (timestamp, customer_id, context, action, **action_probability**, reward, reward_horizon) and inject 5–10% epsilon randomisation among safe actions. Without propensities, OPE is mathematically impossible, forever, and teams discover this in month nine.
InterpretML (EBM)Glass-box GAM with pairwise interactions and monotone_constraintsMIT, Microsoft Research6,943★, commit 2026-09-14, 42 open issues3The right model class for readiness because of governance: you can clip monotonicity (more silence must never raise readiness), and you can see when the model has learned something repellent, like that distressed customers convert better. Data floor: ~2,000–3,000 labelled offer events — at 2,000 customers × 2 offers/yr × 20% acceptance that's a three-year wait. Its hand-editable shape functions let you encode the month-one heuristic as an EBM and let data overwrite it gradually.
Duolingo Sleeping/Recovering BanditKDD 2020: Recovering Difference Softmax — recovering arms (reward decays after a pull, recovers with elapsed time) and sleeping arms (conditional eligibility)Paper free; replication dataset at DOI 10.7910/DVN/23ZWVI is CC BY-NC 4.0 — non-commercial; the only public reimplementation (jakemaz66, 10★) has no LICENSE = all rights reservedPeer-reviewed; +0.5% DAU, +2% new-user retention reported5Closest published match to this exact problem, and the formal model of message fatigue the frequency-capping literature never supplies. "Recovering" is "the customer must be rested before the next offer" as maths — the algorithm learns frequency caps instead of you guessing them. "Sleeping" maps onto WhatsApp's 24h window. Implement from the paper; touch neither the dataset nor the reimplementation.
MABWiserApproachable bandits with neighbourhood policies (Clusters, KNearest, Radius, TreeBandit)Apache-2.0292★, last commit 2024-08-30, 0 open issues1The neighbourhood policies are a genuine small-data trick (pool rewards from similar customers rather than fitting a global contextual model). But two years dormant with no OPE at all, and contextualbandits is maintained, permissive and strictly more capable.
Mab2RecBandit-based recommender on MABWiser + Jurity fairness metricsApache-2.0 (LICENSE read; API reports none)159★, commit 2026-07-10 — but last PyPI release 1.3.1 on 2024-09-042Inverted pyramid: maintained shell over a dormant core, and nothing shipped to users in two years. Jurity fairness evaluation is genuinely useful (is the bot over-pitching certain segments?) but inherits MABWiser's missing OPE.
scikit-upliftSoloModel / ClassTransformation / TwoModels + uplift@k, AUUC, Qini plotsMIT814★ — both master and dev last committed 2022-08-11, i.e. four years; the docs still claim it "is being actively maintained"1Best plain-language explanation of the Persuadables/Do-Not-Disturb framing in OSS. Read the docs, implement in CausalML. A project whose README lies about its own maintenance is the reason to check commit history over documentation.
SCOPE-RLOffline RL + OPE with CVaR/risk metrics for sequential policiesApache-2.0143★, default-branch last commit 2023-12-01, PyPI 0.2.1 2023-07-302A months-long relationship is formally an MDP, not a bandit — today's pushy offer reduces next quarter's LTV. Dead code; adopt the data model: log full trajectories (state, action, propensity, reward, next_state). Bandit logs are a strict subset, so this costs nothing now.
d3rlpyOffline deep RL: CQL, IQL, BCQ, AWAC, TD3+BC, Decision TransformerMIT1,685★, commit 2025-09-10 (~12 mo), v2.8.1 2025-03-02 (~18 mo)2Premature by years. Its present value is forcing the right question: what is the reward? Settle on a long-horizon, margin-weighted reward with explicit penalties for unsubscribes, blocks and negative sentiment before building any bandit — that definition propagates through everything else.
CobaBenchmarking framework for contextual bandit algorithmsBSD-3-Clause53★, 0 open issues, 8.1.0 2026-08-302Small but genuinely maintained, under the VW org. The pragmatic substitute is a replay simulator over your own logs — which you need for OPE anyway, and which reflects your real customer dynamics better than any generic benchmark.
Criteo-UPLIFT v213.98M rows, 12 anonymised features, treatment + exposure + visit/conversionCC-BY-NC-SA-4.0 — non-commercialCanonical uplift benchmark1Sharpest and most easily-missed trap in the layer: it feels like innocuous sample data. Conversion rate ~0.29% is itself a blunt lesson in how much data uplift needs. Use CausalML's synthetic generators instead — they have known ground-truth effects, which is strictly better for validating an estimator.

How to choose here. Decompose "only offer when prepared" into four separable questions and pick per question: who is worth attention (BTYD → PyMC-Marketing) · when is the natural need window (survival → lifelines, then XGBoost-AFT) · would my message change anything (uplift → CausalML/EconML) · which message, learned online (bandits + OPE → contextualbandits/bayesianbandits). Two disciplines dominate the tooling. Individual-level uplift is statistically out of reach at this scale and recommending it would be malpractice — uplift estimates a difference between two noisy conditional expectations, needing roughly 10–100× the data of a propensity model, with reliable per-customer CATE wanting >10k treated plus >10k control; the correct substitute is a permanent randomised global holdout (suppress ~10% of intended offers) measuring one average incrementality number. And log action propensities from day one — it is one line per decision, it is the only thing here that is truly unrecoverable, and it is what lets every future policy be tested against a year of real logs without messaging a single customer.

Things that sound right but aren't (offer timing). "Use lifetimes for CLV" — archived 2024-06-28; so is btyd. "scikit-uplift for uplift" — four years dead, and its own docs claim otherwise. "Open Bandit Pipeline is the OPE library" — nearly four years dead; vendor the math. "Optimise the score against offer acceptance" — the model will learn that manufactured urgency, flattery and pitching into emotional vulnerability all raise acceptance, and you will have built a pushy bot with a trust-shaped score as cover. The label must be compound and long-horizon: accepted AND no complaint/unsubscribe/block/churn in 60–90 days AND next-period order value not down. "A deterministic gate is fine, we'll add exploration later" — you cannot; deterministic propensities are 0 or 1, importance weights are undefined or infinite-variance, and OPE is foreclosed on that data permanently.


8. Signal extraction — reading the customer

ProjectWhat it isLicenceMaturityFitVerdict
GLiNER2 / GLiNERSchema-driven zero-shot entity + structured extractionApache-2.0 (both, and both checkpoints)GLiNER2 1,859★ push 2026-08-24; GLiNER 3,650★ push 2026-09-085Need/fit slot filling and a second "person" schema (occupation, family, pet, skin type, life event) whose distinct-slot count over months is your self-disclosure-depth relationship feature. Slot coverage is a directly defensible gate: you have no business proposing a product until you know what it is for, who it is for, and roughly the budget.
GLiClassZero/few-shot classification in one forward pass over a candidate label listApache-2.0; gliclass-multilang-mini Apache-2.0 with 20 language tags245★, v0.1.20 2026-07-21, 66 commits since March5Single-pass is what makes a 15-label per-turn panel affordable — an NLI zero-shot model costs 15 passes for the same panel. The only clean-licensed zero-shot classifier here that plausibly spans uk/ru/pl. 245★ = real bus-factor risk; pin and vendor.
SetFitFew-shot classification: contrastive Sentence-Transformer tuning + a light head, ~8–64 examples/classApache-2.02,807★, push 2026-09-08, PyPI 1.2.0 2026-09-04 (only 8 commits in six months — alive but glacial)5Your labels are bespoke in any language: "ready for upsell", "venting about a previous order", "price-sensitive", "browsing for someone else", "trust broken". No pretrained model ships those. SetFit gets a working classifier from 30–50 examples/class — one Ukrainian-speaking person, one afternoon. Share one encoder forward pass across 5–6 heads or the panel costs more than the LLM reply.
SnorkelWeak supervision: many noisy labelling functions + a generative label model that learns each one's accuracyApache-2.06,008★, push 2026-09-14 (maintained, low velocity)4Better month-one answer than SetFit for Ukrainian, because the SPIN gate is already a set of rules — encode them as uk/ru labelling functions, add an LLM as one more noisy voter, combine. Labels for your whole history in week two with zero hand-labelling. Trap: correlated uk/ru keyword functions violate conditional independence and silently inflate confidence.
ConvoKitCoordination (linguistic accommodation), PolitenessStrategies, TalkTimeSharing, PromptTypes, Forecaster (CRAFT + LLM backbones)MIT (bundled corpora have per-corpus licences)649★, v4.1.2 2026-06-28, push 2026-07-01 — only 7 commits since March, and those are docs/demo fixes4Only mature OSS prior art for measuring the relationship rather than the transaction. TalkTimeSharing is the cleanest "am I lecturing or advising?" metric. Forecaster is structurally the model you want — swap derailment for "offer accepted and no complaint within 60 days". Hard limit: politeness collections exist only for English (politeness_api, politeness_local) and Chinese (politeness_cscw_zh). Running English markers over Ukrainian returns a plausible near-zero for every message — silent, and it will make a relationship-stage model conclude every customer is equally distant forever.
PresidioPII detection/redaction/pseudonymisation with pluggable NLP enginesMIT10,870★, 2.2.364 2026-07-22, commit 2026-09-15. Repo moved from microsoft/presidio5The eastern gap is far smaller than reported. phone_recognizer.py wraps phonenumbers and defaults to 8 regions with the in-code note "For all regions, use phonenumbers.SUPPORTED_REGIONS" — passing supported_regions=('UA','RU','RO','MD','PL') is a one-line fix. Names/addresses/orgs come from spaCy. Only national IDs need writing (РНОКПП, Ukrainian ID-card, СНИЛС, ИНН, CNP) at ~40–60 lines each: 1–2 engineer-days for the entire "PII gap". country_specific/ has 18 countries and Poland contains exactly one file (PESEL) — nothing further east.
spaCy uk_core_news / ru_core_newsUkrainian & Russian pipelines: tokenisation, POS, lemmatisation, NERMIT end-to-end (uk trained on MIT-licensed Ukr-Synth)v3.8.0; NER F1 0.872 (uk_sm) / 0.944 (ru_sm); sm/md/lg/trf for uk4Two jobs: makes Presidio work for uk/ru, and supplies lemmatisation, which is non-optional for any Slavic politeness lexicon — raw n-gram matching misses most inflected forms. Cleanest licence chain in this layer.
lxyuan/distilbert-base-multilingual-cased-sentiments-student3-class multilingual sentiment, 12 languagesapache-2.01,103,473 dl/mo, modified 2025-03-034The one model that is simultaneously multilingual, fast enough for a <50ms per-turn budget, and unambiguously commercial. Its value is the comparison: every more accurate alternative is legally unusable — see the trap note.
SamLowe/roberta-base-go_emotionsMulti-label emotion over 28 GoEmotions classesMIT; ONNX build available553,732 dl/mo, modified 2026-05-134Polarity is too coarse. Disappointment is the quiet killer a 3-class model scores as neutral, because disappointed customers stay polite. Multi-label matters structurally: "gratitude + disappointment" in one turn is common and is exactly the state where pitching does most damage. English only — the multilingual substitutes are both unusable (below).
yangheng/deberta-v3-base-absa-v1.1Aspect-based sentiment polarityMIT, 91,613 dl/mo, modified 2025-08-12Checkpoint alive; framework PyABSA is not — default v2 branch last commit 2025-08-11, last release v2.1.12 2023-03-21; sibling ABSADatasets last commit 2023-04-242Use the checkpoint standalone via transformers, never the framework. Price resistance is an aspect-level phenomenon: "love the design, bit pricey though" is net-positive at document level and a hard price-resistance veto at aspect level.
textdetox/xlmr-large-toxicity-classifier-v2Binary toxicity, 15 languagesopenrail++ — use-restricted RAIL, not OSI, flows down to derivatives3,066 dl/mo, modified 2025-12-085Ukrainian F1 0.96, Russian 0.9525, English 0.9225 — uk/ru are its two best languages. The "no Slavic moderation tooling" premise is simply wrong. Binary only; trained on social-media toxicity, so an angry-but-clean delivery complaint scores non-toxic.
ukr-detect/ukr-emotions-classifierUkrainian multi-label emotion (EmoBench-UA)openrail++785 dl/mo, modified 2026-08-05 (genuinely maintained)3Partially refutes "no Ukrainian affect classifier" — and confirms its practical consequence. Per-class F1: Fear 0.81, None 0.81, Joy 0.73, Sadness 0.69, Surprise 0.60, Disgust 0.35, Anger 0.31 (99 test examples). You cannot gate "suppress the upsell, they're angry" on a 0.31-F1 anger detector. Use deterministic proxies instead.
adaptive-classifier + escalation-detectionRuntime-extensible classifier with ready-made escalation and customer-intent headsApache-2.0 (library and heads)570★, last commit and last release both 2025-10-07 (~11 mo); head has 665 dl/mo, 2 likes3Escalation probability is the most important veto input — pitching to someone whose parcel is lost destroys more LTV than ten good recommendations create, and unlike a mistimed offer it is not recoverable. Off-the-shelf means the veto ships day one. Validate on your own data first; 2 likes means nobody is battle-testing it.
MoritzLaurer/bge-m3-zeroshot-v2.0-cNLI zero-shot on a BGE-M3 multilingual backbone; -c = commercially-friendly training data onlyMIT1,346 dl/mo (non--c variant: 121,579), modified April 20244The multilingual member of the family that most write-ups miss — the deberta-v3 variants are English-strong, and the card itself routes multilingual users here. The author's own warning on the non--c variants: they "include data with non-commercial licenses" and "Legal opinions diverge if this training data affects the license of the trained model." Take -c, eat the F1.
mmBERT-baseMultilingual encoder, ukr + rus among 1,811 tagsMIT375,474 dl/mo5The base for every bespoke uk/ru head. Do not reach for EuroBERT as the multilingual default — it covers ru but not uk, silently dropping half your audience.
ai4privacyMultilingual PII corpora (flagship pii-masking-openpii-1.5m: ~1.64M entries, 30 language classes)Flagship CC-BY-4.0 (commercial with attribution); other artefacts vary and the org page shows nothing — check eachDatasets active; the three Llama-based anonymiser models are stale (Mar–Jun 2025, 72/200/1.29k downloads)3Use the data, not the models — the models are Llama-derived, dragging the Llama Community Licence into your stack. Largely synthetic, so expect real distribution shift against colloquial Viber Ukrainian; budget a few hundred hand-labelled real messages as a holdout.
facebook/fasttext-language-identificationThe reflex choice for language routingCC-BY-NC-4.0 — NON-COMMERCIAL440,244 dl/mo, modified 2023-06-091The sharpest licensing trap in the layer precisely because it is ubiquitous and nobody checks. Use cis-lmu/glotlid (apache-2.0-plus-notices, covers uk/ru, 248,040 dl/mo) — or better, train a char-n-gram uk/ru/MIXED classifier on your own traffic, since a general 200-language model cannot express the mixed class your routing actually needs.
pymorphy3Slavic morphological analysis and generation (name vocative, adjective agreement)Contradictory for Ukrainian: pymorphy3-dicts-uk declares license: "GPLv3 License" in its PyPI info field while carrying the trove classifier License :: OSI Approved :: MIT License. Core and -dicts-ru are unambiguously MITuk dictionary version string encodes a 2022 build2Prefer spaCy uk_core_news_lg for lemmatisation — MIT end-to-end, sidesteps the question. Reach for pymorphy3 only for generation (getting a Ukrainian first name's vocative right in a greeting is a small, visible courtesy).
lang-ukUkrainian NLP community: ner-uk, tokenize-uk, word-stress, ukrainian-llm-leaderboardMixed and often absenttokenize-uk MIT, ukrainian-word-stress MIT, but ner-uk (94★, the most valuable gold dataset) has NO LICENCE, as do ukrainian-llm-leaderboard, wordnet, vecsSeveral repos refreshed 2026-07-30; leaderboard pushed 2026-08-243Your route to Ukrainian gold data, benchmarks and a fluent annotator. Email the maintainers about ner-uk's licence before training anything commercial on it — an unlicensed dataset is default exclusive copyright.

How to choose here. Build three scores on three timescales and gate them conjunctively, never one additive number: R (relationship capital, decays with a ~45-day half-life of silence, floored at 0.4× peak), N (need/fit evidence, resets per need-episode, with slot coverage and price-band fit as hard sub-gates), M (moment, per-turn, sub-50ms). The heaviest single R feature is advice-acceptance rate — the fraction of bot recommendations followed within two turns — because that is the operational definition of advisor status. Percentile-normalise every engagement feature within channel (Telegram gives no read receipts at all, so a pooled model makes every Telegram customer look systematically colder and they will never receive an offer) and mark unavailable features missing, never neutral. Route by cost: GLiClass as the cheap first pass on every turn, promote only low-confidence turns to a SetFit head or an LLM judge, and push ConvoKit's Coordination/TalkTimeSharing/Politeness to a batch job at session close.

Things that sound right but aren't (signals). pysentimiento — its LICENSE says "for non-commercial use and scientific research purposes only"; disqualified despite being the standard multilingual-social-NLP recommendation. tabularisai/multilingual-sentiment-analysis (143k dl/mo, 23 languages) and tabularisai/multilingual-emotion-classificationCC-BY-NC-4.0. cardiffnlp/twitter-xlm-roberta-base-sentiment1,020,636 downloads/month and NO LICENSE TAG AT ALL, meaning no rights are granted by default. MilaNLProc/xlm-emo-t — 485,865 dl/mo, no licence, frozen since 2023-03-27. facebookresearch/fastTextarchived 2024-03-22. snips-nlu — last push 2023-05-22. And the one that will actually bite: running ConvoKit's English politeness lexicon over Ukrainian fails silently — it returns a plausible near-zero for every message rather than an error, and an unvalidated rapport score gating revenue decisions is worse than no score because it looks like evidence.


9. Guardrails, manipulation limits & PII

ProjectWhat it isLicenceMaturityFitVerdict
Open Policy AgentDeterministic policy decisions with structured reason codes and decision logsApache-2.0, CNCF graduated12,234★, commit 2026-09-155The only component that can guarantee the never-list. For the Article 5(1)(b) exposure specifically — never time an offer to a disclosed vulnerability — a Rego rule with unit tests is a far stronger defence than prompt text, because it is falsifiable and auditable.
OPALStreams live policy and data into OPA in real timeApache-2.05,508★, commit 2026-09-144Fixes OPA's one operational weakness: if the "complaint open" flag is 30 seconds stale you still pitch into an open complaint. Deferred at month one; the fix for a real bug later.
ParlantGuideline matching + strict-mode canned responsesApache-2.018,287★, releases stalled since 2026-04-284Strict mode is the strongest anti-eagerness mechanism in OSS, and responses referencing tool-supplied fields are never selected unless the tool actually ran — a structural guarantee, unlike every guardrail that validates after generation.
NeMo GuardrailsInput / output / dialog / retrieval / execution rails; v0.24.0 adds RailOutcome and standalone check()Apache-2.0 (SPDX header)7,124★, v0.24.0 2026-08-264Retrieval rails matter more here than in a normal bot because your memory persists: an instruction planted in a review in week one can steer the bot in week six. check() also lets you re-run the rail set offline over stored conversations — which is how you answer "prove this never happened". Trap: the best companion rails are NVIDIA NemoGuard models under the NVIDIA Open Model Licence + Llama 3.1 Community Licence, neither OSI.
Granite Guardian 4.1-8BGuard model: harm, bias, jailbreak, RAG groundedness, function-call hallucination, and Bring Your Own CriteriaApache-2.0 on both repo and weights — genuinely rare14,304 dl/mo, modified 2026-08-274BYOC is the most useful capability in the whole layer: "Is this message a sales pitch?", "Does this assert scarcity or a deadline?", "Does this reference a hardship the user disclosed?" have no off-the-shelf classifier anywhere, and BYOC evaluates them as natural-language criteria without fine-tuning. But the card says verbatim: "The model is only trained and tested on English data." For a Ukrainian deployment your best-licensed judge does not speak your customers' language.
Qwen3GuardSafety moderation, 119 languages, Safe/Controversial/Unsafe tiers, plus a token-level streaming variantHF checkpoints apache-2.0; the GitHub repo has NO LICENCE FILE (license: null)Checkpoints 135k/102k/49k dl/mo; repo last push 2025-10-21 (~11 months), contains only eval/4The only broad-multilingual open guard that exists — Granite is English-only, HHEM is English-only, Llama Guard 4 declares language: ['en'], Presidio stops at Poland. The "Controversial" middle tier maps cleanly to route to a human, satisfying WhatsApp's escalation-path requirement and GDPR Article 22's human-intervention safeguard at once. Vendor the weights; reimplement the wrapper (the repo grants you nothing).
PresidioPII boundary: ingest + egressMIT10,870★, commit 2026-09-155Your memory system is the product and the legal centre of gravity. Presidio lets the bot keep "prefers matte finishes", "buying for a six-year-old" while raw identifiers never reach the embedding store. Reversible pseudonymisation is the practical answer to erasure-vs-vector-index: the LLM reasons over placeholders, real values sit in a relational row, erasure is a cascading DELETE plus a reindex.
promptfooDeclarative evals + red-teaming with multi-turn strategies: Crescendo, GOAT, Mischievous User, HydraMIT — now part of OpenAI, stated verbatim in the README: "Promptfoo is now part of OpenAI. Promptfoo remains open source and MIT licensed."25,117★, 0.123.0 2026-09-10, commit 2026-09-155Crescendo is literally the mechanic of a bot going bad at selling: benign, benign, benign, pitch. Invert the red-team framing — escalate until it produces an OFFER, and measure how many turns of social pressure break the gate. Mischievous User tests whether the gate can be talked open by "so what would you recommend?". Governance, not licence, is the risk: your merge gate is now maintained by a frontier model vendor.
Microsoft PyRITMulti-turn adversarial orchestration with converters, scorers, memoryMIT4,470★, v1.1.0 2026-09-04 (reached 1.x, so the API stabilised), commit 2026-09-14. Azure/PyRIT is archived (last push 2026-03-25)4Multi-turn is the point. Your product's thesis is that behaviour changes across turns and sessions, so the interesting failure is never "can one message jailbreak it" — it's can an adversary spend ten turns manufacturing rapport and extract an unauthorised discount, or can an instruction planted in memory in week one fire in week six. Single-turn scanners structurally cannot find any of that.
garakLLM vulnerability scanner with a flexible REST generatorApache-2.09,250★, v0.17.0 2026-09-093Run the leakreplay family specifically: a bot that remembers people can leak one customer's remembered details into another's conversation — a personal-data breach, reputationally fatal, and invisible to every other test here. Seed the staging memory store with distinctive synthetic facts per persona so a leak is trivially detectable.
Guardrails AIInput/output validators with on-fail actions + structured-output validationApache-2.07,415★, v0.11.0 2026-08-14; Hub hosted-inference retirement cutoff was 2026-08-25 — already passed2Narrow but real value: force every offer into a validated schema with an enumerated offer type, a product_id, and a price that must byte-match the tool response. Constraining the shape of the action beats inspecting prose.
LangfuseTracing, sessions, prompt versioning, online LLM-judge evaluationMIT core + commercial ee/, web/src/ee/, worker/src/ee/. Root LICENSE now reads "Copyright (c) 2023-2026 ClickHouse, Inc."34,635★, v4.36.1 2026-09-154Sessions are the right primitive: your unit of analysis is the user across months, not the request. Paywalled in self-hosted: data retention policies, audit logs, project-level RBAC, server-side data masking, SCIM (org-level RBAC and SSO enforcement are free). Retention and masking behind a paywall is a genuine compliance cost for a business holding Ukrainian consumer PII.
OpikTracing + evaluation + online scoring rules on production trafficApache-2.0, no ee/ directory22,035★, 2.2.63 2026-09-154The cleanest licence in observability — and that matters more after this survey found paywalls or closed binaries in Langfuse, Phoenix, PostHog, GrowthBook and Novu. Online evaluation is how you score pushiness continuously on real traffic rather than quarterly. Multi-service self-host (ClickHouse + MySQL + Redis).
GiskardScan + red-team with bias/stereotype detectionApache-2.0. Repo renamed giskardgiskard-oss; v3.0.0 released 2026-08-26 and splitting into separate packages5,816★, commit 2026-09-143The bias scan maps to an exposure everyone overlooks: if the readiness model learns — because it's trained on conversions — that certain demographics convert better and get pitched harder, you have a GDPR Article 22 profiling problem, a potential Article 5(1)(b) problem, and a discrimination problem, none of which anyone intended. Scan the offer decisions, not the chat text. You'd be adopting a three-week-old rewrite mid-split.
COMPL-AI29+ benchmarks mapped to EU AI Act principles; public leaderboardApache-2.0, ETH Zurich / INSAIT / LatticeFlow208★, only 4 open issues, commit 2026-09-07, no releases (run from source)3Benchmarks models, not systems — it will say nothing about whether your offer-timing logic crosses Article 5(1)(a), because that depends on your rules and your data about the user. Value is a dated, reproducible artefact showing diligence, plus a shared vocabulary between lawyers and engineers. Never present it as certification.
LettuceDetectSpan-level groundednessMIT606★, commit 2026-09-075See §6. The only path to Ukrainian groundedness, because even paid HHEM-2.3 covers Russian but not Ukrainian.
HHEM-2.1-OpenCross-encoder factual-consistency scorerApache-2.0217,115 dl/mo, modified 2025-10-201Included to settle the question. language: en. And the paid HHEM-2.3's listed coverage is "English, German, French, Portuguese, Spanish, Arabic, Chinese-Simplified, Korean, Russian, Japanese, and Hindi"Russian yes, Ukrainian no. There is no vendor escape hatch for your primary language.
Llama Guard 4-12BMultimodal safety classifier, MLCommons S1–S14 taxonomyother = Llama 4 Community Licence: acceptable-use policy, 700M-MAU threshold, naming obligations; gated: manual46,985 dl/mo, last modified 2025-04-29 (~17 months)1HF metadata declares language: ['en'] — a single language. Non-OSI, manually gated, 17 months stale, and 12B inference for a safety classifier when a 0.6B would do. Its S6 "Specialized Advice" category is worth stealing conceptually: route to a human, not a flat refusal.
DarkBench660 prompts across six dark-pattern categories: brand bias, user retention, sycophancy, anthropomorphism, harmful generation, sneakingMIT (licence question now resolved)18★, ICLR 2025 oral — entire repo history spans 16 days in March 2025, nothing since3The taxonomy is the asset, not the prompts. Sneaking — quietly reframing the customer's stated need to match the product you want to sell — is the most insidious form of pushiness, because it survives a surface reading of the transcript. Redefine brand bias for a first-party store as "recommended our product when a better-fitting alternative in our own catalogue existed".
DetoxifySmall toxicity classifier, multilingual variant (7 languages)Apache-2.01,300★, commit 2026-07-063Cheap always-on tier, genuinely permissive, genuinely alive.
LLM Guard15 input + 20 output scanners (BanCompetitors, NoRefusal, InvisibleText, FactualConsistency…)MITARCHIVED, 3,205★, last push 2026-07-082Dead, but its scanner inventory is the best available specification. Three worth extracting into your own repo: InvisibleText (zero-width injection in reviews — trivial pure Python, high value), BanCompetitors, and NoRefusal (detects that the model complied with something it should have refused — the inverse check nobody runs, and precisely the shape of "it got talked into pitching").
RebuffPrompt-injection detection with canary tokensApache-2.0ARCHIVED, 1,522★, last push 2024-08-07 — dormant two years before archival1Reimplement the canary-token trick (plant a token in the system prompt, detect injection when it surfaces in output) in ten lines of your own code.
protectai/deberta-v3-base-prompt-injection-v20.2B injection classifierApache-2.0ARCHIVED per the model card, still 859,469 downloads/month, used by 28+ projects2The clearest illustration that download counts lag abandonment by a year or more. English-only; the card itself warns against running it on system prompts. Stopgap only — prefer Prompt Guard 2 (non-OSI) or Qwen3Guard (Apache weights, unmaintained repo) and benchmark all three on your own review corpus.
FidesDSR/erasure orchestration across databases and SaaSApache-2.0ARCHIVED, 483★, 222 open issues frozen, last push 2026-08-061Its archival is a planning finding, not a tool review: there is now no maintained open-source DSR platform, so erasure is a service you build, not a library you import. Budget 2–4 weeks and do it before launch, because the first erasure request will not wait.

How to choose here. Three tiers with different jobs. (1) Deterministic policy (OPA, or plain code) for anything countable and auditable — this is where the never-list lives, gating the tool call. (2) Behavioural/conversational (Parlant strict mode or NeMo rails) so the model's words are constrained on claim-bearing turns. (3) Model judges (Granite BYOC for English, Qwen3Guard for multilingual, LettuceDetect for groundedness, Detoxify/textdetox for toxicity) with cost-tiered routing — cheap classifier on every message, expensive judge only on offer- or claim-bearing turns. Two rules make the difference: gate actions, not text (if the model can mention a product in free prose, no tool-level gate helps — force every offer through a tool), and implement vetoes as a separate boolean layer evaluated AFTER scoring, backed by cheap multilingual keyword rules, so a model outage cannot silently un-veto.

Things that sound right but aren't (guardrails). The four most-cited tools are dead: LLM Guard archived 2026-07-09, Rebuff archived (dormant since Aug 2024), protectai's injection model archived while still pulling 859k downloads/month, Azure/PyRIT archived 2026-03-27 (live project moved to microsoft/PyRIT), Fides archived 2026-08-28. Licence traps concentrate in the most-downloaded artefacts: pysentimiento non-commercial, tabularisai CC-BY-NC, cardiffnlp no licence at all, Qwen3Guard's repo unlicensed while its weights are Apache. Arize Phoenix is Elastic License 2.0 — widely described as open source, and it isn't; Opik gives comparable evaluation under Apache-2.0, so choosing Phoenix means taking a non-OSI licence for no benefit. And the most consequential one for this build: tdlib/telegram-bot-api is BSL-1.0 = the Boost Software License, permissive and OSI-approved — not the Business Source License. Rejecting it on the abbreviation is a mistake in the opposite direction.


10. Orchestration & proactive scheduling

ProjectWhat it isLicenceMaturityFitVerdict
DBOS TransactDurable workflows as a library, checkpointing into your existing PostgresMIT (Conductor/Cloud is a separate optional proprietary control plane)1,574★, v2.31.1 2026-09-08, commit 2026-09-14, "Production/Stable"5Verified dependencies are only psycopg, sqlalchemy, websockets, click, pyyamlzero new stateful services. A durable sleep survives deploys and restarts, which is exactly what "check on her in 21 days" requires. Keep steps coarse: checkpointing every step is a Postgres write amplifier.
pgmqPostgres-native queue: visibility timeouts, delayed delivery, archiving, DLQ — no brokerPostgreSQL Licence5,275★, v1.13.0 2026-09-07, commit 2026-09-14. pgmq/pgmq and tembo-io/pgmq are the same repo4If re-evaluation must happen in the handler at fire time anyway (it must), the scheduler requirement collapses to "durably remember to check on this customer at T" — one row with a visibility timeout. Requires an extension, so check your managed Postgres supports it.
pg-bossPostgres job queue: startAfter far-future scheduling, singletonKey, sendDebounced, RRULE, DLQ, priorityMIT3,951★, 12.32.0 2026-09-14; requires Node 22.12+ / Postgres 13+5Scheduling a job with startAfter: '2027-01-01T08:00:00Z' — over a year out — is one row, documented. singletonKey means a customer can only ever have one pending check-in, and cancelling on inbound is one call. Node.js only.
Graphile WorkerPostgres queue with job_key + job_key_modeMIT2,388★, commit 2026-09-084The most precise expression of the crux property, and the three modes map one-to-one onto three selling behaviours: replace = debouncing (the check-in slides while the customer is engaged), preserve_run_at = throttling (at most one nudge per fortnight no matter how many triggers fire), unsafe_dedupe = strict once-only. Array payloads merge under replace, giving a free batching window. Documented race: locked (executing) jobs cannot be removed, so remove_job only prevents retry — your handler's first statement must be "abort if last_inbound_message_at is within N minutes".
TemporalDurable execution: workflows as ordinary code, signals, timers, versioningMIT23,061★, v1.32.0 2026-09-11, commit 2026-09-144The only engine that says it plainly in its own docs: "A Workflow can sleep for months" and "Sleeping is a resource-light operation… you can run millions of Timers off a single Worker." Race a durable timer against a signal and the check-in cancels silently. Corrects a common belief: docker-compose-postgres.yml runs against plain Postgres with no Elasticsearch (ES is behind ENABLE_ES=true). Cost: determinism constraints and workflow versioning, which bite hard when workflows live six months while you iterate weekly.
HatchetPostgres-centric orchestration with or-groups over durable sleep and durable event waitsMIT, no ee/7,941★, v0.106.5 2026-09-08, commit 2026-09-123Its or-group is the crux requirement expressed declaratively — wait for a sleep or an event, whichever comes first. But the footprint is the opposite of its reputation: its own docker-compose.yml needs postgres:15.6 + edoburu/pgbouncer + rabbitmq:4.2.8-management + nats:2.14.3-alpinefour stateful services including two brokers. Also: the docs state no maximum sleep duration anywhere, so a six-month sleep is plausible but undocumented — prove it before betting on it.
InngestEvent-driven durable workflows; step.waitForEvent with an if correlation expressionSSPL 1.0 with Apache-2.0 future licence — not OSI5,800★, commit 2026-09-153Best ergonomics for wait-or-timeout anywhere. But self-hosting defaults are production-unsafe, per their own docs: in-memory Redis, SQLite persistence, "Inngest's support team does not guarantee direct support for self-hosted instances", and "The Inngest server does not automatically delete old database rows… Large table growth can hurt performance." Plus a documented race: "Events sent before the function is executed will not be handled by the wait."
Trigger.devDurable background jobs; wait.until(), wait.for(), wait.forToken()Apache-2.016,300★, commit 2026-09-153Waits longer than five seconds snapshot the machine and don't count toward compute — holding 10,000 dormant relationships costs nothing. wait.forToken() lets an inbound message resume or divert from outside. Concurrency edge: the slot is only released 60s into the wait, so many short waits eat concurrency. Heaviest self-host (container checkpointing); TypeScript-first.
RestateDurable execution around Virtual Objects — keyed, single-writer stateful entitiesBSL 1.1, Change Licence Apache-2.0 after 4 years; Additional Use Grant permits your own production and internal deployments, forbids a "Public Restate Platform Service"4,400★, commit 2026-09-153Conceptually the best fit: one object per customer, single-writer serialisation, so a concurrent inbound message and a scheduled wake-up cannot corrupt state — a real bug class in every queue-based design. Durable sleep is documented as unbounded ("months or even years"). But the docs warn long sleeps block that object's other requests, which for a per-customer object is a deadlock on your primary interaction path, and whether a delayed message can be cancelled before firing is not documented — that unverified cancellability is the crux property.
RiverTransactional Postgres queue for GoMPL-2.0 core5,700★, commit 2026-09-142Transactional enqueue is a genuine correctness advantage — insert the "follow up in 10 days" job in the same transaction that records the delivery. Pro-only (verified): workflows, batching, sequences, concurrency limits, DLQ, durable periodic jobs, encrypted/ephemeral jobs. Go-only.
NovuNotification infrastructure: workflows with delay/digest, per-subscriber preferences, provider abstractionMIT core + commercial enterprise/packages — the EE licence requires prior written approval from Novu, forbids modification and commercial hosting39,991★, commit 2026-09-15213 chat providers include Telegram and WhatsApp Business — and no Viber (verified by listing packages/providers/src/lib/chat/). Adds MongoDB and Redis to an otherwise Postgres stack. Worse, its subscriber-preference model duplicates state the gate must read, creating two sources of truth about whether a customer may be contacted — a correctness hazard, not just redundancy.
WindmillScripts → workflows + internal UIs, with suspend/approval stepsAGPLv3 + Apache-2.0 + proprietary code behind an enterprise compile flag; free internal use within quotas, no resale17,900★, very active2Wrong tool for month-scale waits (flows are minutes-to-hours). Right tool for the console nobody plans: a screen showing every pending scheduled touch, why the bot thinks each customer is ready, and a cancel button. For a bot that will make mistakes in its first months, a cheap human override surface is worth more than most of the sophistication above it.
DittofeedOpen-source engagement platform on Temporal: segments, computed properties, WaitForNode, SegmentSplitNode, RateLimitNodeMIT open repo; README confirms a "licensed closed-source codebase" covering multi-tenancy, embedding, white-labeling2,938★, last commit 2026-03-27, releases stuck on v0.24.0-alpha.17 (2026-03-28) for three quarters3WaitForNode"wait for a user to enter a segment, with some timeout", branching on segment-entered vs timed-out — is the readiness-gated upsell, already implemented by someone else on Temporal. RateLimitNode is the anti-pushiness governor most teams forget to build. Read it as a blueprint; don't deploy an alpha-only, six-months-dark dependency in your scheduling path.
CeleryThe default Python task queue + Beat schedulerBSD-3-Clause28,900★, v5.6.3, extremely mature1Explicit anti-recommendation for month-scale delays. apply_async(eta=...)/countdown= does not durably park the task — the message is delivered to a worker and held in its prefetch buffer, so deploys, restarts, broker evictions and visibility timeouts lose or duplicate it. The failure is silent and unrecoverable: a lost check-in doesn't throw, a customer just drops out of the cadence for a quarter. Beat is fine for a nightly readiness sweep.
ProcrastinatePostgres task queue for PythonMIT1,400★, actively maintained4The Python-native pg-boss equivalent, and the answer to the gap left by anti-recommending Celery in a Python stack.
MetabaseSelf-service BI with scheduled email digestsAGPL-3.0 outside enterprise/, commercial within49,268★, commit 2026-09-153Not for dashboards — for the forcing function. A weekly auto-emailed digest (offers made, offers withheld, decline rate, block rate, holdout gap with a confidence sequence, golden-set score) is the cheapest defence against building for nine months and shipping nothing. At this data volume a scheduled Python script emailing a table would honestly do; AGPL's network clause matters if dashboards are ever customer-facing. Apache Superset is the permissive alternative.

How to choose here. The crux — "schedule a check-in, cancel it if the customer writes in first, re-evaluate eligibility at fire time" — decomposes into two decisions people conflate. Cancellation is trivial everywhere (delete a row, complete a token, send a signal). Re-evaluation at fire time is not a scheduler feature at all — it is a discipline: the scheduled payload must contain only {customer_id, intent}, never a decision or a rendered message. So choose on operational cost, and the ranking by services you must operate is stark: pg-boss / Graphile Worker / pgmq / Procrastinate = 0 new services; DBOS = 0 new services (a library); Temporal minimal = 1 server + your Postgres; Hatchet = 4 stateful services. At 2,000–50,000 customers the sophisticated engine buys you nothing the simple row does not. Revisit Temporal when you genuinely need long-running multi-service sagas with versioned workflow migrations.

Things that sound right but aren't (scheduling). "Hatchet is the lightweight Temporal" — four stateful services vs Temporal's one, and Temporal doesn't need Elasticsearch. "Celery's countdown handles delayed tasks" — it does not durably park them. "Inngest is open source" — SSPL, and its self-host defaults are SQLite plus in-memory Redis with no automatic row deletion. "Restate's durable sleep solves this" — the docs warn long sleeps block the Virtual Object's other requests, which deadlocks a per-customer object. "Dittofeed is a ready-made engagement platform" — six months dark on alpha-only releases, with multi-tenancy closed-source. "Novu handles multi-channel" — no Viber provider, and it splits consent state away from the gate that must read it.


11. Evaluation & simulation

ProjectWhat it isLicenceMaturityFitVerdict
tau2-benchDual-control agent benchmark; retail domain; verifiable DB-state reward; pass^kMIT2,037★, push 2026-09-114pass_hat_k is confirmed in code (src/tau2/metrics/agent_metrics.py and 4 other files), not just in the paper. Report pass^4, not mean — 70% correct withholding is one pushy conversation in three, and on messaging that is a permanent block. Requires Python ≥3.12 + uv. Single-session by construction; you write the multi-episode driver.
IntellAgentDecomposes your policy prompt into a policy graph, auto-generates adversarial personas, simulates, critiques, reports per-policy failure ratesApache-2.01,257★, last commit 2026-09-14 — but that's a community PR merge; before it, two months of silence4"Must withhold offers until readiness" is a policy, and policies are what this generates adversarial tests against. Per-policy pass rates ("offer-withholding: 61%") are what you need to debug a readiness model — a single aggregate number tells you nothing. Productised version of the ALMITA graph-generation idea. ~$0.10/sample claimed; gate to pre-release.
DeepEvalConversational metrics (KnowledgeRetention, RoleAdherence, ConversationCompleteness, TurnRelevancy) + ConversationSimulatorApache-2.018,274★, commit 2026-09-14, multiple same-day commits5Fastest path to persona-driven simulated customers in CI. ConversationalGolden carries scenario, expected_outcome and Persona(characteristics=...); max_user_simulations, simulation_graph, and stopping_controller are real current parameters. stopping_controller is the single most important modification you will make in this entire stack — it's where the simulated customer gets annoyed and leaves. Without it every simulation monotonically rewards talking more and offering more.
ConcordiaGenerative agent-based modelling around a Game Master that controls narrative timeApache-2.01,702★, commit 2026-09-14, v1.2.0 imminent5The only mature, permissive, actively-maintained engine that can express "three weeks pass; her running shoes are now worn out; she opens Telegram again". Every other tool here is trapped inside one conversation — verified individually for tau2-bench, IntellAgent, DeepEval, promptfoo, Inspect AI and Sotopia. Its Components give the simulated customer her own memory, so she can independently form "this bot pitched me last time when I was stressed". Research library, not an eval harness — you build the measurement layer.
promptfooDeclarative evals + multi-turn red-team strategiesMIT (now OpenAI-owned)25,117★, 0.123.0 2026-09-105Per-PR gate. Write a custom premature-offer plugin; run Crescendo with statefulness enabled so memory is exercised; gate merges on zero successes.
Inspect AISolver/Scorer evaluation framework from the UK AI Security InstituteMIT2,775★, commit 2026-09-154The least vendor-captured harness available — after this survey, nearly every other platform is owned by a party with a commercial interest in the numbers (ClickHouse, OpenAI, Confident AI, Comet, Mem0). If you'll make a business decision on "the bot is not pushy", you want a neutral harness. Also the runtime for DarkBench, so manipulation and sales-quality testing share one Scorer abstraction.
OpikObservability + online LLM-judge scoringApache-2.0, clean SPDX, no ee/22,035★, commit 2026-09-15 with three same-day commits5Contradicting a common impression: it is every bit as active as Langfuse. Online evaluation rules score pushiness on live traffic — which matters because your simulator will never produce a block event and only production will.
LangfuseTracing with Sessions, prompt versioning, datasetsMIT core + commercial ee/; ClickHouse-owned since January 202634,635★, v4.36.1 2026-09-154Sessions map onto "one customer, many conversations over many months" — the one capability worth accepting open-core for. Retention policies and masking are paywalled.
VerdictCompound LLM-judge protocols: Unit/Layer/Block, verification, debate, aggregationMIT348★, 130 commits, last commit 2025-11-05 — ten months, and it was a plot update3Your central metric — "was this offer appropriately timed?" — is the worst possible case for a single-call judge: position-biased, sycophantic toward fluent text, clustering at 4/5 and destroying resolution on exactly the axis you care about. Debate-and-aggregate is the documented fix. Vendor or reimplement (~200 lines); don't depend.
Prometheus-EvalOpen judge models with custom-rubric absolute and pairwise gradingApache-2.0 code; weights inherit Mistral/Mixtral base licences — check the checkpoint1,116★, last commit 2025-04-25 (~17 months)2The economic argument is right and survives: judging ~500,000 turns with a frontier API is prohibitive, slow, and exports your entire conversational strategy corpus to a third party. The implementation has aged out — 0.6–0.7 correlation with a GPT-4-1106-era model on 2+ generation-old bases. Keep the two-tier architecture (cheap self-hosted screen + frontier audit of the 5% borderline plus a 2% random sample, with tier agreement tracked); fine-tune a current small model instead.
SotopiaSocial-intelligence simulation; SOTOPIA-EVAL scores goal completion, believability, RELATIONSHIP, social rules and financial benefitMIT331★, default-branch last commit 2026-01-07 (~8 months)3The only evaluation anywhere that scores relationship as a first-class dimension alongside financial benefit — a precise formalisation of your core tension. Adopt the dimension structure and make relationship delta a VETO, not a trade-off term: any variant that raises financial benefit while lowering relationship delta fails review regardless of revenue. That design decision is worth more than running the code.
LongMemEval500 questions across five long-term memory abilities: extraction, multi-session reasoning, knowledge updates, temporal reasoning, abstentionMIT1,086★, 40 commits, last commit 2026-05-11 (README), last substantive 2025-10-244The two abilities that will hurt you commercially are the two it isolates best. Knowledge updates: she said in March she was buying for her sister, in July for herself — a bot that pitches gift-wrap on stale memory looks stupid and destroys the advisor framing in one message. Abstention: a bot that hallucinates a remembered preference to justify an offer is the fastest trust-destroyer, and a readiness gate structurally creates pressure to find evidence of readiness. Use the oracle variant to separate retrieval failure from reasoning failure.
BEAM2,000+ questions, 10 memory ability types, 100K–10M token bucketsMIT144★, push 2026-08-31, ICLR 20264Newest, largest, MIT and actively maintained — the commercially-safe replacement for LoCoMo. Make it your primary memory regression suite with LongMemEval.
mem0ai/memory-benchmarksHarness running LoCoMo + LongMemEval + BEAM against a pluggable memory backendApache-2.0 (relicensed from MIT 2026-04-13)108★, created 2026-03-30, default-branch last commit 2026-05-133Makes memory backends comparable on a fixed protocol — the memory layer is load-bearing and picking it on vendor marketing is how you rebuild in month four. Two cautions: it is vendor-owned (only Mem0 Cloud and Mem0 OSS supported, so write an adapter for Letta or plain pgvector to detect protocol bias), and because it bundles LoCoMo it inherits a non-commercial dataset — run the LongMemEval + BEAM subsets only.
LoCoMoVery-long-term conversational memory: persona + event-graph generation, 10 multi-session dialoguesCC BY-NC 4.0 — NON-COMMERCIAL, verified from LICENSE.txt1,168★, 18 commits, last commit 2024-08-132Read the paper for the persona + temporal-event-graph method — that's exactly how you should manufacture "her shoes wore out, she started marathon training, she got a raise" across six simulated months. Build from the paper, not the repo. Its score is worthless anyway: it has become the benchmark memory vendors publicly tune against.
Botium Core"Selenium for chatbots": BotiumScript conversation flows + connector architectureMIT251★, last commit 2026-09-053The right tool for channel-level regression testing across Telegram/WhatsApp/Viber, as distinct from model-level quality. Nobody else in this layer covers that.
CRMArenaCRM agent benchmark with confidentiality awarenessCC BY-NC 4.0148★, push 2026-09-101Structurally the same test as "did the bot correctly withhold". Legally unusable. Re-implement the idea in tau2-bench.
Open Bandit PipelineOPE estimators + ZOZO datasetApache-2.0Default-branch last commit 2022-11-052Docs remain the best specification of your logging schema. Vendor the estimators.
RecSim / RecSim NGRecommender simulationApache-2.0Both ARCHIVED, untouched 4+ years1Still the first search result for "recommender simulation". Build on Concordia.
CRSLabConversational recsys toolkitMITLast commit 2023-05-16; pushed_at reads 2024-04-121Its three-module decomposition (recommender / conversation / policy) is the right mental model, and its policy module is the academic ancestor of "when is the customer ready". Its death demonstrates the planning fact: elicitation and critiquing must be built.

How to choose here. There is no open-source benchmark for multi-session, long-horizon relationship selling — every agentic benchmark verified here evaluates a single session with a single goal, and every memory benchmark evaluates whether the agent can answer from long history, never whether it decides correctly from it. So assemble: fork tau2-bench for the verifiable-DB-state reward and pass^k, take IntellAgent's policy-graph generator, and wrap both in a Concordia Game Master that advances the clock in days between sessions and decides whether the customer opens the app at all ("no reply" must be a first-class outcome). Then guard against the trap that dooms most eval suites here: the default metrics are adversarial to your goal. DeepEval's ConversationCompleteness and TurnRelevancy reward a bot that says more, offers more and agrees more; an LLM judge asked "was this helpful?" reliably prefers the sycophantic over-helper. Every quality metric must be paired with an adversarial veto — dark-pattern rate, simulated abandonment, real opt-out rate — and a variant that raises conversion while raising sneaking is rejected, full stop.

Things that sound right but aren't (eval). RecSim and RecSim NG — both archived. Argilla is effectively abandoned post-acquisition (develop last commit 2025-08-05, no commits since March 2026) — use Label Studio (Apache-2.0, 28k★, actively maintained) for the labelling loop, and note that its inter-annotator agreement metrics are Enterprise so you'll compute kappa yourself. LoCoMo and CRMArena and the Duolingo replication dataset and PersonaHub's persona data and Criteo-UPLIFT — all non-commercial. RecSim, Sotopia, LongMemEval, Prometheus-Eval, Verdict, DarkBench, zr-obp, SCOPE-RL — all stale to varying degrees. And two structural mistakes that cost more than any tool choice: measuring per-turn quality instead of per-conversation (ALMITA's central finding is that agents which look strong on individual interactions frequently fail complete conversations — and the gap widens again across sessions), and testing only on clean monolingual messages when OLA (ACL 2026) measures output-language pass rates of 60.9–85.8% under code-switching even when the cue is explicit, with over 92% of users rating a language mismatch "Uncomfortable or Critical".


12. LLM runtime & models

Project / modelWhat it isLicenceMaturityFitVerdict
google/gemma-4-26B-A4B-it26B-total / 3.8B-active MoE, 256K context, native system role, native function calling, configurable thinkingApache-2.0 — verified on 26B-A4B, 31B and 12B. A genuine break from Gemma 3's custom terms9,238,215 dl/mo, created 2026-03-11, modified 2026-07-20; official QAT q4_0 GGUF (557,705 dl/mo) and an -assistant draft model for speculative decoding (131,944 dl/mo)5Best-evidenced choice, and the evidence is independent: lang-uk leaderboard Global-MMLU-uk 0.8664, Belebele-uk 0.9533, ARC-uk 0.9249, Winogrande-uk 0.8445, SQuAD-uk F1 71.69, and IFEval-uk prompt-level 0.8189 / instruction-level 0.8681. That IFEval number is the whole ballgame — your differentiator is a model reliably obeying a withholding policy in Ukrainian. FLORES en-uk chrF 60.77 beats the Ukrainian specialist MamayLM. Caveat the headline: the leaderboard run used enable_thinking: True at max_model_len: 32768, so those numbers are reasoning-mode at 32K, not 256K.
mistralai/Mistral-Small-4-119B-2603119B-total / 6.5B-active MoE, per-request reasoning effort, 256K, official NVFP4 + EAGLE headApache-2.057,087 dl/mo, created 2026-01-23. On OpenRouter as mistralai/mistral-small-2603 — not under any "mistral-small-4" string5The only top-tier model whose structured HF metadata explicitly lists both ru and uk — auditable, unlike Gemma's empty language field and prose "140 languages" claim. And the decisive cost fact: $0.15/M in, $0.60/M out, cache-read $0.015/M — a 90% discount, against Gemma's $0.09→$0.05 (44%). On a prefix-dominated workload (17,600 of 41,691 input tokens are a stable system+policy+catalogue prefix) the nominally pricier model can be cheaper. No Ukrainian leaderboard entry — quality asserted by tag, never measured.
vLLMReference inference server: PagedAttention, continuous batching, FP8/INT4, multi-LoRA, OpenAI-compatibleApache-2.0, no carve-out91,812★, commit 2026-09-15, 21,372 commits5Verified from source, not docs: vllm/config/cache.py line 130 is enable_prefix_caching: bool = True — the public docs telling you to enable it are stale. But prefix_cache_retention_interval defaults to 0, so shipping defaults and assuming cross-session warmth is a silent, expensive mistake. Prefix caching accelerates prefill only. Also the eval backend the lang-uk leaderboard actually used.
LMCacheTiered KV-cache offload (HBM→CPU→disk→object store), P2P sharing, disaggregated prefillApache-2.011,812★, commit 2026-09-15, 2,292 commits4The piece that makes "remembers them across weeks" economically viable if you self-host: vanilla prefix caching lives in GPU memory, so a customer returning Thursday pays a full cold prefill of their entire profile. Adds a stateful storage tier you must size and garbage-collect (KV caches run to tens of MB per long prefix). Tightly coupled to engine internals — vLLM ships daily.
SGLangServing with RadixAttention (radix-tree prefix sharing) + fast constrained decodingApache-2.035,983★, commit 2026-09-153RadixAttention suits many concurrent conversations sharing one large policy prompt; fast grammar-constrained decoding matters because your bot emits structured relationship-state updates alongside prose on every turn, and that side-channel is your state machine. But run one engine — vLLM has 2.5× the stars, the LMCache integration, and parity with the Ukrainian leaderboard's own stack.
OpenRouterOne OpenAI-compatible API over ~445 models with cache tiers and failoverCommercial hosted (models are open-weight, so the exit is a base_url change)445 models, prices verified live 2026-09-155Removes the entire inference tier from your ops surface. Verified: google/gemma-4-26b-a4b-it $0.09/$0.30, cache-read $0.05, 262,144 ctx (a :free variant exists); mistralai/mistral-small-2603 $0.15/$0.60, cache-read $0.015, :batch at half price. Cheap classifier tier: openai/gpt-oss-20b $0.03/$0.13; inclusionai/ling-3.0-flash $0.021/$0.063, cache-read $0.0042.
UnslothFast memory-efficient LoRA/QLoRA/GRPO/DPO fine-tuning + day-one GGUF conversionsApache-2.076,193★, commit 2026-09-15; their Gemma 4 QAT GGUF has 680,197 dl/mo4The cheapest credible path to a distinctive Ukrainian brand voice — formal Ви vs informal ти is a real product decision no prompt handles reliably, and that is a style property a few hundred curated examples teach. Note the pivot: the repo description now reads "Local UI to run and train LLMs and diffusion models" — focus is shifting toward a desktop/Studio product.
TRLPost-training: SFT, DPO, KTO, ORPO, GRPO, PPOApache-2.019,313★, commit 2026-09-154Where the interesting answer to "supportive, never pushy" lives: SFT only teaches imitation, while preference optimisation teaches the model to prefer the patient reply over the pushy one given identical context. KTO is especially well-matched — it learns from binary desirable/undesirable labels, so you bootstrap from existing support logs ("this reply got a reply back" vs "this reply ended the conversation") with no pairwise annotation project.
LLaMA-FactoryUnified fine-tuning across 100+ models with a huge chat-template libraryApache-2.0, ACL 202474,783★, commit 2026-09-143Its template coverage is the specific value: getting the chat template subtly wrong is the most common cause of a finetune that trains cleanly and silently underperforms — and it's especially hard to catch when your evaluation language is one your team may not read natively. Use it for the cross-model bake-off, then consolidate.
INSAIT MamayLM-Gemma-3-27B-IT-v2.0Ukrainian-specialist continued pretraining of Gemma 3gemma — Google Gemma Terms, not OSI: use restrictions, downstream-notice obligation, remote restriction833 dl/mo, 9 likes, created 2026-06-03; 12B v2.0 at 1,757 dl/mo3Concrete evidence of what specialisation buys — and that in 2026 it no longer buys enough. MamayLM-12B v2.0 scores MMLU-uk 0.6426 against base gemma-3-12b-it's 0.538 (+10), while Gemma 4 scores 0.8664 (+19 over the best MamayLM) under Apache-2.0. Its SQuAD-uk F1 of 39.50 vs Gemma 4's 71.69 suggests weak extractive comprehension. Value is as a recipe: INSAIT has twice demonstrated that continued pretraining of a Gemma base on one Slavic language works.
lapa-llm/lapa-v0.1.3-instructUkrainian LLM from UCU / AGH Krakow / KPI / Lviv Polytechnic with an adapted tokenizer (80,000 of Gemma 3's 250,000 tokens replaced with Ukrainian)gemma on checkpoints; the separate lapa-llm/tokenizer artefact has NO declared licence; companion scoring models are MIT1,854 dl/mo, 27 likes — higher adoption than MamayLM4Directly attacks the cost problem: the card claims Ukrainian "requires 1.5 times fewer tokens, thus performing three times fewer computations to achieve better results." That reframes the Ukrainian token penalty as a tokenisation artefact, not a property of the language — which is the argument to make before agreeing to pay 1.5–1.8× per token on a commercial API for a bot that re-reads months of history every turn.
lapa-llm/manipulative-score-modelManipulation scorer on multilingual-e5-base (built as a pretraining-corpus filter)MIT — cleanest licence of any behavioural guardrail here19 dl/mo, 2 likes, modified 2025-11-134Closest thing to a machine check on "advisor, not pushy seller". Your readiness model decides when; nothing else decides whether the offer, once written, is manipulative. Score every outbound recommendation; regenerate above threshold; log the score per message — which is exactly the artefact you want when a customer complains. Weak evidence base: validate on 100 hand-ranked Ukrainian sales messages before trusting it.
Qwen3.8-27BDense VLM, hybrid Gated DeltaNet + Gated Attention, 262K native, preserve_thinkingSplit: 27B is apache-2.0; Qwen3.8-Flash-Next and 2.4T-A95B are other; the QwenLM/Qwen3 GitHub repo has no licence at all and last pushed 2026-01-097,702,543 dl/mo, 15,198 likes — most-liked in the layer3Two genuinely relevant properties: linear attention makes KV cache grow far more slowly with context, and preserve_thinking gives a first-class place for the bot's private deliberation about readiness — carried forward across turns, never shown. But: on the lang-uk leaderboard Qwen3-8B scored MMLU-uk 0.2295 (below chance) while its base scored 0.671 — and on the same run ifeval_uk was a healthy 0.5952. IFEval needs no answer extraction; MMLU/Belebele/SQuAD do. That asymmetry is near-proof of a thinking-mode parsing artefact, but it means no off-the-shelf Qwen RU/UK number is trustworthy without re-running with correct parsing. Output at $2.55/M is ~8.5× Gemma's.
command-a-plus-05-2026218B-total / 25B-active MoE, 128K in / 64K out, 48 language tags including ru and ukapache-2.0 — while the older c4ai-command-a-03-2025 remains cc-by-nc-4.042,715 dl/mo, created 2026-05-113Corrects the assumption that Cohere is non-commercial — the trap is version-specific, not vendor-specific. 64K output is unusually large and genuinely useful for nightly structured relationship summaries. But 218B means multi-GPU even quantised, it is not listed on OpenRouter, and it has no Ukrainian benchmark entry.
openai/gpt-oss-120bOpen-weight reasoning models, configurable effort, 131KApache-2.0, no riders5,341,536 dl/mo, created 2025-08-04, last modified 2025-08-26 (~13 months, no successor)2The cost floor, and a cautionary example. **Verified againstthe live OpenRouter API: gpt-oss-120b exposes NO input_cache_read field at all, and gpt-oss-20b exposes one at $0.03/M — identical to its prompt price, a 0% discount. So the headline $0.037/M advantage is illusory for a bot that re-ships a 3,000-token profile every turn. Empty language metadata, no Ukrainian leaderboard entry, worst measured Ukrainian tokenizer (1.82×). Its :batch tier is priced above standard.
llama.cpp / OllamaGGUF inference on CPU/Apple Silicon/consumer GPU; one-command local model runningMIT (both)128,284★ / 181,017★, both pushed 2026-09-152Development and Ukrainian voice prototyping only — the key open question (does this produce warm, idiomatic advisory Ukrainian) can only be answered by a native speaker sitting with the model interactively. Google publishes official Gemma 4 QAT GGUF, so the exact models are available. Do not ship it: Ollama's prefix-caching story is far weaker, and Q4 quality loss is disproportionately concentrated in lower-resource languages — your laptop prototype flatters the model in English and misleads you in Ukrainian, and the lang-uk leaderboard explicitly lists quantized evaluation as unfinished.
meta-llama/Llama-4-Scout-17B-16E-InstructMoE, 17B active, long context, multimodalother = Llama 4 Community Licence: acceptable-use policy, 700M-MAU threshold requiring a licence at Meta's sole discretion, naming obligations on derivatives160,609 dl/mo, created 2025-04-02, last modified 2025-05-22 (~16 months)1Explicit exclusion, on two independent grounds. Its official language list is exactly ['ar','de','en','es','fr','hi','id','it','pt','th','tl','vi']no ru, no uk. And Llama-3.1-8B-Instruct scores Global-MMLU-uk 0.4294 and IFEval-uk 0.3549 on the lang-uk leaderboard, against Gemma 4's 0.8664 / 0.8189. Non-OSI licence in exchange for a model that does not speak your customers' languages.
text-generation-inferenceHuggingFace's Rust/Python serving toolkitApache-2.0 (the HFOIL episode was reverted long before)ARCHIVED (archived: true), 10,885★, last push 2026-03-211Verified negative. Still recommended in pre-2026 architecture docs and by models answering from memory. If you inherit it, that is an immediate migration item, not a working foundation.

How to choose here. Two 2026 facts invert 2024-era instincts. Licensing flipped: Gemma 4 and Command A+ 05-2026 are Apache-2.0; Llama 4 is the restrictive one and does not list Russian or Ukrainian. And the Ukrainian evidence is now decisive: on the only public Ukrainian suite that exists, gemma-4-26B-A4B-it beats the best Ukrainian-specialist finetune by roughly 19 points of MMLU-uk while activating only 3.8B parameters per token — so the 2024 playbook of "use a language-specific finetune" is obsolete for Russian and half-obsolete for Ukrainian (MamayLM is still the best UK specialist, but it is a generation behind and stuck under non-OSI Gemma terms). On economics, measure in Ukrainian tokens, not English ones: I measured the same advisory message at EN 61 / UK 92 / RU 69 tokens on Gemma 4 (UK 1.51×, RU 1.13×), EN 60 / UK 99 / RU 72 on Qwen3.8 (1.65×), EN 55 / UK 100 / RU 71 on gpt-oss (1.82×) — and on an older 100k-vocab tokenizer the Ukrainian penalty is 2.39–2.49×, so large-vocab tokenization is an explicit model-selection criterion. Chars-per-token also falls from 4.37 to 2.82, so your context window fills ~1.5× faster than English intuition suggests. Finally, compare on cache tiers, not list price, and do not self-host for money: measured break-even is 165,185 conversations/month (1× L40S community, no redundancy) to 455,826 (2× L40S secure, HA), against ~100,000/month at 50,000 customers. Self-host for Ukrainian PII residency, latency control, cross-session KV persistence and serving your own LoRA — not to save $780/month that costs more than that in one engineer-week of GPU incident response.

Things that sound right but aren't (runtime). "TGI is the alternative to vLLM" — archived 2026-03-21. "Llama is the safe default open model" — non-OSI, 16 months stale, and Russian and Ukrainian are absent from its official language list. "gpt-oss is the cheapest" — it has no usable prompt-cache tier, so a prefix-heavy workload pays full rate on every turn, and its Ukrainian tokenizer is the worst measured. "Cohere is CC-BY-NC" — true of c4ai-command-a-03-2025, false of command-a-plus-05-2026. "Qwen is Apache" — model-by-model, and the cheap hosted Flash variant is one of the non-Apache ones. "Set enable_prefix_caching=True" — it's been the default; the knob that actually matters is prefix_cache_retention_interval, which defaults to 0. And the checkpoint name gemma-4-26B-A4B-it-qat-w4a16-ct does not exist — Google's real QAT repos are -qat-q4_0-gguf and -qat-q4_0-unquantized.


13. Channel policy comparison — who can initiate, windows, templates, cost

This table decides more architecture than any model choice. All figures verified from primary vendor documentation on 2026-09-15 except where marked.

TelegramWhatsApp (Cloud API)Viber
Who may initiateThe bot, to anyone who ever pressed /start or opened a deep link — forever, unlimitedOnly inside an open window, or with a pre-approved template. Opt-in required, and Meta's policy demands affirmative action (pre-ticked boxes, ToS-implied and purchase-implied consent all fail)Only to subscribers. One free welcome message within 5 minutes of conversation_started is the sole exception
Session windowNone. No expiry, ever24-hour customer service window, opened by a user message or call. Plus a 72-hour Free Entry Point window from a Click-to-WhatsApp ad or Page CTA24h session (widely reported; not described in Viber's own REST API docs — get it contractually). billing_status 0–4 free, 5 charged
Free in-windowEverythingVerbatim: "All non-template messages are free… Non-template messages can only be sent within an open customer service window." Utility templates also free in-window; service messages free for all businesses. In an FEP window any message type is freeSession messages free. Also billing_status 3–4 = "Free out of session 1:1 message/keyboard", up to an undocumented monthly threshold (error 23 freeMessagesExceeded proves it exists; the value is never stated)
Template rulesNo templates, no approval, no categoriesPre-approved templates in four categories: marketing / utility / authentication / service. Category assignment is Meta'sBusiness Messages: "All transactional messages must use registered templates approved by Viber." Bots: no templates, promotional permitted in-session with auto-opt-in when the customer initiates
Promotional limitsToS bans "unsolicited messages" and harassment. No price signal — annoyance and blocks are the only governorMarketing templates always charged, and capped per-recipient across ALL businesses — error 131049, "Per-User Marketing Template Message Limits… not delivered to maintain healthy ecosystem engagement." You cannot see or control the budget other brands consumed. (The widely-quoted "~2/user/day" figure appears in no Meta document — treat as unverified folklore.)Contractual: promotional content forbidden in the first message, and transactional messages must not contain discounts, promo codes, product recommendations or purchase incentives. So a "helpful tip that happens to suggest a product" is a contract breach, not a clever workaround
Approximate costZero. "bots are able to message their users at no cost." Paid Broadcasts (0.1 Stars/msg above 30/s) require ≥100,000 Stars balance and ≥100,000 MAU — unreachable and irrelevant herePer-message since 2025-07-01, charged only on delivered templates. No platform fee on Cloud API direct. BSP markup is the trap: Twilio charges $0.005 per message inbound AND outbound, including on messages Meta bills at zero — $800/month at 50k customers vs **€49/month flat** on 360dialogUkrainian resellers: transactional ~€0.010–0.016 (0.76 UAH), promotional €0.027–0.035 (1.65 UAH), with a **€200/month (10,000 UAH) minimum package ≈ 15,000 prepaid messages**. Those rate cards price Viber Business Messages, not the chatbot SKU, which is partner-gated and unpriced — the widely-quoted "€100–115/month" appears in no primary source
Throughput limits~1 msg/s per chat · ~30 msg/s broadcast · 20/min per group; 429 on breachMessaging limits tier 250 → 2,000 → 10,000 → 100,000 → unlimited unique out-of-window recipients per rolling 24h, portfolio-wide (adding numbers doesn't escape it), reviewed ~every 6h, gated on quality rating. In-window messaging is uncappedBroadcast 500 requests / 10s window; user-details lookup twice per 12h per user id
Delivery telemetryNone. No read receipts, no user online status, no typing indicator. Only message_reaction (requires bot admin and explicit allowed_updates opt-in — unreliable in 1:1 chats) and my_chat_member for block detectionsent / delivered / read as separate webhooks, plus a pricing object with billable / pricing_model / category. Read receipts are user-disableable, so the data is missing-not-at-randomdelivered, seen, subscribed, unsubscribed, conversation_started, failed. Caveat: "The seen callback will only be sent once when the user reads the unread messages, regardless of the number of messages sent" — any per-message read-rate feature on it is fabricated
Access / onboardingSelf-serve via BotFather, instantMeta Business verification; Cloud API direct or a BSPCommercial terms only since 2024-02-05, verbatim: "since 5.02.24, Viber bots can only be created on commercial terms" — via Rakuten Viber or a verified partner. Sender registration wants 6 logo variations, service name in UA + EN, website, industry, expected volume, one-way/two-way, destination countries, sample intro message. Viber review 2–3 days on top. Budget 2–4 weeks
IdentityStable user_idPhone numberNo phone number exposed. Opaque per-account user id — cross-channel identity resolution needs its own verification flow (code exchange or order-number challenge)
Webhook requirementsHTTPSHTTPS, Meta signatureCA-signed cert on the Sun Java trusted root list — self-signed explicitly unsupported. HMAC-SHA256 over the JSON body, key = auth token, header X-Viber-Content-Signature. Must return HTTP 200 or Viber retries 10 times with exponential intervals from 10s to 900s — duplicates are guaranteed, so dedupe on message_token before any state mutation
Known upcoming changeBot API 10.3 (2026-08-24); three feature releases in 20261 Oct 2026: Ukraine named explicitly"Higher utility and authentication rates, plus a new authentication-international rate." Marketing is NOT listed as changing for Ukraine. Rates revise quarterly (1 Jan / 1 Apr / 1 Jul / 1 Oct)Developer Agreement now requires developers to "allow the users to opt out of AI related features and ensure human oversight" and forbids AI-generated unsolicited bulk messaging

Reading the table. Three consequences fall straight out of it.

First, the cadence primitive is (one paid or earned window-opener) → (free multi-message burst inside the window) — never N independent paid broadcasts. The education payload is free on all three channels inside a session; only opening is metered. Batching a 3-message teaching burst into one opened window cuts an 8-touch education month by roughly 80–87%. Cap the drain at 3 messages per window-open event and 6 per 24h, or the burst reads as a wall of text. Apply an inversion test to every opener: would we still send this if in-window messages cost the same as templates? If no, it is a fee-dodge — legitimate openers are order status, a back-in-stock item they explicitly asked about, a genuine post-purchase check-in; illegitimate ones are manufactured urgency, content-free "are you there?", and questions whose answer you already know.

Second, the withholding policy is economically aligned with good selling on WhatsApp and economically neutral on Telegram — which inverts the usual intuition that patience costs money. A gate that waits for the customer to re-initiate keeps traffic inside the free window; a gate that fires on a timer spends a marketing template that may not even be delivered (131049). Conversely Telegram's zero cost means there is no price signal to restrain you, so enforce the attention budget more strictly there, not less. And Viber is fixed-cost-bound, not per-message-bound: with ~€200/month already sunk buying ~15,000 messages, under-sending Viber below roughly 1,500 active contacts is pure waste — ration only above ~2,000.

Third, the telemetry asymmetry will silently bias your relationship model if you pool channels. Read-latency and read-rate features simply do not exist on Telegram, so a single cross-channel R model makes every Telegram customer look systematically colder and they will never receive an offer. Normalise every engagement feature within channel by percentile rank, mark unavailable features missing rather than imputing neutral, and never reach for TDLib to recover Telegram read state — that requires a user account and is a ToS and ban risk on your primary channel.

Two operational rules that are easy to get wrong and expensive to fix: a stop signal on any channel must suppress all channels (WhatsApp's policy binds requests made "whether inside or outside WhatsApp", so you need one suppression ledger keyed on person, not per channel), and channel window state belongs in the veto layer, never as a score contributor — persist wa_window_expires_at, viber_subscribed, telegram_chat_member_status as typed columns the gate reads live, so "is this customer ready" and "am I currently allowed to message them, and will it cost me a paid template" remain two separate gates that must both pass.