Observabilité et contrôleurs

Comment Nika OS observe ses propres pods sans polluer son contexte, et le contrôleur en ligne qui pilote les hyperparamètres à partir des signaux observés.

L’observabilité comme primitive

Un système agentique non observable est un système qu’on ne peut pas piloter. Nika OS expose donc l’observabilité comme une primitive de premier ordre, pas comme un add-on.

Trois sources de signal coexistent :

  1. Le stream Redis par pod — chaque appel d’outil produit un XADD.
  2. Le bus JSONL — append-only, audit trail permanent.
  3. Le fleet state — dictionnaire Redis nika:fleet:state maintenu par le daemon fleet_consumer.py.

Le fleet daemon

fleet_consumer.py tourne en permanence dans un tmux dédié (nika-os:FLEET-CONSUMER). Il :

  • consomme le stream cg:worker du feed entités ;
  • maintient un état agrégé par pod (status, last tool, error count, loss score) ;
  • expose cet état via nika:fleet:state (HASH Redis lu par fleet_status.py).

Sans ce daemon, les pods sont aveugles entre eux. Avant toute action susceptible de conflit inter-pod (édition de fichier partagé, déploiement, changement de schéma), un pod doit appeler :

python3 scripts/fleet_status.py --active

Cette commande retourne en quelques millisecondes la liste des pods actifs, leur entité, leur dernière action, et leur loss score. Aucune lecture de context window n’est nécessaire.

Pod streaming sans pollution de contexte

Une des erreurs classiques en orchestration multi-agent est de demander à l’orchestrateur de lire les transcripts de chaque pod pour comprendre ce qu’ils font. Cela charge la fenêtre de contexte de l’orchestrateur en contenu peu prioritaire (logs détaillés), et la sature rapidement.

Nika OS pose la séparation suivante :

  • Les événements significatifs (création, complétion, échec, escalade) arrivent au kernel via le feed entités.
  • Les logs détaillés restent en local sur le pod (logs/) et en Redis Streams. Le kernel les lit à la demande, jamais par défaut.
  • Une skill dédiée (pod-observe) permet au kernel de faire un semantic search sur les tool calls d’un pod précis, sans charger son transcript.

Cette discipline permet au kernel de coexister avec 6 pods workers actifs en parallèle sans saturer sa propre fenêtre de contexte.

Le contrôleur en ligne

Observer ne suffit pas : il faut agir sur ce qu’on observe. Pour les hyperparamètres qui ont une récompense mesurable au moment de la décision (température du modèle, nombre de retries d’un pod, concurrence, nombre de chunks RAG), Nika OS utilise un contrôleur en ligne.

À chaque mesure, le contrôleur combine trois ingrédients :

  1. une estimation de savoir si l’écart observé est du bruit ou une dérive réelle ;
  2. le coût de l’écart à la cible — une perte qui croît non-linéairement avec la distance, ce qui permet de prioriser : un écart proche coûte peu, un écart lointain coûte beaucoup ;
  3. une allocation de l’intervention qui croît avec la confiance — on intervient peu quand le signal est incertain, fermement quand il est franc.
flowchart LR
    M["📊 Mesure y(t)"] --> W["Bruit ou dérive ?<br/>P(dérive réelle)"]
    M --> T["Coût de l'écart<br/>croît avec la distance"]
    W --> K["Allocation<br/>∝ confiance"]
    K --> D{"Décision"}
    T --> D
    D -->|"P faible"| N["🤫 Bruit<br/>ne rien faire"]
    D -->|"P intermédiaire"| S["🧭 Doute<br/>correction douce<br/>+ attendre confirmation"]
    D -->|"P élevée + coût fort"| F["⚡ Dérive<br/>correction ferme<br/>+ immédiate"]

    classDef input fill:#F3EADE,color:#2C3E42,stroke:#7DB5A5,stroke-width:2px;
    classDef calc fill:#FAF5EC,color:#2C3E42,stroke:#A86640,stroke-width:1.5px;
    classDef decide fill:#2C3E42,color:#FDFBF8,stroke:#1A262A,stroke-width:2px;
    classDef noop fill:#5E9384,color:#FDFBF8,stroke:#5E9384;
    classDef soft fill:#E99971,color:#FDFBF8,stroke:#C97A55;
    classDef firm fill:#C97A55,color:#FDFBF8,stroke:#A86640;
    class M input;
    class W,T,K calc;
    class D decide;
    class N noop;
    class S soft;
    class F firm;

Trois zones distinctes émergent :

  • Bruit (faible probabilité de dérive) — ne rien faire.
  • Doute (zone intermédiaire) — correction douce, attendre confirmation.
  • Dérive (probabilité élevée et coût fort) — correction ferme et immédiate.

C’est ce comportement qui rend le système antifragile au sens de Taleb : au lieu d’osciller à chaque bruit, il attend d’avoir l’information statistique nécessaire pour intervenir, et apprend de chaque dérive observée.

Application à l’orchestration d’agents

Le contrôleur en ligne est utilisé pour piloter les pods. La métrique observée est une loss score calculée sur les tool calls et les sorties du pod.

Quand la loss d’un pod franchit un seuil, le système :

  1. Ne kill pas le pod immédiatement (équivalent du « ne rien faire » dans la zone bruit).
  2. Si la loss continue de croître au-delà du seuil intermédiaire, mute les primitives mutables (température, retry policy, prompts).
  3. Si la loss reste élevée après mutation, escalade : WhatsApp alert → bus JSONL → review request à l'opérateur humain.

Le système d’alerting pod (pod-alerts skill) lit le stream nika:kernel:alerts rempli par le pod stream embedder. Au début d’une session de triage, l’opérateur peut demander « des alertes ? » et recevoir la liste des pods en problème avec leur loss observée.

Pour aller plus loin

Le contrôleur en ligne n’est qu’un des trois mécanismes d’évolution du système. La page Méta-curateur et évolution décrit comment le harnais — prompts, skills, hooks, tools et mesures de succès — évolue par type de tâche, et comment la supervision continue et la consolidation nocturne se complètent.

Juger un livrable : la difficulté du “Definition of Done”

Observer un pod, c’est facile (status, latency, exit code). Juger s’il a réellement accompli sa tâche, c’est nettement plus difficile. Cette difficulté est au cœur de la R&D Nika OS.

Pourquoi le DoD est piégeux

Pour un job classique (build, tests, déploiement), le DoD est binaire : exit 0 ou pas, tests verts ou pas. Pour un job agentique, le DoD est multi-dimensionnel :

  • Le pod a-t-il produit les bons fichiers (cohérence avec le brief) ?
  • Les fichiers ont-ils la qualité attendue (lisibilité, complétude) ?
  • Les invariants métier sont-ils respectés (pas de PII, pas de claims faux, pas d’engagement irréversible) ?
  • Le style correspond-il (tone professionnel, formulation cohérente) ?
  • Y a-t-il eu des effets de bord non documentés (commit non listé, modification de fichier hors scope) ?

Aucun de ces critères n’est observable par un simple exit code. Tous nécessitent un jugement sémantique.

LLM-as-judge — la couche d’évaluation

Notre primitive pod_handoff applique un jugement structuré à la fin de chaque pod (hook SubagentStop). Le verdict est l’un de :

VerdictSensAction
DONEDoD atteint, livrables produits, invariants respectéskill (no-op)
PARTIALUne partie des livrables manque ou a un défaut mineurrelaunch avec brief enrichi
STUCKLe pod est resté bloqué sans produirerelaunch retry_count++
DORMANTSTUCK longue durée sans transcript ni session — projet multi-jours, pas erreurarchive sans escalade
NO_DODBrief sans Definition of Done expliciteescalation humaine

Pourquoi un LLM (et pas seulement des règles)

Plusieurs critères sont implémentés en règles déterministes (regex, comptage de fichiers, lecture de frontmatter). Mais d’autres exigent un modèle de langage capable de comprendre le brief, comparer aux outputs, et émettre un score nuancé. Cette couche est nécessairement subjective. Pour réduire le biais :

  • Cross-CLI judge — le même output est jugé par 2-3 agents CLI différents (par exemple Codex, Gemini CLI, ou tout CLI agentique compatible MCP) ; on prend la médiane ou le consensus.
  • Algorithmic check before LLM — on commence par les vérifications déterministes (factualité contre RAG, schéma JSON, longueur, etc.) ; le LLM n’est appelé que sur l’incertitude résiduelle.
  • Audit log obligatoire — chaque verdict LLM est tracé en JSONL avec le prompt, l’output, la confiance estimée et le verdict humain a posteriori quand il y en a un. Le tournoi GEPA utilise ces décalages pour faire évoluer le prompt de jugement lui-même.

L’exploration comme premier-citoyen

Conséquence directe : on ne peut pas optimiser ce qu’on ne peut pas mesurer. Nika OS accepte donc une part irréductible d’exploration :

  • Plusieurs CLI répondent en parallèle au même prompt (tournoi multi-pods) pour générer de la diversité d’outputs.
  • Un méta-pod déterministe combine les sorties via algorithmes classiques (consensus voting, outlier detection, factual check) avant d’engager le LLM-as-judge.
  • Le contrôleur pondère la confiance du jugement : si la dispersion est forte (faible accord entre juges), on diminue l’allocation et on intervient moins fortement. Si l’accord est élevé, on agit avec confiance.

Cette philosophie — bénéficier de la variabilité plutôt que la combattre — est l’expression opérationnelle de la doctrine d’antifragilité décrite plus haut. Un pod individuel peut se tromper ; un essaim de pods couplé à un évaluateur multi-perspectives est plus difficile à tromper.