01 — Lab

What I build outside client work

Tools built for my own needs, with their source code, their measurements and their technical decisions.

in progress · 2026

Weave

Project Memory for AI Coding Agents

A tool that gives AI coding agents a memory. After each Claude Code or pi session, it reads the transcript and updates the project's documents (decisions, backlog, status) without making anything up: code extracts the facts, the LLM only arranges them.

automated tests
291
packages published on npm
5
production dependencies in the engine
0
Node.jsJavaScript (ESM)TypeScriptClaude Agent SDKpi SDKpnpm workspacesGitLab CIGitHub Actionsnpm
↓ Problem, architecture and technical decisions

The problem

Coding agents like Claude Code move fast, but forget everything between sessions. Their decisions stay buried in a transcript nobody rereads, compaction of long sessions wipes out the intermediate reasoning, and asking the agent to document its work makes it reread the whole history, at full price.

What I built

A tool that reads the transcript of a finished session and updates the project documents you choose: decision log, backlog, status page, changelog. The next session, human or agent, starts from what actually happened.

  • Nothing made up: code extracts the facts, the LLM only arranges them; if a session has nothing for a document, it is left untouched
  • Declarative: each document is described in a few lines of YAML (expected content, structure, write mode)
  • Safe to rerun: running again on the same session never duplicates an entry
  • Frugal: the model gets a compact extract prepared by code, not the full history; everything that can fail is checked locally before the LLM call
  • Reliable: 291 automated tests, about 1.6 lines of test per line of code
  • Integrated: CLI, /weave command in pi, Claude Code hook; 5 packages published on npm

Under the hood

Three steps, and a strict boundary between what depends on the agent and what does not:

transcript (.jsonl)
└─ Extract  → bundle     structured facts: actions, errors, files   (adapter)
└─ Scribe   → fragment   one LLM call per target document           (engine)
└─ Write    → document   append / prepend / replace, guarded writes (engine)

Only two contracts connect the engine to the agents: extract(transcript) → bundle and complete(prompt) → text. The engine knows neither transcript formats nor SDKs: supporting a new agent means writing an adapter, without touching the core.

  • Extraction: parsers for pi and Claude Code transcripts (successful actions, tool errors, files changed, compactions, subagent delegations with their result and cost)
  • Scribe prompt: an explicit data contract; transcript content is treated as material, never as instructions, which guards against prompt injection
  • Idempotency: each appended fragment carries a session marker (<!-- weaved:session=<id> -->), so a rerun never writes twice
  • Safe writes: paths confined to the session directory, collisions rejected between targets, .bak backups, the transcript and the config (symbolic and hard links included), write access checked before the LLM call
  • Typed errors: CONTRACT / DATA / RUNTIME codes compared by value rather than instanceof, which breaks as soon as a package is duplicated in node_modules
  • Publishing: GitLab CI publishes to npm on every version merged into main, and compatibility jobs run against the real Claude Code and pi SDKs

Deliberate choices

Rejected optionWhy
Asking the agent to document at the end of the sessionResends the whole history to the model and relies on its memory, which compaction has already truncated
Feeding the raw transcript to the LLMMore expensive, and it makes things up: code extracts the facts, the model arranges them
One tool per agentA shared engine and one adapter per agent: adding one leaves the core untouched
A YAML libraryA flat subset parsed in-house is enough for targets.yaml and keeps the engine dependency-free

completed · 2026

DF-oracle

RAG Assistant for the Dwarf Fortress Wiki

A command-line assistant that answers Dwarf Fortress gameplay questions in French, drawing on the official English wiki. Every answer cites its sources with a link to the exact wiki section; when the information isn't there, it says so instead of making things up.

of questions finding the right article within 5 passages
90.9%
wiki passages embedded locally
~11,000
automated tests
351
TypeScriptNode.jsLangChain JSOllama (bge-m3)HNSWLibGeminisaxesVitest
↓ Problem, architecture and technical decisions

The problem

Dwarf Fortress is a game of rare depth, documented by a huge community wiki, in English only. Finding the right answer means navigating dozens of articles and tables. Asking a general-purpose LLM directly doesn’t help much: its knowledge of the game is outdated, and it fills the gaps with plausible but wrong answers.

What I built

A RAG (retrieval-augmented generation) assistant that fetches the relevant wiki passages before answering:

  • French question, English wiki — a multilingual embedding model bridges the two, without translating the corpus
  • Sourced answers — every claim points to a [n] marker, resolved into a link to the exact wiki section
  • No hallucination — if the passages don’t cover the question, a fixed reply: “The archives say nothing about this.”
  • Measured quality — 44 reference questions: 90.9% find the right article within the top 5 passages, and the 4 split tables tested are restored at 100%
  • Local and frugal — embeddings and index run on the machine (4 GB of VRAM is enough); only the final write-up goes through Gemini’s free tier

Under the hood

Two separate pipelines, connected by an index persisted on disk:

Ingestion (once)
MediaWiki XML dump (~29 MB)
└─ Parse    → filtered articles     SAX streaming, never fully loaded in memory
└─ Clean    → markdown              wikitext, templates and tables converted
└─ Chunk    → ~11,000 passages      split by section + title breadcrumb
└─ Embed    → HNSWLib index         bge-m3 via Ollama, batched, resumable

Answer (per question)
French question
└─ Retrieve → passages              MMR + reassembly of split tables
└─ Prompt   → cited context [n]     strict grounding rules
└─ LLM      → answer + sources      Gemini, temperature 0
  • Ingestion — the dump is streamed with saxes; off-topic pages are filtered by category
  • Cleaning — wikitext converted to markdown, tables included; since wikitext isn’t a regular grammar, cleaning is deliberately best-effort and judged against the real corpus
  • Chunking — by wiki section, each passage carrying its path (Article > Section > Subsection) so it keeps its context once isolated
  • Resumable indexing — a checkpoint every 5,000 passages: an Ollama crash doesn’t mean starting over
  • Retrieval — MMR (relevance + diversity); tables split across several passages are stitched back together so lists reach the LLM whole
  • Prompt safety — wiki content is never placed in the system instructions: it stays treated as data, not as instructions

Settings measured, not guessed

Every retrieval parameter was set by an evaluation script over the 44 reference questions:

SettingMeasurementDecision
HNSW index precision+6.8 pts of recall with exact search, for a few millisecondsExact search at this scale
Number of passages sentSame recall at 5 and 8, only +1 article at 12 for +50% tokens8 by default, 5 and 12 documented
Relevance / diversity balanceOnly setting that holds recall from 5 passagesλ = 0.8

The evaluation also overturned a conclusion: the recall gain credited to MMR actually came from approximate search. Once the index is exact, MMR’s only contribution is passage diversity.

Deliberate choices

Rejected optionWhy
Crawling the siteAn XML dump is complete, reproducible, and doesn’t load the wiki’s servers
Server-based vector databaseHNSWLib is enough for ~11,000 passages: one file on disk, zero services to run
”Game expert” personaWould push the model to draw on its own memory; the persona answers only from the archives provided