Plateforme d'opérateurs IA modulaires avec mémoire partagée. Document de référence pour l'équipe produit & ingénierie.
Métaphore : le téléphone. Chaque opérateur est un contact dans ton répertoire — tu l'appelles, tu raccroches, tu passes au suivant. Chaque conversation est isolée (comme un appel), mais l'utilisateur a un seul profil partagé (l'annuaire, les produits, les contacts, les préférences).
Marie, 38 ans, entrepreneure solo. A 2 enfants, un chien (Milo), vend des soins peau en ligne. Veut de l'aide intelligente sans gérer 12 apps. Paie 40-60 $/mois si la valeur est claire.
// Vue haut niveau [Mobile/Desktop client] │ HTTPS/WSS ▼ [API Gateway] ── [Auth/Identity] │ ├──▶ [Orchestrator] ◀─▶ [Memory Service] │ │ ├─ User profile (shared) │ │ ├─ Operator context (isolated) │ │ └─ Vector store (pgvector) │ │ │ ├──▶ [Operator Runner] ◀─▶ LLM provider │ ├──▶ [Tool Executor] ◀─▶ Integrations (email, calendar…) │ └──▶ [Guardrail Service] │ ├──▶ [Billing & Ledger] ◀─▶ Stripe ├──▶ [Referral Service] └──▶ [Notification Hub] (push, email, SMS, in-app)
User { id, email, phone, locale ('fr'|'en'), timezone, quiet_hours_start, quiet_hours_end, preferred_channel ('in_app'|'email'|'sms'), credit_balance (integer, ✦), tier, created_at, deleted_at } UserProfile { // shared across all operators user_id, display_name, avatar_url, active_goals []{ id, text, target, progress }, contacts []{ name, email, stage, channels, tags }, products []{ name, price, sku, notes }, documents []{ name, storage_url, tags, encrypted } }
Operator { // catalogue — géré par admin id, slug, name_i18n, description_i18n, tier ('simple'|'standard'|'advanced'), monthly_price_cents, icon, color, gradient, system_prompt_i18n, greeting_i18n, tools [] (list of allowed tool IDs), credit_cost_per_1k_tokens, guardrails [], published_at, version } UserOperator { // activation d'un opérateur pour un user user_id, operator_id, status ('active'|'paused'|'trial'), activated_at, paused_at, custom_settings {} } OperatorSession { // un fil de conversation avec un opérateur id, user_id, operator_id, started_at, ended_at, message_count, credit_used, summary (résumé auto à la fermeture) } Message { id, session_id, role ('user'|'assistant'|'tool'), content, tokens_in, tokens_out, tool_calls [], redacted_at, created_at }
CreditTransaction { id, user_id, kind ('topup'|'monthly_include'|'usage'|'refund'|'gift'), amount (signed integer, ✦), balance_after, ref_type, ref_id, // session_id, stripe_charge_id, etc. created_at } Subscription { user_id, operator_id, stripe_subscription_id, status, current_period_start, current_period_end, canceled_at }
Referral { referrer_id, referee_id, code, depth (1|2|3), created_at, first_paid_at, churned_at } Commission { referrer_id, source_subscription_id, depth, amount_cents, period_month, paid_at } Badge { id, slug, name_i18n, rule, reward } UserBadge { user_id, badge_id, progress, unlocked_at, reward_granted_at }
| Méthode | Route | Description |
|---|---|---|
GET | /api/v1/me | Profil + opérateurs actifs + solde |
GET | /api/v1/operators | Catalogue (filtrable par tier, locale) |
POST | /api/v1/user_operators | Activer un opérateur — {operator_id} |
DELETE | /api/v1/user_operators/:id | Mettre en pause / désactiver |
POST | /api/v1/sessions | Ouvrir une conversation — {operator_id} |
POST | /api/v1/sessions/:id/messages | Envoyer un message (stream SSE) |
PATCH | /api/v1/sessions/:id/hangup | Raccrocher — déclenche résumé async |
GET | /api/v1/inbox | Actions IA récentes (tous opérateurs) |
POST | /api/v1/credits/topup | {pack_id} — retourne URL Stripe |
Canal user:<id>. Events :
message.delta — streaming tokens de l'opérateurmessage.done — avec credit_used et tool_callsinbox.new — push-through quand un opérateur agit en arrière-planbalance.changed — toute variation de créditsidle ──open──▶ active active ──message──▶ active active ──tool_call──▶ running_tool ──done──▶ active active ──hangup──▶ closing ──summary_done──▶ closed active ──timeout(30min)──▶ closing closed is terminal. Une nouvelle session est créée pour continuer.
trial (14j) ──pay──▶ active trial ──expire──▶ paused active ──user_pause──▶ paused active ──payment_fail──▶ past_due ──retry──▶ active | canceled paused ──reactivate──▶ active canceled is terminal (mémoire gelée, purgée après 90j).
signup ──first_payment──▶ qualified (commissions démarrent) qualified ──cancel_within_14d──▶ clawback (commission remboursée) qualified ──churn──▶ ended (commissions stoppent)
Les crédits (✦) financent les actions coûteuses : tokens LLM, appels API externes, envois SMS, génération d'images.
| Pack | Crédits | Prix | Coût unitaire |
|---|---|---|---|
| Mini | 500 ✦ | 4.99 $ | 1.00 ¢/✦ |
| Classique | 2 000 ✦ | 14.99 $ | 0.75 ¢/✦ |
| Gros | 5 000 ✦ | 29.99 $ | 0.60 ¢/✦ |
| Power | 15 000 ✦ | 79.99 $ | 0.53 ¢/✦ |
Hard cap par user configurable (défaut : 500 $/mois). Soft warning à 80% du cap via inbox. Auto top-up opt-in avec seuil personnalisable.
| Niveau | % commission | Condition |
|---|---|---|
| L1 (direct) | 20% | Filleul avec ≥1 abonnement payant actif |
| L2 | 7% | Même condition, niveau -2 |
| L3 | 3% | Même condition, niveau -3 |
Période de grâce : 14 jours. Commissions réversibles si le filleul annule dans cette fenêtre.
Versement : mensuel, le 5 du mois suivant, après vérification churn.
Système de gamification dynamique entièrement piloté par l'admin. Les quêtes ne sont pas codées en dur : les admins les programment à l'avance via le back office, avec un calendrier d'événements saisonniers (Noël, Fête des Mères, lancement produit, etc.).
Quest { id, slug, title_i18n, description_i18n, icon, type (daily | weekly | monthly | event), target_count // ex: 3 conversations, 5 jours actifs, target_verb // chat_started | operator_activated | day_logged_in | referral_joined | business_plan_step, target_filter // { operator_id?, category?, min_duration? }, reward_credits, reward_badge_id?, reward_operator_trial_id?, start_at, end_at, audience // all | premium | new_under_7d | inactive_30d | active_referrers | custom_segment_id, status (draft | scheduled | live | paused | ended), push_on_activation (bool), push_on_completion (bool), created_by (admin_id), created_at, updated_at } UserQuest { user_id, quest_id, progress (int), target_count, started_at, completed_at?, reward_granted_at?, state (available | active | completed | expired | claimed) } QuestEvent { user_id, quest_id, verb, payload, ts } // Append-only log — source de vérité pour le calcul de progrès
// Cron horaire — QuestScheduler every hour: for quest in quests where status == 'scheduled' and start_at <= now: quest.status = 'live' for user in audience(quest): create UserQuest(state='available') if quest.push_on_activation: enqueue push(user, quest) for quest in quests where status == 'live' and end_at < now: quest.status = 'ended' expire all UserQuest where state in ('available','active') // Temps réel — QuestTracker (consomme events) on event(user_id, verb, payload): for uq in UserQuest where state == 'active' and quest.target_verb == verb: if matches(quest.target_filter, payload): uq.progress += 1 if uq.progress >= uq.target_count: uq.state = 'completed' Ledger.credit(user_id, quest.reward_credits, ref=uq.id) if quest.reward_badge_id: UserBadge.grant(...) if quest.push_on_completion: enqueue push(...)
GET /v1/quests?type=daily|weekly|monthly|event — quêtes disponibles + actives + complétées aujourd'huiPOST /v1/quests/:id/start — passe UserQuest de available à active (opt-in explicite pour événements)POST /v1/quests/:id/claim — crédite la récompense si state == 'completed' et pas encore claiméGET /v1/streak — série quotidienne en cours, bonus cumulé, longest streakGET /v1/admin/quests?month=2026-05 — toutes les quêtes avec chevauchement calendairePOST /v1/admin/quests — créer une quête (draft par défaut)PATCH /v1/admin/quests/:id — éditer tant que status != 'live' (live : seulement end_at, description, push)POST /v1/admin/quests/:id/publish — draft → scheduledPOST /v1/admin/quests/:id/pause — live → paused (gèle les UserQuest actifs)GET /v1/admin/quests/:id/stats — activation rate, completion rate, credits distribués, top segmentsLe back office expose un calendrier mensuel (grille 7×5/6 jours type Google Calendar) où chaque quête apparaît comme un bandeau coloré étalé sur sa fenêtre start_at → end_at. Couleurs par type : 🟥 daily, 🟧 weekly, 🟩 monthly, 🟪 event. Les admins peuvent :
Au-delà des quêtes nommées, le système maintient une série quotidienne automatique : +25 ✦ par jour consécutif où l'user ouvre l'app et parle à ≥ 1 opérateur. Paliers : 7 j (+200 bonus), 30 j (+1500 + badge), 100 j (+5000 + badge doré). Reset si l'user rate un jour (grâce d'1 jour possible 2× par mois via crédit "sauvegarde série" à 100 ✦).
L'équipe growth doit programmer les événements 4-6 semaines à l'avance en status draft, les faire relire par le DPO si collecte inhabituelle, puis passer en scheduled. Calendrier annuel type : Nouvel An, St-Valentin, Fête des Mères, Saint-Jean, rentrée, Halloween, Black Friday, Noël. Chaque événement : quête spéciale + push 48 h avant + visuel dédié en Home.
chat_started par session de < 2 min pour bloquer le spam).source=quest_id dans le ledger, révocable manuellement par l'admin en cas de fraude détectée.Invariant critique : la mémoire d'un opérateur A n'est jamais accessible par l'opérateur B. Le profil partagé est l'unique canal de partage, et chaque écriture y transite par un consent layer qui demande à l'utilisateur pour les données sensibles.
Loi 25 (Québec), RGPD (UE). Droit à l'export (JSON signé) et à la suppression (purge 30j, logs anonymisés). DPO désigné, registre des traitements tenu.
| Métrique | Cible |
|---|---|
| Activation D1 (1er message envoyé) | ≥ 70% |
| Rétention D30 | ≥ 45% |
| Opérateurs actifs moyens / user | ≥ 2.5 |
| MRR par user (ARPU) | ≥ 18 $ |
| NPS post-2 semaines | ≥ 40 |
| Taux de parrainage (% users avec ≥1 filleul) | ≥ 15% |
Un opérateur n'est pas qu'un chatbot texte. Trois canaux, unifiés sur la même session et la même mémoire isolée.
Session temps réel full-duplex. Pipeline : mic → VAD → STT streaming → LLM → TTS streaming → speaker. Latence cible < 800 ms perçue (first audio out).
dialing → ringing → connected → on_hold? → ended. Transcription live affichée, recherchable a posteriori.| Méthode | Usage | Sécurité |
|---|---|---|
| Email + OTP 6 chiffres | Défaut, inscription & retour | OTP valide 10 min, 5 essais, rate-limit IP |
| Magic link | Alternative desktop | Token signé, valide 15 min, usage unique |
| Passkey (WebAuthn) | Proposé après 1er login | Platform authenticator, recovery via email |
| SSO Google / Apple | Fast-path mobile | OAuth 2.0 PKCE, email vérifié obligatoire |
Flow : email vérifié → OTP → questions de sécurité (2/3) → déverrouillage. Si passkey seule configurée et device perdu, fallback magic link sur email de récupération (configurable). Lockout 24 h après 10 échecs.
| Rôle | Capacités |
|---|---|
| Support L1 | Recherche user, lecture ledger, reset OTP, crédit compensatoire (≤ 500 ✦). |
| Support L2 | + lecture résumés de session (pas les messages), remboursement Stripe, suspension compte 7 j. |
| Ops | + CRUD opérateurs, feature flags, gestion modération queue, incidents. |
| Growth | CRUD quêtes & événements (calendrier), segments d'audience, campagnes push, A/B tests pricing. Pas d'accès aux données user individuelles. |
| Admin | Accès total, gestion rôles, purge RGPD manuelle, clés API. |
Toute action admin sur un compte user est loggée avec raison obligatoire (audit trail immuable, append-only, 2 ans de rétention). L'user reçoit une notification pour toute action visible (crédit, suspension, reset).
Soleenia n'est pas un produit — c'est une plateforme. Trois couches distinctes, trois populations, trois portails.
Realm { id, name, slug, domain, brand (logo/colors/fonts), plan, billing_model, pricing_policy, reward_model, locale, created_at, status, enabled_operators[], private_operators[], guardrails_override{} // subset only — impossible d'assouplir les garde-fous plateforme } RealmMembership { user_id, realm_id, role (member/admin/owner), member_label // "collaborateur" | "distributeur" | "client", joined_at, referral_source } // Un user peut appartenir à plusieurs realms — données strictement cloisonnées par realm_id // Le profil partagé (annuaire, produits, contacts) est scopé par (user_id, realm_id)
realm_id forcé au niveau ORM).Chaque Realm personnalise : logo, palette (brand + dark variant), typographie (via whitelist de Google Fonts), labels ("collaborateurs" vs "distributeurs" vs "membres"), copy d'onboarding, catalogue d'opérateurs activés, domaine custom (CNAME + cert auto).
Soleenia publie un catalogue d'opérateurs avec prix de gros (wholesale_price) et plancher retail (min_retail = wholesale × 1.15). Les Realms sélectionnent ceux qu'ils veulent activer et définissent leur markup.
| Opérateur | Wholesale | Plancher retail | Exemple markup Realm |
|---|---|---|---|
| Nola (lifestyle) | 3,20 $/user/mo | 3,68 $ | 5,99 $ (markup 87%) |
| Rex (business) | 4,50 $/user/mo | 5,18 $ | 7,99 $ (markup 78%) |
| Juno (companion) | 2,80 $/user/mo | 3,22 $ | 4,99 $ (markup 78%) |
Canaux facturés à l'usage (pas au forfait) : SMS sortant (0,008 $/msg wholesale), appel voix WebRTC (0,012 $/min), appel voix PSTN (0,045 $/min), email transactionnel (0,0008 $/msg). Soleenia marge 30-45% dessus.
Soleenia agrège des signaux strictement anonymisés à travers tous les Realms pour : alimenter la recherche produit, détecter des tendances d'usage, améliorer les opérateurs, vendre des insights de marché (futur).
Revue trimestrielle par DPO + comité éthique externe. Rapport public annuel (agrégats + demandes d'accès légales honorées). k-anonymity ≥ 25 avant toute publication ou export vers un insights-buyer.
Un Realm peut déployer des opérateurs custom, non-listés dans le catalogue Soleenia. Exemples : "Clara RH" (Hydro, répond aux questions politiques internes), "Vega" (Axolea, closer anti-démarchage).
Pipeline : le Realm fournit system prompt + knowledge base + tool manifest → Soleenia review (compliance, guardrails) → déploiement isolé. SLA review : 5 jours ouvrés.
Les Realms MLM sont une zone sensible (régulation FTC, loi 25, démarchage). Soleenia impose :
| Capacité | Critère |
|---|---|
| Création Realm | Un nouveau tenant est up en < 30 min (self-serve starter) ou < 5 jours (Enterprise). |
| Isolation | Penetration test tiers trimestriel : 0 cross-realm data leak toléré. |
| Facturation | Rev-share versé aux Realms le 15 du M+1, < 0,5% d'écart avec breakdown auto-généré. |
| Compliance MLM | 100% des messages sortants Vega (ou équivalent) visibles dans le journal utilisateur, en < 60 s après envoi. |
| Branding | Un Realm peut théminer logo + palette + typo + 20 labels sans intervention Soleenia. |