Architecture

Vue d'ensemble de l'architecture

Comment Cradle se divise en un cœur indépendant de l'hôte avec deux points d'entrée — le serveur headless et l'application de bureau — et comment un message circule de bout en bout.

Cradle est un cœur unique et indépendant de l'hôte, avec deux façons de l'exécuter : en tant que daemon cradle-server headless, ou embarqué dans l'application de bureau Electron. Le cœur n'importe jamais Electron ; chaque point d'entrée fournit un HostAdapter avant que tout code du cœur ne s'exécute.

Deux points d'entrée, un seul cœur

              ┌──────────────── cœur (indépendant de l'hôte) ───────────────┐
              │  · DB (better-sqlite3 + sqlite-vec) + migrations             │
              │  · Orchestrateur de triage, routeur, agent-service           │
              │  · Système de risque (L1 règles + L2 classifieur)            │
              │  · Runners llama.cpp (chat + embedding)                      │
              │  · Canaux : api / telegram / widget                         │
              │  · RAG : chunker / ingest / retrieve / parsers               │
              │  · Auth : api_keys + scopes + audit                         │
              └─────────────────────────────────────────────────────────────┘
                  ▲                                        ▲
   ElectronHostAdapter                           NodeHostAdapter
   (app.getPath, dialog,                         (~/.cradle, EventEmitter,
    BrowserWindow.send)                           JSON stdout logger)
                  │                                        │
        src/electron/  (BrowserWindow + React UI)   src/server/  (HTTP API, pas de GUI)

Le contrat HostAdapter abstrait tout ce qui est spécifique à la plateforme : répertoire de données utilisateur, répertoire temporaire, chemins des ressources, livraison d'événements, sélecteurs de fichiers, et logging. L'application de bureau l'implémente avec les API Electron ; le serveur l'implémente avec ~/.cradle, un EventEmitter, et un logger JSON structuré (et pickFile lève simplement une exception, car il est headless).

Serveur vs application de bureau

Aspectcradle-serverApplication Electron
Démarragenode dist/server/index.jsElectron Forge + Vite, BrowserWindow
Répertoire de données~/.cradle/Répertoire de données OS
Authapi_keys + scopesMême table ; clé admin auto-bootstrap
Sélecteur de fichiersLève une exception (headless)dialog.showOpenDialog
Livraison d'événementsEventEmitter (SSE)webContents.send vers le renderer

Flux de message de bout en bout

Chaque message entrant traverse le même harnais, quel que soit le point d'entrée qui héberge le cœur :

Entrant → ChatChannel (telegram | widget | api)
  → unified IncomingMessage
  → persister le message + upsert ticket
  → TriageOrchestrator.processIncoming
      ├── RiskAssessor.layer1(rules)      → règles déclenchées + niveau
      ├── RiskAssessor.layer2(classifier) → { category, risk, confidence,
      │                                       reasoning, requiredSkills }
      ├── Router.pick(category, skills)   → Agent
      ├── si l'agent a des bases de connaissances :
      │     RAG.search(text, { kbIds, topK }) → extraits sources
      ├── AgentService.executeAgent(agent, turns, { sources })
      │     → construit le prompt (system + langue + sources + tour)
      │     → runner.generate (streamé) → brouillon
      ├── sauvegarde messages + risk_assessment
      └── décision par niveau final :
          ├── vert         → envoi automatique
          └── jaune | rouge → ticket en attente opérateur
                              → Boîte de réception → approuver / modifier / rejeter → envoyer

La propriété clé : un verdict rouge ou jaune n'est jamais envoyé automatiquement. Il devient un ticket dans la Boîte de réception opérateur, et un humain approuve, modifie ou rejette le brouillon avant qu'il n'atteigne le canal.

À l'intérieur du cœur

  • Triageorchestrator (pilote le flux), router (choisit un agent par rôle + intersection de compétences), agent-service (construction du prompt + exécution), et le sous-système risk (règles déterministes, classifieur LLM, catégories, compétences).
  • LLM — une interface ModelRunner implémentée par LlamaCppRunner (wrappant node-llama-cpp), un gestionnaire de modèles (téléchargements résumables vérifiés SHA-256), et un registre de modèles avec éviction LRU et chargement paresseux.
  • RAG — un chunker paragraphe-first, un ingest par lots idempotent, une recherche top-k sur sqlite-vec, et des parsers pour Markdown, TXT, PDF, DOCX, XLSX plus un crawler web.
  • Canaux — une interface commune ChatChannel avec Telegram (grammY), un widget intégrable (Fastify + WebSocket), et une API HTTP directe, tous convergents via un channel manager.

Le renderer de bureau

Le renderer Electron est une app React + Zustand avec des pages pour Dashboard, Boîte de réception, Détail du ticket, Agents, Modèles, Connaissances, Intégrations, Risque & Triage, et Paramètres — le côté humain du harnais où les opérateurs examinent et approuvent.

Pour approfondir, voir modèles et RAG.