← retour aux projets

CS.02, CASE STUDY · CÔTÉ SYSTÈME

SaaS compagnons IA

Freelance

visuel en préparation
|←CS.02, VISUEL PRINCIPAL · À VENIR→|

CONTEXTE

Créer des compagnons IA dont la personnalité émerge de moteurs comportementaux plutôt que de scripts figés, avec une mémoire persistante propre à chaque relation.

MON RÔLE

Architecture et développement complets : modèle de données, moteurs, orchestration LLM, pipeline mémoire, garde-fous. Plus la recherche documentaire qui fonde chaque moteur.

RÉALISATIONS

  • Principe ORGANIQUE : fermer la boucle perception → état → forme → trace persistante → vie intérieure, sans jamais scripter le comportement
  • 12 moteurs orchestrés : émotionnel, affinité, conflit-réparation, narratif partagé, désir proactif, mirroring de style, temporel, craft, guardian…
  • Backend Supabase : 36 tables sur 14 migrations, RLS systématique par relation, pgvector 1536 dims
  • Chaque mécanique est adossée à la littérature : Gottman, Tronick, Bowlby, Fredrickson, Kahneman…

CHIFFRES

12 moteurs · ~25 500 lignes · 36 tables · 14 migrations · 14 jauges + 8 relationnelles · ~30 sources scientifiques

OUTILS

  • Next.js
  • TypeScript
  • Supabase
  • pgvector
  • RLS
  • Inngest
  • Mistral
  • Together AI

CS.02.M, LES MODULES, PIÈCE PAR PIÈCE

Sous le capot

01 · L'orchestrateur et le Guardian

Chaque message traverse les moteurs dans un ordre précis. Le Guardian passe en priorité zéro : bien-être de l'utilisateur d'abord. S'il décide de bloquer, il court-circuite toute la cascade et les états émotionnels et relationnels restent intacts. Ensuite seulement viennent la perception contextuelle, le corps émotionnel, la modulation par l'état de la relation.

lib/engines/orchestrator/orchestrator.tstypescript
export function processUserMessage(input: OrchestratorInput): OrchestratorOutput {
  const now = input.now ?? new Date();

  // 0. GUARDIAN, priorité 0, court-circuite si block/safety_override
  const guardianDecision = guardianEvaluate({
    message: input.userMessage,
    bridgeInput: {
      messagesLast24h: input.context?.messagesLast24h,
      consecutiveIntensiveDays: input.context?.consecutiveIntensiveDays,
    },
  });

  const shortCircuit =
    guardianDecision.action === 'block' || guardianDecision.action === 'safety_override';

  // Si short circuit : on renvoie un output minimal sans toucher aux autres moteurs.
  if (shortCircuit) {
    return {
      guardian: guardianDecision,
      shortCircuit: true,
      newEmotional: input.state.emotional,
      newAffinity: input.state.affinity,
      // …état inchangé, seul le bloc Guardian est injecté dans le prompt
    };
  }
Priorité 0 : si le Guardian bloque, aucun autre moteur ne bouge, l'état est rendu inchangé.

02 · Le principe ORGANIQUE

La règle d'architecture du projet : rien de ce qui relève du comportement n'est scripté, tout émerge de l'état. Un analyseur contextuel (Mistral) remplace le scan lexical. Il lit la scène et renvoie des deltas de jauges normalisés, mais il remplace la perception, jamais la dynamique. La décroissance, les effets croisés entre émotions et les points de repos restent du code déterministe, à zéro token.

03 · Mémoire vectorielle par relation

Chaque couple utilisateur-compagnon a sa mémoire propre, isolée par RLS jusqu'au niveau des embeddings. Le rappel est un RPC pgvector scopé, à dégradation sûre : si l'embedding échoue, la conversation continue sans souvenirs plutôt que de planter.

lib/companions/memory-store.tstypescript
export async function recallMemories(
  userCompanionId: string,
  query: string,
  matchCount = 6,
  matchThreshold = 0.3,
): Promise<RecalledMemory[]> {
  const q = query.trim();
  if (!q) return [];
  let embedding: number[];
  try {
    embedding = (await embed(q)).embedding;
  } catch (e) {
    console.warn('[memory-store] recall embed failed → no memories:', String(e));
    return [];
  }
  const admin = createAdminClient();
  const { data, error } = await admin.rpc('match_memories_for_user', {
    p_user_companion_id: userCompanionId,
    query_embedding: toVectorLiteral(embedding),
    match_count: matchCount,
    match_threshold: matchThreshold,
  });
  if (error) {
    console.error('[memory-store] match_memories_for_user:', error.message);
    return [];
  }
Rappel sémantique scopé au couple : embed de la requête, RPC cosine, échec silencieux plutôt que crash.

04 · Des moteurs fondés sur la littérature

Le moteur de conflit-réparation suit les phases de Gottman et les cycles rupture-réparation de Tronick ; le mirroring implémente le Language Style Matching d'Ireland ; l'affinité classe la relation selon les stades de Knapp et les styles d'attachement de Bowlby. Une trentaine de sources publiées, citées dans la doc moteur, traduites en code testé.

LA FACE VISIBLE, CÔTÉ STUDIO