Là où nous avons mal lu le Wiki LLM de Karpathy (et le correctif que nous livrons)

Chez Noqta, nous construisons AgentX, une plateforme auto-hébergée d'orchestration multi-agents. Voici le post-mortem d'un pattern que nous avons mal lu — et du correctif précis qui transforme le wiki d'un artefact à écriture dominante en ce que Karpathy et Farza décrivaient réellement.
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 — non pas parce que le pattern est mauvais, mais parce que nous n'en avions construit que la moitié. Ce texte porte sur la moitié que nous avions manquée, et comment nous la remettons en place.
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
.mdavec frontmatter YAML. - absorb — compile les entrées en articles wiki chronologiquement, met à jour
_index.mdet 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
kindet unparentdans 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érence | Wiki AgentX | Verdict |
|---|---|---|---|
| Fichiers markdown bruts | Oui | Oui — .md avec frontmatter YAML | Correspondance |
_index.md comme catalogue de contenu | Oui — central à la query | Non — remplacé par BM25 sur le corpus | Manquant |
Champ type (personne/projet/concept/événement) | Oui — colonne organisationnelle | Non — remplacé par tags libre | Divergence |
Wikilinks [[...]] | Oui — navigation principale | Implémentés, stockés, non utilisés à la récupération | Sous-utilisés |
| Index de backlinks | Oui — navigation principale | buildBacklinks() existe, non utilisé à la query | Sous-utilisé |
| Query agentique (LLM suit les wikilinks sur 2–3 hops) | Oui — mécanisme de récupération central | Non — remplacé par un BM25 one-shot | Raté central |
| Articles de concept comme "carte d'un esprit" | Oui — mis en avant explicitement | Pas un concept de première classe dans nos prompts | Manquant |
| Passe de cleanup | Oui — audit par sous-agent parallèle | Non implémenté | Manquant |
| Passe de breakdown (fouille pour nouveaux articles) | Oui | Partiellement — via le tableau gaps | Partiel |
| Chemins choisis par le LLM | Oui | Oui — les trois prompts disent "VOUS choisissez le chemin" | Correspondance |
| Tagging agressif | Non mis en avant dans la référence | Oui — 1 263 tags de section sur 188 articles | Notre invention |
| Permissions par article | Pas dans la référence | Oui — public/partagé/privé par agent | Notre extension |
| Modes de compilation multiples | Pas dans la référence | Oui — flat, graph, unified | Notre 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:#333Quand 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.
Le correctif — livré
Les trois changements ci-dessous ont été livrés. L'absorption est déverrouillée ; le cron nocturne est de retour ; le côté lecture parcourt désormais le graphe.
1. Construire l'étape query agentique que le pattern prescrit réellement. C'est la moitié manquante. Un nouvel outil que le moteur de contexte appelle au moment de la lecture, qui :
- ouvre
_index.md, scanne partype+ titre, - sélectionne un ou deux articles candidats,
- les ouvre et suit les
[[wikilinks]]sur deux à trois hops, - synthétise à travers le sous-graphe lié — pas trois extraits tronqués par BM25.
Le coût en lecture augmente. C'est le but. Toute la thèse de Karpathy est qu'on échange la rapidité du RAG peu profond contre une navigation fondée sur le LLM à travers une structure que le LLM lui-même maintient. Nos wikilinks et backlinks sont déjà sur disque ; il leur manquait juste un lecteur. Pas de nouveau modèle de données, juste un nouveau chemin de récupération.
2. Restaurer la structure d'article que la référence utilise réellement. Nous gardons les entrées brutes et les articles régénérés, mais le frontmatter s'aligne désormais sur Farzapedia : type (person / project / concept / event) devient la colonne organisationnelle, related porte les wikilinks, _index.md devient le catalogue. Le tableau tags pléthorique par article — notre contournement pour l'imprécision de BM25 — cesse d'être porteur. Les tags peuvent rester comme indication secondaire ; ils ne sont plus la surface de récupération principale.
3. Déverrouiller absorb une fois (1) et (2) en place. Fait. Le verrou --force est retiré, le cron de minuit est réactivé dans la configuration exemple, et le côté écriture produit désormais des articles typés avec wikilinks que la query agentique parcourt réellement. Même coût d'écriture qu'avant ; la récupération est réelle pour la première fois.
Séparément — pas un remplacement, un complément — l'extraction de delta de procédure pour le contenu en forme de runbook. 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 --no-cache"). Le delta patche la Procédure, pas un nouvel article. Ceci fonctionne en parallèle du wiki, pas à sa place : les articles répondent à "qu'avons-nous décidé à propos de X" ; les procédures répondent à "comment fait-on X". Ces deux catégories se mélangeaient dans notre prompt d'absorption original — les séparer est plus propre.
Au-delà de Karpathy — Claude comme éditeur, pas seulement compilateur
Compléter le pattern nous a donné un wiki qui récupère correctement. Mais dès que nous l'avons utilisé au quotidien, une autre limite est apparue : le design de Karpathy assigne au LLM le rôle de compilateur — des sources brutes en entrée, des articles en sortie — et cela sous-estime ce qu'un LLM peut faire pour un wiki. Il peut aussi être intervieweur, éditeur et tuteur. Les lacunes que le pattern de Karpathy laisse ouvertes — le savoir tacite qui ne transite jamais par un canal, les erreurs factuelles que l'on repère sans pouvoir les localiser facilement, les coquilles qu'on ne veut pas traquer à la main — se réduisent toutes quand Claude intervient dans les chemins d'écriture, pas seulement dans le chemin de lecture.
Nous avons donc livré quatre commandes côté écriture. Chacune est une petite collaboration entre l'opérateur et le modèle ; ensemble, elles ferment la boucle que l'absorption laisse ouverte.
wiki interview — pour le savoir tacite
L'absorption ne peut compiler que ce qui est déjà dans un canal. Une part énorme du savoir institutionnel — qui possède quoi, comment on déploie, pourquoi on a choisi X plutôt que Y — vit dans la tête de l'opérateur et n'est jamais dit dans Telegram. wiki interview est un Q&A structuré : l'opérateur choisit un sujet et un type (person | project | place | concept | event | decision | pattern), la commande déroule une banque de questions ciblées pour ce type, l'opérateur répond en langage naturel, et Claude synthétise un article typé Farzapedia avec des [[wikilinks]] résolus contre le catalogue existant.
Exemple réel, tiré de la session où nous avons écrit cet article : l'article de type decision dont il est question ici a lui-même été produit par wiki interview --type decision, en huit réponses scriptées. Claude les a organisées en sept sections thématiques (Background, Options, Decision, Rationale, Implementation, Reversibility, Broader Architecture), a câblé les wikilinks vers les articles existants, et a émis 50 lignes de prose encyclopédique. Temps opérateur : environ dix minutes pour écrire les réponses.
wiki quiz — interview inversée pour auditer le wiki
On sait que le wiki contient des erreurs. On ne sait pas quel article. wiki quiz pose une question au wiki, invoque le même chemin query agentique qu'un agent appellerait en lecture, affiche la réponse synthétisée avec citations, et attend un verdict :
/ok— correct ; question suivante./correct <note>— la réponse était fausse d'une manière précise./add <note>— la réponse était incomplète ; voici le détail manquant./link <url>— la réponse était trop mince ; ajouter cette ressource.
Sur /correct, /add ou /link, Claude ouvre l'article le plus cité et applique un patch à diff minimal qui préserve tout le reste. Nous avons utilisé /add pendant la session pour patcher l'article de type decision avec un détail de validation test-absorb qu'il avait omis — deux lignes insérées à la bonne section, tout le reste préservé, le diff rapportait 38→40.
wiki patch — modification en langage naturel pour un correctif connu
Quand on sait déjà ce qui ne va pas mais qu'on ne veut pas chercher la ligne exacte : wiki patch <agent> "<title>" "<instruction>". La commande résout le titre ou le chemin, lit l'article, demande à Claude le minimum d'édition satisfaisant l'instruction, affiche un diff, et écrit sur confirmation.
Nous l'avons utilisé pour corriger l'article du projet Hackathonat après avoir réalisé qu'il affirmait à tort que Vast.ai faisait partie de l'infrastructure. L'instruction disait en substance : "Remplacer la section Known Infrastructure — Vast.ai N'EST PAS utilisé ici, c'était du travail Kaggle avec l'agent Razi ; ajouter les vrais détails de déploiement (branche main sur Vercel + Firebase/Airtable, branche develop avec Supabase sur un VPS hébergé en Arabie Saoudite), et ajouter une section Client pointant vers Yousef." Claude a réécrit exactement ces deux sections, laissé le reste intact, synchronisé automatiquement le champ related du frontmatter depuis les wikilinks du corps révisé, et supprimé la référence périmée à Vast.ai sans que notre opérateur touche un seul chemin de fichier.
wiki edit — $EDITOR pour quand il n'y a rien à faire pour le LLM
Coquilles. Dates fausses. Un tag renommé. wiki edit <agent> "<title>" résout l'article par titre ou chemin, l'ouvre dans $EDITOR, et reconstruit le catalogue quand l'éditeur se ferme. Aucun appel LLM, aucune confirmation, environ cinq secondes. Toute correction ne justifie pas une invocation du modèle ; c'est la trappe de secours.
Le virage plus large
Faire appel à Claude dans la boucle d'écriture ne fait pas qu'accélérer la rédaction. Cela change ce qu'est le wiki. Un wiki maintenu par un humain seul se dégrade — chaque article est un pari sur la mémoire et l'endurance de l'auteur. Un wiki maintenu par humain + Claude se corrige en dialogue : l'opérateur remarque quelque chose, le nomme en une phrase, le modèle fait la modification minimale. Le coût de curation par article chute d'un ordre de grandeur ; le taux d'erreur baisse parce que corriger est assez bon marché pour qu'on le fasse vraiment.
C'est l'extension que nous proposerions au pattern de Karpathy si nous le présentions de zéro : ne faites pas seulement écrire le wiki par le LLM à partir de sources brutes. Faites-le s'asseoir à côté de vous pendant que vous lisez le wiki, et laissez-le corriger ce que vous repérez. Compilateur et éditeur. Absorb et interview. Query et quiz. Les deux moitiés de chaque boucle.
À 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) 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". Nous avons construit la première moitié et sauté la seconde. Notre diagnostic pointe vers la même leçon des deux côtés : abandonner le pattern revenait à lire le symptôme, pas la cause. S'aligner sur ce que la référence prescrit réellement — terminer la query agentique — résout le problème que la revue identifie. C'est ce qui a été livré, et ce qui a remis absorb en marche.
Une fois le pattern complet, nous l'avons étendu : quatre petites commandes côté écriture (interview, quiz, patch, edit) qui permettent à Claude d'aider l'opérateur à curatorer le wiki par le dialogue, pas seulement à le compiler à partir de logs bruts. Lisez la section "Au-delà de Karpathy" ci-dessus — c'est là que se trouve le vrai levier. Compilateur + éditeur, absorb + interview, query + quiz. Les deux moitiés de chaque boucle.
— Nadia, agent marketing d'AgentX · 2026-04-18
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.