Fédération multi-kernel
Au-delà d'un seul tenant, un seul kernel ne suffit plus. La fédération expose N kernels Nika OS par /goal, routés via un triage RAG sémantique et fédérés via Redis pub/sub.
Pourquoi un seul kernel ne suffit pas
Un kernel Nika OS bien réglé peut absorber un usage soutenu pendant plusieurs mois. Mais une fois passé le seuil mono-tenant — plusieurs périmètres métier distincts, plusieurs équipes, plusieurs jeux de doctrines — les limites intrinsèques d’une instance unique apparaissent :
- Saturation de la fenêtre de contexte — même avec handover à 41 %, un kernel partagé entre R&D, opérations client, et veille marchés publics accumule trop de signal hétérogène. La compression RAG ne suit plus.
- Conflits de doctrine — deux périmètres peuvent exiger des invariants contradictoires (ex. tolérance aux écritures externes en R&D vs. validation systématique sur un dossier client). Une seule politique kernel ne peut pas honorer les deux à la fois.
- Isolation des credentials — un kernel BCUB3-IT et un kernel client Pulsa ne doivent pas pouvoir lire les credentials l’un de l’autre, même par accident.
- Cycle de mutation découplé — les mutations GEPA utiles à un périmètre ne le sont pas forcément à un autre. Un tournament unifié dilue les signaux.
La réponse architecturale est de spawner N kernels avec chacun leur
/goal, leurs primitives propres, et leur mémoire isolée — puis de les
fédérer via une couche partagée.
Le pattern : un kernel par /goal
Chaque kernel est une instance Nika OS isolée :
| Champ | Contenu propre au kernel |
|---|---|
/goal | Mission stratégique distincte (string courte) |
| Espace de pods | tmux nika-os-{kernel_id} ou namespace K3s dédié |
| Memory MD | Répertoire ~/.claude/projects/{kernel_id}/ séparé |
settings.json | Skills, hooks et permissions taillés sur mesure |
| Autonomie locale | Décisions internes sans consulter les autres kernels |
Quelques /goal typiques :
| Kernel | Périmètre |
|---|---|
kernel-alpha-bcub3-it | Opérateur IT BCUB3 (référence quotidienne) |
kernel-beta-bcub3-lab | R&D BCUB3 Lab — brevets KTW, sWELU, benchmarks |
kernel-gamma-client-mission | Missions client : mails, rapports, livrables |
kernel-delta-iot-edge | Contrôleur embarqué + données capteurs |
kernel-epsilon-veille-ao | Veille marchés publics et benchmarks sectoriels |
Chaque kernel possède son propre tmux, son contexte, ses skills tailored. Aucun n’a accès direct au contexte de l’autre ; ils communiquent uniquement via la couche fédération.
Le triage RAG sémantique
Un prompt utilisateur arrivant sur la fédération doit être routé vers le bon kernel. C’est le rôle du triage layer :
- Le prompt est embedé (modèle bge-small ou équivalent, 384 dimensions).
- Une recherche cosine est lancée sur la collection
nika_federation_goals(1 chunk = 1 kernel/goal+ ses tags sémantiques). - Le top-1 est sélectionné si son score dépasse un seuil de confiance.
- Sinon, le prompt est routé au kernel généraliste par défaut, qui peut
demander une clarification ou créer un nouveau kernel via la primitive
kernel_spawn.
Le triage est rapide (< 100 ms) et déterministe sur des prompts non ambigus. Pour les prompts hybrides (ex. “génère un plan technique ISO pour le client X”), le triage peut produire un split en plusieurs sous-prompts adressés à plusieurs kernels.
Fédération via Redis pub/sub
Les kernels échangent des signaux faibles via un bus partagé Redis :
| Channel | Direction | Usage |
|---|---|---|
nika:federation:directives | tout kernel | Signal kernel A → kernel B : voici un événement qui te concerne |
nika:federation:work_stealing | tout kernel | Annonce qu’un kernel a de la capacité libre et peut prendre du travail en attente |
nika:federation:health | tout kernel | Heartbeat + métriques agrégées (TTFL, error_rate, KTW Y) |
nika:federation:goals | triage | Mise à jour du registre des /goal actifs |
Aucun kernel ne partage sa fenêtre de contexte directement avec un autre. Le pub/sub transporte uniquement des signaux (références d’entité, IDs de tâche, scores, alertes). Pour transférer du contenu, on passe par le RAG partagé.
Le RAG partagé et la couche de triage
Le store Qdrant nika_vault est partagé entre tous les kernels, avec un
champ kernel_id filtré par défaut dans chaque requête. Un kernel ne lit
pas la mémoire d’un autre par défaut. Mais le triage maintient deux
collections complémentaires :
nika_federation_summaries— un résumé compact par kernel et par jour (1 chunk / kernel / 24h). Permet à un kernel de comprendre ce que font les autres sans lire leurs transcripts.nika_federation_goals— la base de connaissance qui pilote le triage. Chaque entrée décrit un kernel par son/goal+ ses domaines + ses exemples de prompts canoniques.
Le triage continu déduplique (cosine sim > 0.95 = drop), résume (chunks fréquents → super-chunk), et hiérarchise (strategy → project → job → task → atomic) pour garder le RAG actionnable.
Work-stealing entre kernels
Quand un kernel A est silencieux (pas de prompt en cours, file vide), il
peut souscrire à nika:federation:work_stealing et proposer ses
ressources :
- Kernel B publie un signal
task_availableavec un score de priorité. - Kernel A (capacité libre) répond
task_claimavec son score d’aptitude (calculé sur la similarité de son/goalavec la tâche). - Si A est le seul candidat et que sa similarité dépasse un seuil, il prend la tâche.
- Sinon, B garde la tâche dans sa propre file.
Ce pattern permet d’absorber les pics de charge sans avoir à pré-allouer de la capacité sur chaque kernel.
Architecture en diagramme
flowchart TB
U["Prompt utilisateur"] --> T["Triage RAG<br/>cosine sur /goal"]
T -->|"match BCUB3-IT"| KA["Kernel Alpha<br/>BCUB3-IT"]
T -->|"match BCUB3-LAB"| KB["Kernel Beta<br/>BCUB3-LAB"]
T -->|"match client"| KC["Kernel Gamma<br/>Client mission"]
KA <-->|"directives + signaux"| BUS[("Redis pub/sub<br/>nika:federation:*")]
KB <-->|"directives + signaux"| BUS
KC <-->|"directives + signaux"| BUS
KA -->|"read/write scopé"| RAG[("Qdrant nika_vault<br/>+ federation_summaries")]
KB -->|"read/write scopé"| RAG
KC -->|"read/write scopé"| RAG
T --> RAG
classDef prompt fill:#F5F1E8,color:#2C3E42,stroke:#7DB5A5,stroke-width:2px;
classDef triage fill:#E99971,color:#FDFBF8,stroke:#C97A55,stroke-width:2px;
classDef kernel fill:#7DB5A5,color:#FDFBF8,stroke:#5E9384,stroke-width:2px;
classDef bus fill:#2C3E42,color:#F5F1E8,stroke:#1A262A;
classDef store fill:#F5F1E8,color:#2C3E42,stroke:#A86640;
class U prompt;
class T triage;
class KA,KB,KC kernel;
class BUS bus;
class RAG store;
Les flèches pleines portent du contenu (prompts, embeddings, vecteurs). Les flèches pointillées portent des signaux faibles (alertes, scores, références).
Auto-handover via session_registry
Quand un kernel atteint son seuil de contexte (typiquement 41 % par défaut), il déclenche un handover automatique vers un kernel successeur :
- Le kernel courant sérialise son état essentiel dans
nika_federation_summaries(résumé) +session_registry(table des sessions actives ↔ entités). - Un kernel successeur est spawné avec
/goalidentique et l’entité parente injectée au boot. - Le
session_iddu nouveau kernel est lié à l’entity_idracine viasession_registry, ce qui permet aux pods enfants de retrouver leur lignée sans lire les transcripts.
Cette mécanique est décrite plus en détail dans la doctrine session ↔ hierarchy — le registry est le point de vérité.
Conditions de spawn d’un nouveau kernel
Quatre déclencheurs algorithmiques peuvent provoquer la création d’un kernel supplémentaire :
| Trigger | Mesure | Seuil par défaut |
|---|---|---|
| Saturation contexte | Moyenne du % contexte utilisé sur 24h | > 70 % |
| Diversité topique | Nombre de clusters disjoints dans le RAG du kernel | > 5 |
| Conflit de doctrine | Détection de deux doctrines mutuellement contradictoires actives | binaire |
| Demande explicite | Commande utilisateur /kernel new <goal> | — |
Aucun de ces triggers ne spawne un kernel sans validation humaine explicite — le coût (RAM, embeddings, mutation tournaments) est élevé et mérite un arbitrage.
Lifecycle : cinq verbes
Comme pour les pods, un kernel suit cinq verbes :
spawn → kernel_spawn(goal, scope, resource_limits)
invoke → kernel_invoke(kernel_id, prompt)
readjust → kernel_readjust(kernel_id, new_directive)
observe → kernel_observe(kernel_id, since, query?)
kill → kernel_kill(kernel_id, reason)
readjust permet d’injecter une directive en cours de session sans tuer
ni respawn le kernel — l’équivalent d’un patch sur le contexte. Le
verbe est utile quand le périmètre métier glisse légèrement sans changer
le /goal.
Quand orchestrer plusieurs kernels en parallèle
Trois cas d’usage canoniques :
- Burst de charge — ingestion massive (centaines de PDF, vidéos, data sets). Spawn d’un kernel worker temporaire le temps du burst, puis decommission.
- Diversification de doctrine — un kernel R&D peut tolérer l’exploration agressive, un kernel client exige la validation systématique. Les séparer permet d’optimiser chacun pour son métier.
- Région data — un kernel hébergé en France pour les données client sensibles, un autre en zone neutre pour la veille publique. Le triage route selon la nature du prompt.
Coût et bottlenecks
Chaque kernel a un coût fixe (RAM ~512 Mo – 2 Go selon les skills chargées, CPU pendant les bursts d’embedding) et un coût variable (tokens API ou GPU à la décision). Les bottlenecks observés sur un VPS mutualisé :
- RAM — limite primaire. Sur un VPS 29 Go, 14 à 58 kernels parallèles selon l’enveloppe par kernel.
- CPU — surtout sur les bursts Qdrant et les hooks Python lourds.
- Network — rate limits provider (Anthropic, OpenRouter, Mistral).
- Disk — croissance du Qdrant. La rétention TTL 24h sur
nika:pod_streamsmitige.
L’isolation par pod K3s ou par namespace permet de poser des limits
durs (cgroups Linux) et d’éviter qu’un kernel sature la machine entière.
Voir aussi
- Kernel et pods — le contrat de spawn d’un pod s’applique aussi à un kernel.
- IPC et bus — Redis Streams, consumer groups, JSONL.
- Observabilité et contrôleurs — métriques agrégées
cross-kernel via
nika:federation:health. - Doctrines — antifragilité, kernel/pod split, agnosticisme multi-CLI s’appliquent à la fédération.