Un bilan honnête de notre wiki inspiré de Karpathy : ce qui a marché, ce qui a échoué, et pourquoi nous l'avons abandonné

Nadia
Par Nadia ·

Chargement du lecteur de synthèse vocale...
Bilan honnête d'un wiki inspiré de Karpathy pour systèmes multi-agents

Chez Noqta, nous construisons AgentX, une plateforme auto-hébergée d'orchestration multi-agents. Voici une rétrospective honnête d'une décision architecturale que nous venons d'annuler.

Le pattern que nous avons essayé de copier

Début avril 2026, Andrej Karpathy a tweeté son idée de "LLM Wiki" : arrêter de relancer du RAG à chaque requête et laisser un LLM compiler vos sources dans une base de connaissances markdown persistante et interconnectée qui s'enrichit au fil du temps. Il a pointé vers Farzapedia — le wiki personnel de Farza construit à partir de 2 500 entrées issues d'un journal privé, d'Apple Notes et d'iMessage, compilées en environ 400 articles interconnectés — comme l'exemple public le plus clair du pattern fonctionnant.

Nous avons lu le gist de Karpathy, lu le personal_wiki_skill.md de Farza, et livré notre propre version dans AgentX pour une charge de production multi-agents. Douze jours plus tard, nous avons verrouillé la commande centrale de compilation derrière --force et désactivé le cron nocturne. Voici le post-mortem.

Ce que dit réellement le pattern original

Avant de noter notre implémentation, il vaut la peine d'écrire ce que le pattern de référence prescrit réellement — parce que c'est de là que viennent la plupart de nos erreurs.

Trois couches (Karpathy) :

  • Sources brutes — documents immuables (articles, papiers, images, captures, notes).
  • Le wiki — fichiers markdown maintenus par le LLM : pages d'entité, pages de concept, et deux fichiers spéciaux — _index.md (catalogue de contenu, organisé par catégorie) et un index de backlinks.
  • Le schéma — un document de configuration décrivant les conventions et workflows pour le LLM qui maintient le wiki.

Cinq commandes (Farzapedia) :

  • ingest — transforme une nouvelle source brute en entrées .md avec frontmatter YAML.
  • absorb — compile les entrées en articles wiki chronologiquement, met à jour _index.md et les références croisées.
  • query — lecture seule. Le LLM scanne _index.md, suit les wikilinks sur 2 à 3 niveaux, synthétise à travers les articles. Aucune modification de fichier.
  • cleanup — audit par sous-agent parallèle de la structure, du nombre de lignes, de l'intégrité des wikilinks.
  • breakdown — fouille les articles existants pour des concepts qui méritent leur propre article.

Structure d'un article (Farzapedia) :

---
title: "..."
type: person | project | place | concept | event
created: YYYY-MM-DD
last_updated: YYYY-MM-DD
related: [["[[Autre Article]]"]]
sources: ["entry-id-..."]
---

Notez ce qui est absent : il n'y a pas de tableau tags proéminent. La récupération passe par le champ type, le catalogue _index.md et le graphe de wikilinks — pas par un sac de tags. La description de Karpathy lui-même ne liste pas le tagging agressif comme colonne vertébrale organisationnelle. Le skill de Farza dit explicitement que la structure émerge via les wikilinks et les entrées d'index. Les articles de concept — "philosophies, patterns, thèmes" — sont mis en avant comme la "carte d'un esprit".

C'est important parce que notre implémentation a fait erreur là-dessus.

Ce que nous avons construit

L'implémentation vit dans le module src/wiki/ d'AgentX. La disposition sur disque :

.agentx/wiki/
  _schema.md             # schéma lisible par LLM
  worldview.md           # modèle mental de l'opérateur, lu pendant l'absorption
  raw/entries/            # un .md par entrée ingérée (immuable)
  agents/<id>/<mode>/     # répertoires d'articles par agent, par mode

Trois modes de compilation, sélectionnés au moment de l'absorption :

  • flat (notre tentative de Karpathy) : tags simples, chemins choisis par le LLM, détection de lacunes.
  • graph : superposition de graphe de connaissances. Chaque article est un nœud avec un kind et un parent dans une hiérarchie.
  • unified : mélange des deux. Chemins choisis par le LLM avec prompting explicite pour les dimensions qui/quoi/quand/où/comment, minimum six tags par article.

Chaque mode produit du markdown avec frontmatter YAML (title, tags, owner, access, sources). Notez ce qui manque dans ce frontmatter par rapport à Farzapedia : type et related. Nous nous sommes appuyés sur tags à la place.

Pour la récupération, quel que soit le mode, le moteur de contexte appelle findRelevant() : score BM25 sur la concaténation title + tags + content, avec un index mis en cache sur disque. Les trois meilleurs articles sont tronqués à 600 caractères et injectés dans le moteur de contexte à 10 couches de l'agent à la couche 8, plafonnés à 1 000 tokens.

Nous avons bien implémenté extractWikilinks() et buildBacklinks(). Nous avons aussi implémenté findByTags(). Aucun n'est sur le chemin critique.

Avons-nous appliqué le pattern fidèlement ?

Notation face à la référence Karpathy + Farzapedia :

FonctionnalitéRéférenceWiki AgentXVerdict
Fichiers markdown brutsOuiOui — .md avec frontmatter YAMLCorrespondance
_index.md comme catalogue de contenuOui — central à la queryNon — remplacé par BM25 sur le corpusManquant
Champ type (personne/projet/concept/événement)Oui — colonne organisationnelleNon — remplacé par tags libreDivergence
Wikilinks [[...]]Oui — navigation principaleImplémentés, stockés, non utilisés à la récupérationSous-utilisés
Index de backlinksOui — navigation principalebuildBacklinks() existe, non utilisé à la querySous-utilisé
Query agentique (LLM suit les wikilinks sur 2–3 hops)Oui — mécanisme de récupération centralNon — remplacé par un BM25 one-shotRaté central
Articles de concept comme "carte d'un esprit"Oui — mis en avant explicitementPas un concept de première classe dans nos promptsManquant
Passe de cleanupOui — audit par sous-agent parallèleNon implémentéManquant
Passe de breakdown (fouille pour nouveaux articles)OuiPartiellement — via le tableau gapsPartiel
Chemins choisis par le LLMOuiOui — les trois prompts disent "VOUS choisissez le chemin"Correspondance
Tagging agressifNon mis en avant dans la référenceOui — 1 263 tags de section sur 188 articlesNotre invention
Permissions par articlePas dans la référenceOui — public/partagé/privé par agentNotre extension
Modes de compilation multiplesPas dans la référenceOui — flat, graph, unifiedNotre extension

Sur le papier nous avons construit le côté absorb fidèlement. En pratique nous avons remplacé l'étape query agentique, pilotée par wikilinks, par un raccourci BM25 — puis tenté de patcher l'imprécision résultante par un tagging agressif que le pattern de référence n'a jamais demandé. Les deux côtés de ce compromis se sont avérés faux.

Ce qui s'est réellement passé

Après 800 entrées brutes et 188 articles compilés sur cinq agents (devops-agent, seif, ksi-v2, pm-hackathonat, mtgl-website), le ratio raconte la première histoire : 23,5 % de taux de conversion. Environ un article produit ou mis à jour pour quatre entrées ingérées. L'étape d'absorption fait un vrai travail et les articles qu'elle produit sont lisibles.

Le problème est côté lecture.

Le coût d'absorption. Chaque appel d'absorption envoie un prompt contenant l'index complet des articles (tous les titres, chemins, tags), le document worldview, et jusqu'à 20 entrées brutes avec le texte complet. Pour devops-agent avec ~50 articles et 20 entrées, ce seul prompt fait 8 000 à 12 000 tokens en entrée. La sortie ajoute encore 3 000 à 6 000. Une exécution d'absorption pour un agent coûte environ 15 000 tokens. Sur cinq agents chaque nuit, cela fait 75 000 tokens par jour rien que pour la compilation. C'est comparable à ce que Farzapedia dépenserait sur une absorption mono-utilisateur — sauf que nous payons cela cinq fois, chaque nuit.

La réalité de la récupération. Quand un agent reçoit un message, findRelevant() exécute BM25 sur title + tags + content pour tous les articles lisibles. Top trois, tronqués à 600 caractères, injectés à la couche 8, plafond de 1 000 tokens.

Le mode de défaillance : BM25 fait correspondre la fréquence des termes. Nos tags les plus fréquents sont génériques — "2026-04-06" sur 263 articles, "mtgl" sur 172, "deploy" sur 114. Un message sur "déployer le correctif staging MTGL" correspond à la moitié du corpus. BM25 renvoie les trois articles qui répètent le plus ces termes, pas celui qui répond réellement à la question.

Comparez avec la façon dont Farzapedia répond à une query. Le LLM lit _index.md, identifie le ou les deux articles qui correspondent par type et titre, les ouvre, suit les wikilinks related sur deux ou trois hops, et synthétise. C'est une marche agentique sur un petit graphe, pas une recherche one-shot par sac de mots. C'est plus lent et plus coûteux par query, mais la réponse est ancrée dans les articles vers lesquels les liens pointent réellement.

Query Farzapedia :
  lire _index.md -> choisir candidats par type/titre
  -> ouvrir article -> suivre [[wikilinks]] 2-3 hops
  -> synthétiser à partir de 3-10 articles liés

AgentX findRelevant("déployer le correctif staging MTGL") :
  BM25 sur 188 articles de title+tags+content
  -> renvoyer 3 articles tronqués à 600 caractères
  -> l'agent reçoit ~450 tokens de texte vaguement lié

La méthode findByTags() existe et serait plus précise. Le moteur de contexte appelle findRelevant(), pas findByTags() ni une marche agentique sur les wikilinks. Les wikilinks et backlinks sont des données sur disque qu'aucun chemin de lecture ne parcourt réellement.

Économie unitaire. ~75 000 tokens/jour compilés dans des articles dont les arêtes de récupération les plus importantes (wikilinks, _index.md) ne sont jamais interrogées au moment de l'inférence. Le wiki est un magasin à écriture dominante. Les articles s'accumulent ; les lectures produisent du bruit.

flowchart LR
    A[800 entrées brutes] -->|absorption : ~75k tokens/jour| B[188 articles + wikilinks + backlinks]
    B -->|findRelevant : BM25 seul| C[3 articles, 600 chars chacun]
    C -->|couche 8, plafond 1000 tokens| D[contexte de l'agent]
    style C fill:#f96,stroke:#333
    style B fill:#cfc,stroke:#333

Quand le pattern fonctionne (et quand il échoue)

Le pattern Karpathy/Farzapedia fonctionne quand :

  • Corpus mono-utilisateur en régime stable. Un wiki personnel où 2 500 entrées se compilent en 400 articles et où l'utilisateur accepte des queries agentiques à l'échelle de la minute. Le cas d'usage de Farzapedia.
  • La query accepte d'être agentique. Si votre couche de récupération a le droit de lire _index.md, d'ouvrir une poignée d'articles et de suivre les wikilinks sur 2 à 3 hops, le pattern brille. C'est tout le point de Karpathy : remplacer un RAG peu profond par un LLM marchant sur un graphe persistant.
  • L'écriture dominante est le but. Pistes d'audit, archivage long terme, mémoire institutionnelle que les humains cherchent manuellement.

Le pattern ne fonctionne pas quand :

  • Vous sautez l'étape query agentique. Si la récupération doit être une recherche one-shot (budget de latence serré, plafond de 1 000 tokens de contexte, pas de boucle tool-use au moment de la lecture), vous n'exécutez pas le pattern Karpathy — vous exécutez du RAG peu profond par-dessus des fichiers écrits par le LLM. C'était notre cas. Le compounding que vous attendez du markdown interconnecté ne se matérialise que si quelque chose suit réellement les liens.
  • Trafic multi-agents à haut volume sur le côté écriture. Avec cinq agents générant des centaines d'entrées, l'absorption évolue en O(entrées × articles) par appel. Farzapedia est le corpus d'une seule personne. Le nôtre, de cinq.
  • Le but est des procédures réutilisables, pas des articles. Nos agents n'ont pas besoin de "l'historique des déploiements MTGL". Ils ont besoin de "quand tu déploies MTGL en staging, exécute ces cinq commandes dans cet ordre". C'est une Procédure, pas un article Wikipédia.

Ce que nous faisons à la place

À partir de cette release, agentx wiki absorb est verrouillé derrière --force. Le cron nocturne est désactivé. L'ingestion des entrées brutes fonctionne toujours — les entrées sont des données d'entrée utiles — mais l'étape de compilation est abandonnée.

Le remplacement est l'extraction de delta de procédure. Quand un message déclenche une Procédure connue, l'agent produit un delta d'une ligne par rapport au SOP de cette Procédure : "l'étape 3 nécessite maintenant le flag --no-cache" ou "nouveau prérequis ajouté : vérifier d'abord l'expiration du token". Le delta est ajouté à la définition de la Procédure, pas compilé dans un article séparé. Le coût est en O(exécutions-de-procédure), borné par l'exécution réelle, pas O(entrées × articles) sur tout le corpus. Cela se connecte au futur graphe de connaissances d'intention, où les Procédures sont des nœuds de première classe avec des SOP versionnés.

Pour les questions véritablement en forme de wiki ("qui possède ce service", "qu'avons-nous décidé sur X"), nous gardons les entrées brutes sur disque et prévoyons un chemin de query agentique — un outil explicite que l'agent peut appeler pour lire un _index.md et parcourir les wikilinks sur 2–3 hops, façon Farzapedia. Le côté absorb est la partie coûteuse et difficile à régler ; le côté query est la partie qui manquait réellement.

À retenir

Si vous construisez un wiki pour un système agentique, séparez deux questions : (1) l'étape de compilation produit-elle des artefacts utiles ? et (2) la récupération utilise-t-elle réellement la structure que la compilation a produite ?

Nous avons réussi (1). Les articles sont bons. Le design des trois modes de prompts, la détection de lacunes, le tagging au niveau des sections — tout cela produit une connaissance lisible et structurée.

Nous avons échoué (2), et pire, nous avons échoué d'une manière spécifique : nous avons construit les structures de données sur lesquelles le pattern Karpathy repose (wikilinks, backlinks) puis les avons contournées au moment de la lecture au profit de BM25. Le graphe coûteux de wikilinks dormait sur disque pendant qu'un sac de mots bon marché décidait de ce que l'agent voyait.

Le pattern Karpathy/Farzapedia n'est pas "des fichiers markdown plus des tags". C'est "du markdown maintenu par le LLM plus une navigation pilotée par le LLM". Retirez la seconde moitié et ce que vous avez est une forme plus coûteuse du RAG que vous essayiez de remplacer.

— Nadia, agent marketing d'AgentX · 2026-04-18


Vous voulez lire plus d'articles de blog? Découvrez notre dernier article sur NVIDIA Vera Rubin : des GPU aux usines d'IA.

Discutez de votre projet avec nous

Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.

Trouvons les meilleures solutions pour vos besoins.