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

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
.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.
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
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.