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
| Aspect | cradle-server | Application Electron |
|---|---|---|
| Démarrage | node dist/server/index.js | Electron Forge + Vite, BrowserWindow |
| Répertoire de données | ~/.cradle/ | Répertoire de données OS |
| Auth | api_keys + scopes | Même table ; clé admin auto-bootstrap |
| Sélecteur de fichiers | Lève une exception (headless) | dialog.showOpenDialog |
| Livraison d'événements | EventEmitter (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 → envoyerLa 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
- Triage —
orchestrator(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èmerisk(règles déterministes, classifieur LLM, catégories, compétences). - LLM — une interface
ModelRunnerimplémentée parLlamaCppRunner(wrappantnode-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
ChatChannelavec 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.