An Anki deck compiled from class notes by an LLM agent
Raw class notes go into a folder; an LLM agent turns them into a structured dictionary, sentences and Anki cards under a written specification. A Python script computes which cards each word requires, validates every change, synthesizes speech with Azure and drives Anki desktop through AnkiConnect. Items are ordered by HSK 3.0 level, and each batch returns a review to the learner. The system is MIT, the accumulated content CC BY.
There are four ways into the Mandarin dictionary on this site, and none of them is a link: a pin behind my portrait on the About page, a line in the footer, three letters typed on any page, and a command in the palette for those who know what to ask for. I hid them on purpose and I stand by it. For everyone with better things to do, here is the dictionary , and here is the repository that builds it.
This week the deck crossed HSK 1, the first of the seven rungs the new HSK uses to measure Mandarin, with about 500 words on it. That is a small number and I am well aware of it. The machinery that got me there is less small. It is a folder where I drop class notes in whatever state they come out, an LLM agent that turns them into a dictionary and a set of Anki cards, a Python script that refuses to let either of us cut corners, and a review that comes back to me after every batch telling me what I missed.
How I ended up with a tutor and a pipeline
It started with a YouTube video about why China makes most of what the rest of us use (this one , in Spanish). I watched it for the industry and came out of it with a language to learn, which is not how these things usually go. The choice between German and Mandarin took longer than it should have, and the argument that settled it is in the note below.
Why Mandarin over German.
German wins on every neutral measure: shared alphabet, cognates, a B2 in under two years from Spanish. What tipped it was machine translation. It already covers the documents and the emails, which is most of what German would have given me at a tech company. In China, the part that translation cannot touch (the lunch, the side comments, the chat groups, walking into a factory and reading the room) is exactly the part that matters, and it gets more valuable as the tools get better.
Then came the question of how to learn it. I shortlisted two schools in Barcelona, a large institute with a business Mandarin track and a small one with groups capped at seven, and ended up with neither. For Mandarin, a private tutor wins on the one thing that matters most at the start: tones. A tone gets fixed when someone hears you get it wrong and stops you on the spot, and in a group of seven that happens to you perhaps five minutes an hour1. One to one, the whole hour is yours, the pace follows you instead of the slowest student, and (this is where the pipeline comes in) the tutor can work from what the review says I am missing. The schools can wait until I need the business track.
1A sixty-minute class split seven ways, minus the teacher's own speaking time, leaves each student somewhere between five and eight minutes of talking.
Before we start
Two ideas explain why a deck of flashcards is worth this much plumbing. Both are well established, and both are optional reading if you already use Anki.
SpacingRetrievalskip
Spacing
Memory decays on a curve that Hermann Ebbinghaus measured on himself in 1885, and every review that happens just before you would have forgotten flattens that curve a little more. Spaced practice beats the same number of repetitions crammed together, and the effect holds across hundreds of studies1. A scheduler that shows each card again at a growing interval is the cheapest way to exploit it.
1The standard reference is Cepeda and colleagues, Distributed practice in verbal recall tasks, Psychological Bulletin, 2006, a meta-analysis of more than three hundred experiments.
Retrieval
Pulling an answer out of memory strengthens it more than reading it again. Students who were tested on a passage remembered more of it a week later than students who reread it for the same time, even though the rereaders felt better prepared1. This is why every card in the deck asks me to produce something (type the pinyin, type the hanzi, mark the tones, say the sentence aloud) instead of showing me a word and asking whether I know it.
1Roediger and Karpicke, Test-enhanced learning, Psychological Science, 2006.
Anki in one paragraph
Anki is a free flashcard program with a scheduler that decides, card by card, when you should see each one again, based on how well you answered last time. A note holds the content, a card is one question generated from it, a deck is a folder of cards, and a preset holds the daily limits. The desktop app is where decks are built; AnkiWeb is a free sync service; and the phone apps (AnkiDroid, AnkiMobile) sync against it, so a card edited on the computer shows up on the phone after one tap. The piece that makes the rest of this article possible is AnkiConnect , an add-on that exposes Anki as an HTTP API on localhost:8765 while the desktop app is open. With it, a script can import a deck, move cards between decks, set limits and trigger a sync without anyone touching the interface.
From a folder of notes to a deck
The system is a repository with numbered folders, one per stage, and a single Python script, anki.py, that does everything computable. The agent (I use Claude Code, but any coding agent that reads files works) does everything that needs judgement.
writeswriteswritesanki.py pushanki.py exportInboxclass notes, any formatAgentreads the specDatabaseLogReviewNotebookDeckDictionary
The review goes back into the inbox, which closes the loop: my answers to it are part of the next batch. The folders, in order:
1-INBOXwhere I write. Phone notes, transcribed photos of paper, half-finished lists. The only thing that helps is a date on top. Processed notes are archived untouched in history/, one folder per batch.
2-DIGESTSone log per batch, written by the agent and never rewritten: what went in, what did not and why, what it corrected in my notes, what it reorganized.
3-DATAthe single source of truth, one YAML file per theme: words, expressions, characters, pronunciation rules, groups of easily confused items, sentences, cards and the generated audio. Only the agent edits it.
4-NOTEBOOKthe same data rendered as Markdown for reading. Regenerated every batch.
5-OUTPUTthe compiled .apkg and a public dictionary.json.
A batch is whatever sits in the inbox when I say process the inbox. I aim for one a week, because the review then lands before my next class, but nothing depends on the rhythm: the structure is never frozen, and every batch the agent revisits themes and groups and reorganizes when the new material asks for it. Cards keep a permanent ID through all of it, so moving one never costs its review history.
An agent that sleeps between batches
The fashionable shape for an agentic system right now is one that never stops: a loop on a schedule, a memory store, tools calling tools, a dashboard to watch it think. I felt the pull. Then I counted what this problem needs, and it is a handful of runs a month, each started by a person who has just come back from class.
So the agent here is awake only while it works. Everything that makes it behave lives in files placed where it will look. AGENTS.md at the root is the first thing any coding agent opens, and it points to the specification (docs/design.md, how the system works) and to the learner (docs/goal.md, who it works for). Every folder carries a README.md saying what lives there and who may touch it. The numbered folder names give it the order of the work before it has read a line. The directory is the interface, and a well-placed paragraph of instructions does the job a runtime would do in a more ambitious design: nothing to host, nothing to monitor, and nothing running at three in the morning.
The whole thing took a couple of days with Opus 5.5 at the keyboard and Azure giving it a voice (I said as much at the time ), and most of those days went into the rules, since the code followed from them. That is also why I expect it to age well. The rails are plain text and a validator; when the next model arrives it reads the same files, writes better cards under the same checks, and the deck improves without my touching the system.
The model writes, the script checks
The rule I would keep if I threw everything else away: anything that can be computed is decided by code, from rules written in a specification, and the model only works on the difference the code hands it. Left alone, a model will happily write four cards for a word you only need to recognise and none for a word you need to say. So every entry carries a use, a small ladder where each rung includes the one below it:
readrecognise the hanzia card that shows the character hearunderstand it spokenadds a listening card sayproduce itadds typed production, tones and speaking
From use, the part of speech and the pronunciation, anki.py plan computes the cards every entry requires and lists what is missing. scaffold writes the standard ones in a fixed format, so the agent only drafts the few that need real prose. Then check refuses the batch if any required card is missing, if two cards test the same thing, if a word I want to say never appears inside a sentence, if an ID changed since the last batch, or if a pinyin reading disagrees with the dictionary without a stated reason. Closing a batch commits and tags it (lote-001, lote-002), which gives check a baseline to compare against and gives me a way back from any mistake. A GitHub Actions workflow runs check and the tests on every push, belt and braces.
Two details did more for the deck than their size suggests. The typed answer accepts pinyin with tone marks, pinyin with digits (ni3 hao3) or hanzi from a phone keyboard, and ignores spacing and punctuation, which meant writing the comparison in a few lines of JavaScript inside the card template. And the back of each card shows the IPA transcription, the pinyin traps a Spanish speaker falls into (the b in bà is really an unaspirated p) and the family of the word when it has one, so 他, 她 and 它 always turn up together.
Speech, cached
Every answer and every sentence has audio, synthesized with Azure's neural text-to-speech (the voice is zh-CN-YunyangNeural), slowed down to 70% for sentences and 60% for single words, with a short silence in front because some players swallow the first syllable. Files are named after a hash of the text and the synthesis settings and indexed by the exact Chinese string, so a word is paid for once no matter how many cards use it, and changing the voice regenerates everything cleanly. The paid tier costs cents per batch; the free tier would have done for me alone, but its output cannot be redistributed, and the whole point was to publish it.
Driving Anki from outside
anki.py push compiles the deck with genanki, opens Anki desktop if it is not running, waits for AnkiConnect and then does in a few seconds what I used to do with the mouse: imports the package, forces the card templates to update, orders the new cards so the basics come first, moves each card to the subdeck of its level and theme, sets the daily limits in a preset of its own and syncs with AnkiWeb. From there the phone picks it up at the next sync.
My Anki collection holds other decks I did not build, and an HTTP API that can delete decks is a loaded gun. Every destructive call is scoped to this deck's name, the limits live in a preset this deck owns so the shared default is never touched, a subdeck is removed only after checking it is empty, and deleting more than ten orphaned cards requires an explicit --force. I learned to insist on this the honest way: early on, a cleanup call went out without listing its targets first, and it only did no damage because there was nothing to damage. Those guards now have tests of their own, run against a simulated AnkiConnect with foreign decks in the collection. And nothing gets corrected inside Anki: every push overwrites the deck from the data, so a fix made in the app would simply vanish.
Levels, a goal file and a review
Everything in the deck is ordered by the levels of HSK 3.0, the 2021 revision of the Chinese proficiency test: levels 1 to 6, plus a seventh bucket for levels 7 to 9, which share one vocabulary list. A word on the official list takes its official level, from an MIT-licensed copy of the list kept in the repository. Anything else (a lot of what a person actually says is not on any exam) gets a level the agent estimates, with the reason written down. A sentence takes the level of its hardest word, so it never smuggles vocabulary from later on. The deck is split by level first and theme second, and I study one level at a time.
What the agent keeps for a given learner lives in one file, docs/goal.md: who is learning, what for (in my case speaking, work, living in China, in roughly that order), what goes in to be said, what only to be understood and what stays out. The agent reads it before every batch and applies it as the first filter on every decision. It works as a swappable prompt module: the rest of the specification describes how the system works and holds no personal preference, so pointing the pipeline at someone else is a matter of rewriting one file.
The review is the part I did not expect to like this much. It opens with a short, cumulative reflection: my level and the distance to the next, what my tutor has been covering and whether it fits what I want, how I have been studying across batches and one concrete piece of advice. Then come the gaps: the thematic ones (I could say morning and evening but not afternoon), the basics of my level I have not met yet, characters with a story worth knowing. Under every point there is an empty line for my answer, which goes into the next batch. My tutor sees some of those gaps in class the following week. Between the two of them, I rarely have to decide what to study next.
A template with my notes inside
The repository is two things sharing a folder, and the licenses say so. The system (the script, the specification, the tests) is MIT: fork it, empty the data folders, rewrite the goal file, change the deck name and it is yours. The content (my notes, the dictionary, the logs, the audio) is CC BY: use it for anything, commercial included, as long as you name where it came from. The public dictionary is an export with an explicit list of publishable fields, served through jsDelivr from a pinned commit, and it is what powers the page on this site .
The deck itself is not on AnkiWeb's shared decks yet. It is still small and I am still early in Mandarin, but I wanted the whole thing piped before the content grew, and there is a reminder in my calendar to publish it there. Until then, anyone who wants it can build it from the repository.
HSK 2 adds some 750 words. The pipeline does not care.