← Back to blog

Ablaut: conjugating German with a verification loop

August 16, 2026

I have been playing lately with a concept for an agent-first language learning app for German, and I ran into some interesting challenges. Take a fill-the-blank exercise:

Der Vogel hat auf dem Ast __________. (sitzen)

"The bird sat on the branch", in the perfect tense: the learner must supply the past participle of sitzen, which is gesessen.

LLMs these days are pretty good at generating such games, and will also provide the answer for you. Even so, I want to reduce the need for intelligence wherever possible, both for cost and for correctness: a learning app must never teach something wrong. If a deterministic engine provides the answers, a small, cheap model can generate exercises on the fly, basically for free, and the app can follow where the learner wants to go instead of walking through pre-scripted lessons.

So I naturally wondered whether my coding agent (Claude Code running Fable 5) could build that engine for me, with a proper verification loop to keep it on track. The ground truth already exists, because linguists have done the work: UniMorph annotates 194k inflected German forms, and Wiktextract extracts another 794k from Wiktionary. The domain itself is a minefield of rules: a weak default most verbs follow, strong verbs whose stem vowel shifts through Grimm's ablaut classes (singen, sang, gesungen), prefixes that detach and fly to the end of the sentence (stehe auf) or refuse to (verstehe), orthographic adjustments on top (du arbeitest, but du lässt), and exceptions that have exceptions. A golden harness diffed every form the engine generated against both gold standards, CI failed the build on any regression, and the agreement ratio climbed PR after PR:

Agreement with UniMorph (overall %), by pull request
607080901002nd oracle99.15234101112pull request #

After some work, the engine agrees with UniMorph on 99.2% of forms and with Wiktextract on 98.0%, and has zero known errors on the slots where the two sources agree with each other. I am releasing it as ablaut: a Rust crate, a Python package, and an npm package via a 106 KB WASM binary, MIT or Apache licensed. You can try it in the browser.

import ablaut
 
c = ablaut.conjugate("aufstehen")
c.present[0]        # "stehe auf"
c.perfect[0]        # "bin aufgestanden"
c.zu_infinitive     # "aufzustehen"

I would be very much open to hearing from users of similar software: linguists, NLP professionals, language app builders. If you have remarks or ideas, please let me know.