01 — Labo

Ce que je construis en dehors des missions

Des outils conçus pour mes propres besoins, avec leur code source, leurs mesures et leurs choix techniques.

en cours · 2026

Weave

Mémoire de projet pour agents de code IA

Un outil qui donne une mémoire aux agents de code IA. Après chaque session Claude Code ou pi, il lit le transcript et met à jour les documents du projet (décisions, backlog, statut) sans rien inventer : le code extrait les faits, le LLM ne fait que les mettre en forme.

tests automatisés
291
paquets publiés sur npm
5
dépendance de production pour le moteur
0
Node.jsJavaScript (ESM)TypeScriptClaude Agent SDKpi SDKpnpm workspacesGitLab CIGitHub Actionsnpm
↓ Problème, architecture et choix techniques

Le problème

Les agents de code comme Claude Code avancent vite, mais oublient tout d’une session à l’autre. Leurs décisions restent enfouies dans un transcript que personne ne relit, la compaction des longues sessions efface le raisonnement intermédiaire, et demander à l’agent de documenter son travail lui fait relire tout l’historique, à plein tarif.

Ce que j’ai construit

Un outil qui lit le transcript d’une session terminée et met à jour les documents de projet choisis : journal de décisions, backlog, page de statut, changelog. La session suivante, humaine ou agent, repart de ce qui s’est réellement passé.

  • Rien d’inventé : le code extrait les faits, le LLM ne fait que les mettre en forme ; si la session n’apporte rien à un document, il reste intact
  • Déclaratif : chaque document se décrit en quelques lignes de YAML (contenu attendu, structure, mode d’écriture)
  • Rejouable : relancer sur la même session ne duplique aucune entrée
  • Économe : le modèle reçoit un extrait compact préparé par du code, pas l’historique complet ; tout ce qui peut échouer est vérifié localement avant l’appel au LLM
  • Fiable : 291 tests automatisés, soit ~1,6 ligne de test par ligne de code
  • Intégré : CLI, commande /weave dans pi, hook Claude Code ; 5 paquets publiés sur npm

Sous le capot

Trois étapes, et une frontière stricte entre ce qui dépend de l’agent et ce qui n’en dépend pas :

transcript (.jsonl)
└─ Extract  → bundle     faits structurés : actions, erreurs, fichiers   (adaptateur)
└─ Scribe   → fragment   un appel LLM par document cible                 (moteur)
└─ Write    → document   append / prepend / replace, écritures gardées   (moteur)

Deux contrats seulement relient le moteur aux agents : extract(transcript) → bundle et complete(prompt) → texte. Le moteur ne connaît ni format de transcript ni SDK : supporter un nouvel agent revient à écrire un adaptateur, sans toucher au cœur.

  • Extraction : parsers des transcripts pi et Claude Code (actions réussies, erreurs d’outils, fichiers modifiés, compactions, délégations à des sous-agents avec leur résultat et leur coût)
  • Prompt du scribe : contrat de données explicite ; le contenu du transcript est traité comme matériau, jamais comme instructions, ce qui le protège de l’injection de prompt
  • Idempotence : chaque fragment ajouté porte un marqueur de session (<!-- weaved:session=<id> -->), donc un rejeu n’écrit pas deux fois
  • Écritures sûres : chemins confinés au répertoire de la session, collisions refusées entre cibles, sauvegardes .bak, transcript et configuration (liens symboliques et physiques compris), droits d’écriture vérifiés avant l’appel au LLM
  • Erreurs typées : codes CONTRACT / DATA / RUNTIME comparés par valeur plutôt que par instanceof, qui échoue dès qu’un paquet est dupliqué dans node_modules
  • Publication : la CI GitLab publie sur npm à chaque version fusionnée dans main, et des jobs de compatibilité tournent contre les vrais SDK de Claude Code et pi

Choix assumés

Option écartéePourquoi
Demander à l’agent de documenter en fin de sessionRenvoie tout l’historique au modèle et dépend de sa mémoire, que la compaction a déjà tronquée
Donner le transcript brut au LLMPlus cher, et il invente : le code extrait les faits, le modèle les arrange
Un outil par agentUn moteur commun et un adaptateur par agent : en ajouter un ne touche pas au cœur
Une bibliothèque YAMLUn sous-ensemble plat parsé en interne suffit au targets.yaml et garde le moteur sans aucune dépendance

terminé · 2026

DF-oracle

Assistant RAG sur le wiki Dwarf Fortress

Un assistant en ligne de commande qui répond en français aux questions de gameplay Dwarf Fortress à partir du wiki officiel anglais. Chaque réponse cite ses sources avec un lien vers la section exacte du wiki ; si l'information n'y est pas, il le dit au lieu d'inventer.

de questions trouvant le bon article dès 5 passages
90,9 %
passages du wiki vectorisés en local
~11 000
tests automatisés
351
TypeScriptNode.jsLangChain JSOllama (bge-m3)HNSWLibGeminisaxesVitest
↓ Problème, architecture et choix techniques

Le problème

Dwarf Fortress est un jeu d’une profondeur rare, documenté par un wiki communautaire immense, en anglais uniquement. Trouver la bonne réponse demande de naviguer entre des dizaines d’articles et de tableaux. Demander directement à un LLM généraliste ne marche pas mieux : sa mémoire du jeu est datée, et il comble les trous avec des réponses plausibles mais fausses.

Ce que j’ai construit

Un assistant RAG (génération augmentée par recherche) qui va chercher les passages utiles du wiki avant de répondre :

  • Question en français, wiki en anglais — un modèle d’embeddings multilingue fait le pont, sans traduire le corpus
  • Réponses sourcées — chaque affirmation renvoie à un numéro [n], résolu en lien vers la section exacte du wiki
  • Pas d’invention — si les passages ne couvrent pas la question, réponse figée : « Les archives ne disent rien là-dessus. »
  • Qualité mesurée — 44 questions de référence : 90,9 % trouvent le bon article parmi les 5 premiers passages, et les 4 tableaux découpés testés sont restitués à 100 %
  • Local et frugal — embeddings et index sur la machine (4 Go de VRAM suffisent), seule la rédaction finale passe par l’offre gratuite de Gemini

Sous le capot

Deux chaînes séparées, reliées par un index persisté sur disque :

Ingestion (une fois)
Dump XML MediaWiki (~29 Mo)
└─ Parse    → articles filtrés      lecture en flux SAX, jamais chargé entier en mémoire
└─ Clean    → markdown              wikitext, modèles et tableaux convertis
└─ Chunk    → ~11 000 passages      découpe par section + fil d'Ariane du titre
└─ Embed    → index HNSWLib         bge-m3 via Ollama, par lots, avec reprise

Réponse (à chaque question)
Question FR
└─ Retrieve → passages              MMR + recollage des tableaux découpés
└─ Prompt   → contexte cité [n]     consignes d'ancrage strict
└─ LLM      → réponse + sources     Gemini, température 0
  • Ingestion — le dump est lu en streaming avec saxes ; les pages hors sujet sont filtrées par catégorie
  • Nettoyage — conversion du wikitext en markdown, tableaux compris ; le wikitext n’étant pas une grammaire régulière, le nettoyage est assumé best-effort et jugé sur le corpus réel
  • Découpage — par section du wiki, chaque passage portant son chemin (Article > Section > Sous-section) pour garder le contexte une fois isolé
  • Indexation reprenable — un checkpoint tous les 5 000 passages : un plantage d’Ollama ne fait pas repartir de zéro
  • Recherche — MMR (pertinence + diversité) ; les tableaux découpés en plusieurs passages sont recollés pour que les listes arrivent entières au LLM
  • Sécurité du prompt — le contenu du wiki n’est jamais placé dans les consignes système : il reste traité comme une donnée, pas comme une instruction

Réglages mesurés, pas devinés

Chaque paramètre de recherche a été fixé par un script d’évaluation sur les 44 questions de référence :

RéglageMesureDécision
Précision de l’index HNSW+6,8 pts de rappel en recherche exacte, pour quelques millisecondesRecherche exacte à cette échelle
Nombre de passages envoyésRappel identique à 5 et à 8, +1 article seulement à 12 pour +50 % de tokens8 par défaut, 5 et 12 documentés
Dosage pertinence / diversitéSeul réglage à tenir le rappel dès 5 passagesλ = 0,8

L’évaluation a aussi corrigé une conclusion : le gain de rappel attribué au MMR venait en fait de la recherche approximative. Une fois l’index exact, son seul apport est la diversité des passages.

Choix assumés

Option écartéePourquoi
Crawler le siteUn dump XML est complet, reproductible et ne charge pas les serveurs du wiki
Base vectorielle serveurHNSWLib suffit à ~11 000 passages : un fichier sur disque, zéro service à opérer
Persona « expert du jeu »Inciterait le modèle à puiser dans sa mémoire ; le persona répond uniquement à partir des archives fournies