Turns raw language-lesson notes into a cross-linked Obsidian wiki and Anki cards from a single prompt in Claude Code. The cards come with pronunciation audio, pictures, example sentences, and cloze deletions.
Drop a lesson note into wiki/raw/ and tell Claude Code to process it:
/process-raw-notes wiki/raw/2026-07-05-lesson.md
The pipeline extracts the words, phrases and sentences worth learning, writes a wiki page for each, generates the media, and files the Anki cards. It ships tuned for learning Spanish from English; adapting it to another pair is a documented checklist.
The architecture is inspired by Andrej Karpathy's LLM Wiki note: an LLM-maintained knowledge base over immutable raw sources. This project applies that idea to language learning.
The typical language-learning loop looks like this: take a lesson, leave raw notes (vocab, phrases, grammar), intend to turn them into Anki cards and maintain a personal dictionary, never actually do it. The problem is not the learning. It's the busywork: picking an image per word, recording pronunciation, writing example sentences, building cloze deletions, filing everything consistently, keeping cross-references between lessons. Humans abandon the loop; LLMs don't.
This project takes over that part. The user adds a new raw note and asks to process it. The orchestrator parses it,
updates the wiki, produces media, writes cards, and tracks what was done in a log. The user reviews the result in
Obsidian (for the wiki) and Anki (for the cards). The cards they see tomorrow in Anki are the ones they added to
wiki/raw/ yesterday, without the user writing either wiki pages or cards by hand.
- Claude Code with workflow support (tested on 2.1.195) — the ingest runs inside it as a background workflow.
- Python 3 — standard library only, nothing to install.
- Anki with the AnkiConnect add-on.
- An ElevenLabs API key for pronunciation audio (paid; the free tier covers a few small lessons).
- A Stability AI API key for card images (paid).
- Obsidian — optional, for browsing the wiki and its graph; any markdown viewer works.
-
Fork or clone the repo.
-
Copy
.env.exampleto.envand fill in the ElevenLabs and Stability keys. -
Set up Anki — the deck, the two note types, AnkiConnect — step by step in
docs/anki-setup.md. Keep Anki running during ingests. Phone sync isdocs/ankiweb-sync-setup.md. -
Optional: open
wiki/as an Obsidian vault;docs/obsidian-front-matter-title-setup.mdmakes the graph show words instead of numeric IDs. -
Open the repo in Claude Code.
-
Drop a lesson note into
wiki/raw/— one item per line,term - translation.TEMPLATE.mdis the recommended shape; deviations are fine, the extractor judges by meaning. -
Ask for the ingest:
/process-raw-notes wiki/raw/<your-note>.mdWatch live progress with
/workflows. The finished run reports what was created, what was skipped as a duplicate, and what was parked inwiki/unresolved.mdwith a reason. -
Review the new pages in Obsidian and the new cards in Anki.
One run takes a raw note end to end: it extracts candidate items, normalizes each to canonical dictionary form,
deduplicates against the existing wiki and inside the file, reserves stable IDs, builds every entity (audio and image
over the ElevenLabs and Stability REST APIs, then the wiki page, then the Anki notes), gates each on a completeness
check, and updates the index and the log. Claude Code executes this as a deterministic background workflow
(.claude/workflows/ingest.js); LLM agents are called only for language judgement, and every side effect goes through
small Python modules. The runtime map lives in .claude/CLAUDE.md.
The system keeps three layers:
wiki/raw/ immutable source notes from lessons and homework
wiki/ LLM-maintained markdown knowledge base
Anki derived flashcards, synced from the wiki over AnkiConnect
wiki/raw/ holds the user's curated lesson notes, homework conversations, teacher-chat exports. Markdown or plain
text, occasionally images or PDFs. Agents read this layer but never modify it. If the user wants to edit a raw note,
they edit it themselves; a future re-ingest would re-process it. The folder sits inside wiki/ only so that Obsidian
resolves the sources: wikilinks on entity pages to the actual lesson notes; conceptually it remains its own layer,
owned by the user alone.
wiki/ is a directory of markdown files, one per entity, created and updated by the LLM as lessons are ingested.
The wiki is the compiled artifact: accumulating, interlinked, and the master for all media. Opened in Obsidian, the
graph view shows the shape of the user's growing vocabulary.
Anki is the repetition layer. Cards are generated from the wiki through AnkiConnect. Two note types live in one deck: L1→L2 (a native-language prompt with a picture → the target word with audio) and Cloze (a target-language sentence with the studied word blanked out). Card fields, tags, and media all come straight from the wiki. The wiki is the source of truth; Anki is derived.
-
Lemma. A single word in canonical dictionary form. Gets a wiki page with grammatical metadata, one pronunciation audio, optionally one image (only if the concept is visualizable), one example sentence, and one cloze. Produces both an L1→L2 card and a Cloze card. Polysemy is modeled as separate wiki pages, one per sense, each with its own ID.
-
Phrase. A fixed multi-word expression: a collocation, an idiom, a greeting. Gets a wiki page, an audio of the whole phrase, optionally an image, and one example sentence. Produces only the L1→L2 card, no cloze, because the phrase itself is the unit being practiced.
-
Sentence. A syntactically complete utterance the user wants to memorize as a whole. Gets a wiki page and one audio of the full sentence. Produces only the L1→L2 card; no image, no cloze.
-
Rule. A grammar pattern, a pronunciation rule, a number template. Gets a wiki page in
rules/<kind>/and nothing else: no media, no Anki cards. Lemma and phrase pages link to rules viarelated:references.
A handful of special files inside wiki/ carry the navigational and bookkeeping load.
index.md is the content-oriented catalog: every entity page listed by category with a link and a one-line
summary. It is the entry point for querying the wiki. To answer a question, the LLM reads index.md first and drills
into the pages it points to. This lightweight navigation works well at moderate scale (hundreds of pages) and avoids
the need for embedding-based RAG infrastructure. The ingest workflow updates index.md at the end of every run.
log.md is the chronological, append-only record of what happened and when. Each entry starts with a consistent
prefix, ## [YYYY-MM-DD] <op> | <human-anchor>, which keeps the log parseable with plain Unix tools
(grep '^## \[' wiki/log.md | tail -5 returns the last five operations). The log gives a timeline of the wiki's
evolution and lets the orchestrator detect whether a given raw file has already been ingested.
unresolved.md is the append-only parking lot for raw items the normalizer could not confidently handle:
suspected typos, ambiguous words with no translation, lines that violate the one-unit-per-line convention. Each entry
records the offending item and a human-readable reason. Nothing is ever silently dropped: an item that cannot become a
wiki page lands here instead. Because wiki/raw/ is immutable, the user does not edit source notes to fix these;
instead they copy the corrected items into a new raw file, run the ingest on it, and then clear the entries they
resolved from unresolved.md. It is the one file inside wiki/ the user edits by hand.
.counter holds the next free entity ID as a plain integer on its first line, followed by a trailing empty line
(kept so git diffs stay clean). The ingest's reserve_ids.py reads and parses only the integer on the first line,
writes the incremented value back to that same line, and never touches the trailing empty line. The stored value is
the bare number; the 5-digit ID is formed at generation time by left-padding it with zeros (42 → 00042).
.
├── TEMPLATE.md recommended structure for raw notes
├── .env.example API-key template (copy to .env, which stays untracked)
├── docs/ setup guides: Anki, AnkiWeb sync, Obsidian, language-pair adaptation
├── .claude/
│ ├── CLAUDE.md orchestrator cheatsheet (runtime map)
│ ├── agents/ LLM agent definitions (language judgement)
│ ├── skills/ process-raw-notes entry-point skill
│ ├── workflows/ ingest.js — deterministic ingest orchestrator
│ └── scripts/ Python modules: media, wiki pages, Anki, verify
└── wiki/ LLM-maintained knowledge base (Obsidian vault)
├── raw/ immutable source notes (added by the user)
├── index.md catalog of all pages
├── log.md chronological ingest log
├── unresolved.md parked items pending a user fix-file
├── .counter next free entity ID
├── lemmas/ lemma pages (one word, canonical form)
├── phrases/ phrase pages (fixed multi-word units)
├── sentences/ sentence pages (full utterances)
├── rules/ rule pages
└── assets/{images,audio}/ master copies of media, synced to Anki
Nothing in the pipeline is tied to Spanish or English, but the pair is baked into the Anki setup, the voice, and the
agent prompts. docs/adapting-to-your-languages.md lists every place to change
and ends with a prompt that lets Claude do the whole edit for you.
Next up: a grammarian for rule pages, an enricher for thin entries, a mechanic that repairs broken cards. The full
list, in rough priority order, is in ROADMAP.md.
Questions and ideas go to Discussions, bugs to issues — both welcome. If the project saves you the busywork, you can buy me a coffee.
