Mnemo
Mnemo is a language-learning co-pilot that blends human-curated goals with adaptive LLM-driven practice. These docs describe the experience we’re aiming for so we can stay aligned before writing code.
MVP Scope
- Define study tracks. A learner can add multiple target languages, set a level/descriptor (“basic German”), and load the curated grammar-factor list for that track.
- Curate grammar + vocab. Per track, they accept/prune the suggested grammar factors and author vocab groups in plain English. Everything is stored per user/track so it’s easy to customize.
- Run translation drills. Mnemo selects (grammar factor, vocab group) pairs with the highest need, asks the LLM generator for a prompt, and collects the learner’s translation.
- Judge + record attempts. A judge agent grades the response (grammar + vocab) and the result updates SRS scores and the journal.
- Surface what’s next. The dashboard shows track-level decay state and points the learner to the next drill. Data exports and deletions work from day one.
The rest of the mdBook dives into user workflow, architecture, and the build journal.
User Guide Overview
Mnemo’s MVP journey boils down to three steps:
- Tell us what you’re learning. Specify your native language, add a target language (e.g., “advanced German”, “basic Spanish”), and enable the grammar factors you care about.
- Describe vocab areas. Create plain-English buckets (“ordering food in Berlin”, “Spanish travel emergencies”). Mnemo expands each bucket into concrete words/phrases you can prune or extend.
- Practice translations. Hit Test Me (5/10/20 minute sessions). The agent shows a sentence to translate either to or from your native language, scores how well you handled each word + grammar concept, and schedules the next review.
Everything else in the docs supports that loop. For a page-by-page walkthrough, see the UI Site Map.
UI Site Map
This chapter sketches the MVP surfaces so you can visualize how Mnemo will feel before we cut any HTML. Each section shows the page layout (ASCII mockups) plus the backing data flow so we know exactly which tables/LLM calls power every click.
1. Dashboard
┌───────────────────────────────────────────────┐
│ Mnemo │
│-----------------------------------------------│
│ Tracks [+ Add track]
│ [★] Greek — "basic travel" [Test Me] [⚙] │
│ [ ] German — "advanced" [Test Me] [⚙] │
│ │
│ Next up │
│ Grammar: Present tense -ω verbs (due now) │
│ Vocab: Ordering coffee in Athens │
│ │
│ Session length │
│ ( slider ▭▭▭▭▭ ) 10 prompts │
│ │
│ [ Test Me ] │
└───────────────────────────────────────────────┘
- Landing page after login.
- Lists every language track with a quick “Test Me” button and a link to configure (
[⚙]). - Dedicated + Add track button opens the track wizard without diving into settings.
- Shows the current queue preview chosen by the scheduler so learners know what’s coming.
- Session length slider (or dropdown) feeds the scheduler before it generates the batch.
Data & Actions
| Interaction | Reads | Writes / Calls |
|---|---|---|
| Render track list | user_language_tracks (filtered by user_id), joined with track_factor_selection for status summary | — |
| “Next up” preview | Scheduler pulls due pairs from srs_state + track_factor_selection, plus vocab metadata from vocab_groups | — |
| Session length slider | none (pure UI until submit) | value passed to scheduler request |
| Test Me | Scheduler queries srs_state and enqueues (grammar_factor_id, vocab_group_id) pairs; generator prompt built from those rows | generator LLM call (no DB write until answer) |
| + Add track | — | navigates to add-track page |
2. Add / Edit Language Track
┌───────────────────────────────────────────────┐
│ New Track │
│-----------------------------------------------│
│ Language: [Greek ▾] │
│ Descriptor: ["basic travel"] │
│ Native lang: English (read-only) │
│ Experience: [Beginner ▾] │
│ │
│ [Continue to Grammar ▶] │
└───────────────────────────────────────────────┘
- Lightweight wizard (one screen for MVP) invoked from “Add track” or
[⚙]. - Language dropdown lists only the curated bundles we ship—no custom languages in the UI.
- Descriptor is the only freeform field so learners can remind themselves of the track’s intent.
- Native language is shown for context (editable elsewhere in profile settings).
- Continue button jumps straight to the grammar checklist for that track.
Data & Actions
| Interaction | Reads | Writes / Calls |
|---|---|---|
| Populate language dropdown | language_grammar_factors (distinct language_name seeded in code) | — |
| Show native language | users.native_language | — |
| Save/Continue | — | Insert into user_language_tracks (language, descriptor, experience, cadence). Seed track_factor_selection rows for every curated factor with default enabled=false. |
| Cancel | — | none |
3. Grammar Checklist
┌───────────────────────────────────────────────┐
│ Greek Grammar Factors │
│-----------------------------------------------│
│ [x] Alphabet basics 12 prompts · 92% ✔ │
│ [x] Present tense -ω verbs 7 prompts · 57% ✔ │
│ [ ] Café ordering phrases 0 prompts yet │
│ [ ] Transit emergencies 0 prompts yet │
│-----------------------------------------------│
│ Stats update automatically from your attempts │
│ │
│ [Save changes] [Back to track] │
└───────────────────────────────────────────────┘
- Shows the curated checklist shipped in code for the selected language.
- Each row is just a checkbox: enabled items feed the scheduler; disabled ones are ignored.
- Attempt counts + correctness summaries are read-only data pulled from recent practice history.
- Save writes
track_factor_selection; there’s still no freeform editing of factors in MVP.
Data & Actions
| Interaction | Reads | Writes / Calls |
|---|---|---|
| Render checklist | language_grammar_factors (for the selected language) joined with track_factor_selection for this track_id | — |
| Show stats | Aggregate attempts scoped to track_id + grammar_factor_id (count + % correct derived from last N judge entries) | — |
| Toggle checkbox | — | Updates track_factor_selection.enabled (boolean) in place. |
| Save | — | persists all pending toggles in one transaction |
4. Vocabulary Area Builder
┌───────────────────────────────────────────────┐
│ Vocabulary Areas (Greek) │
│-----------------------------------------------│
│ ▼ Ordering coffee (12 words ready) │
│ καφές ✖ ελληνικός ✖ ζάχαρη ✖ │
│ [ + More suggestions ] │
│ ▶ Transit pickups (collapsed) │
│ │
│ + Add new area │
│-----------------------------------------------│
│ Add Area │
│ Description: ["Emergency room visit"] │
│ Guidance: (textarea for extra hints) │
│ [Generate 15 words] │
│ │
│ Suggestions │
│ νοσοκομείο ✖ τραύμα ✖ ασφάλιση ✖ │
│ │
│ [Accept list] [Regenerate] [ + More suggestions ]
└───────────────────────────────────────────────┘
- Existing vocab areas render as an accordion: collapsed rows show the label + word count, while expanded rows expose the words with per-word trash icons and a per-area “+ More suggestions” button.
- Bottom panel lets the user describe a new area; clicking Generate calls the LLM and shows the 10–20 suggested words.
- MVP lets you accept the current list, regenerate entirely, or ask for a few more words before saving.
Data & Actions
| Interaction | Reads | Writes / Calls |
|---|---|---|
| Render area list | vocab_groups (per track_id) + word counts from group_words join vocab_items | — |
| Expand area | Fetch group_words + vocab_items rows for that group | — |
| Delete word | — | Delete row from group_words; cleanup orphaned vocab_items if no other references |
| + More suggestions (existing area) | Context: track_id, area metadata, remaining words; call vocab generator LLM | On accept, insert new vocab_items + group_words |
| Add new area → Generate | — | LLM call with (track_id, area_description, guidance) |
| Accept new area list | — | Insert into vocab_groups, vocab_items, group_words |
5. Practice Session (Test Me)
┌───────────────────────────────────────────────┐
│ Prompt 3 of 10 │
│ Grammar focus: Present tense -ω verbs │
│ Vocab focus: Ordering coffee │
│-----------------------------------------------│
│ Translate into Greek: │
│ "I would like a sweet coffee, please." │
│ │
│ [ textarea for answer ] │
│ │
│ [ Submit answer ] │
└───────────────────────────────────────────────┘
- Displays the single prompt currently under review with explicit grammar + vocab badges.
- Submit posts to the judge; there are no hints, retries, or partial credit UI in MVP.
- After submission we immediately show verdict + model answer inline (next section).
Data & Actions
| Interaction | Reads | Writes / Calls |
|---|---|---|
| Render prompt | Scheduler selects due pair from srs_state + track_factor_selection; generator LLM pulls: factor metadata (language_grammar_factors), vocab words (vocab_items), recent attempts for context | Inserts a stub row in attempts with generator payload + prompt ID |
| Submit answer | — | Judge LLM invoked with prompt + learner answer. Result stored in judge_actions + updates attempts.verdict. Update srs_state scores for the affected factor + vocab group. |
6. Attempt Verdict & History
┌───────────────────────────────────────────────┐
│ Result │
│-----------------------------------------------│
│ Focus item Verdict Notes │
│ Grammar • Present -ω verbs ✖ Missing article
│ Word • καφές ✔ Nailed it
│ Word • ζάχαρη ✖ Forgot accent
│-----------------------------------------------│
│ Model answer: «Θα ήθελα έναν γλυκό καφέ, παρακαλώ.»
│ Your answer: «Θέλω γλυκό καφέ παρακαλώ» │
│-----------------------------------------------│
│ [Next prompt ▶] │
│ │
│ Recent attempts │
│ ✔ #1021 Greek / coffee / present tense │
│ ✖ #1020 Greek / transit / aorist │
└───────────────────────────────────────────────┘
- Immediately follows the prompt view after judge response.
- Shows per-item verdicts for every grammar factor + vocab word that was targeted, mirroring what we log in the DB.
- Model vs learner answers stay visible for quick comparison.
- Recent attempts sidebar offers quick navigation across the session.
Data & Actions
| Interaction | Reads | Writes / Calls |
|---|---|---|
| Render verdict table | judge_actions rows filtered by attempt_id, joined with language_grammar_factors/vocab_items for display names | — |
| Show model vs learner answer | attempts.prompt_blob + attempts.answer_blob | — |
| Recent attempts list | latest attempts for the track ordered by created_at | — |
| “Next prompt” | same as Practice Session flow (scheduler + generator) | See Section 5 |
7. Account & Admin Console
┌───────────────────────────────────────────────┐
│ Settings │
│-----------------------------------------------│
│ Profile │
│ Display name [Niko] │
│ Email [nikomatsakis@…] │
│ Native lang [English ▾] │
│ Password [••••••] [Change] │
│ │
│ Data │
│ [Delete my account] │
│-----------------------------------------------│
│ Admin (password required) │
│ [Unlock console] │
│ Users: (table appears after unlock) │
└───────────────────────────────────────────────┘
- Combines user profile edits, account deletion, and the admin override switch.
- Admin unlock (via env-set password) reveals a simple table where the operator can reset passwords, remove users, or pause sign-ups; no multi-tenant tools yet.
- Data export is intentionally deferred until after the MVP loop is live.
Data & Actions
| Interaction | Reads | Writes / Calls |
|---|---|---|
| Render profile | users row for the authenticated user | — |
| Update profile fields | — | Update users (display name, email, native language). Password change writes to users.password_hash. |
| Delete my account | — | Delete from users cascade to tracks/vocab/attempts (enforced via FK). |
| Admin unlock | — | Checks shared admin password (env). On success, loads summary list from users and user_language_tracks. |
| Admin actions | users/user_language_tracks as needed | Reset password, delete user, toggle “allow sign-ups” flag stored in config table (future). |
With these mockups and data notes we can now trace an MVP user story end to end—UI, queries, writes, and LLM calls all line up with the schema from the Architecture chapter. This should stay in sync with future iterations so we keep the “docs from the future” promise before building screens.
Configuration
Native Language
- Set once (editable). We use it to decide translation direction and to localize UI copy later.
Target Languages
For each target language you add:
- Goal + proficiency: freeform descriptor plus a dropdown (beginner / intermediate / advanced / expert).
- Grammar checklist: we show the full curated list for that language with checkboxes. Enabling/disabling a factor doesn’t delete its data—we still display last-seen time, accuracy, and next scheduled review so you can re-enable later.
- Per-factor stats: sparkline or simple text showing “last attempted”, “confidence”, and “next due”. Even disabled factors keep their history visible.
Vocabulary Areas
- Describe an area in plain English (“Berlin coffee order”, “Spanish emergency room”). Mnemo stores the description + tags and asks the LLM to propose concrete words/phrases.
- Each word entry tracks: spelling, meaning/notes, associated areas, accuracy, next due time. Because a word can belong to multiple areas we keep a
wordstable and a join table for(word_id, area_id). - Actions per area: “add more words” (re-prompt the LLM), “delete word”, or “move word to another area”. Removing a word from one area doesn’t erase its history if it still belongs elsewhere.
What You See on the Settings Page
- Native language summary.
- List of target languages with goal + quick stats.
- Within each language, the grammar table and vocabulary areas described above.
- Buttons for exporting/deleting your data.
That’s all the configuration we need for MVP; everything feeds the Test Me loop.
Practice Flow
From the learner’s perspective:
- Tap “Test Me.” Pick a 5, 10, or 20 minute session from the dashboard. (We remember your native language so sentences can go both directions.)
- Translate once. Mnemo serves a sentence that targets a specific grammar factor + vocab area. Sometimes you translate into the target language; other times back into your native language. You get a single input box—no retries in MVP.
- Judge + track. The judge agent scores the attempt, noting which words/concepts were solid and which need more work. That data updates the SRS queues for the vocab area and grammar factor (even if that word appears in multiple areas).
- Repeat until time’s up. Once the timer expires we show a quick summary (attempts, strengths, upcoming focus) and drop you back on the dashboard.
That’s the simple loop we’re committing to before any extra bells or whistles.
Architecture Overview
Mnemo ships as a Rust web service with a lightweight front-end, a prompt-orchestration layer, and a persistence core. Key themes:
- Deterministic orchestration. Generator + judge agents use typed prompts so we can version/test them.
- Event-sourced attempts. Every learner response stores raw prompt, answer, judge verdict, and metadata for re-grading.
- Modular decay engine. Scheduling logic is isolated so we can evolve SRS math without touching UI layers.
- Batteries-included hosting. The repo includes a Dockerfile + Railway config so anyone can deploy with minimal setup (LLM API key + admin password). Built-in auth is simple email/password, with an admin login to delete users, lock sign-ups, or reset credentials.
- Data governance & multi-user readiness. Each table is scoped by
user_id, retention policies are explicit, and export/delete flows are part of the contract (see the data retention section).
The following pages break down the components and data model.
System Components
Web Interface (Axum + HTMX)
- Minimal pages: dashboard (track list + queues), configuration (language factors + vocab groups), practice screen.
- Keep it server-rendered for MVP; bots/mobile clients can come later once the HTTP surface stabilizes.
LLM Orchestrator
- Two stateless prompt functions powered by
deterministic:- Generator takes
(track_id, grammar_factor_id, vocab_group_id)plus language metadata and returns one translation prompt + answer key. - Judge takes the prompt context + learner answer and emits a single verdict (no retries/state machines in MVP).
- Generator takes
- Prompts live in
prompts/alongside fixtures so we can spell out exactly what context we send and regression-test edits.
Scheduler & Decay Engine
- Lightweight priority queue per track using the exponential decay math outlined in the Data Model chapter (start simple; evolve toward SM-2 if needed).
- Inputs: attempt history + the learner’s requested session length. No boosts or multi-step logic yet.
- Outputs the next
(grammar factor, vocab group)pair whenever the user hits Test Me.
Persistence Layer
- SQLite via
sqlxto start. - Tables cover: users, language_grammar_factors (curated in code), track_factor_selection, vocab_groups, vocab_items, attempts, judge_actions, srs_state, journal_entries.
- Migrations checked in; Postgres on Railway will use the same ones.
Audit Trail (MVP)
- Basic request/response logging for LLM calls.
- Manual journal entries optional; auto-insights deferred until after the core loop is solid.
Data Model & SRS
Core Tables
| Table | Purpose | Key Fields |
|---|---|---|
users | learner profile | id, handle, timezone, notification defaults |
user_language_tracks | per-language configuration | id, user_id, language_name, qualifier, descriptor, cadence prefs, experience level |
language_grammar_factors | curated grammar factors per language (checked into code + seeded to DB) | id, language_name, category, factor_key, description |
track_factor_selection | per-track subset + status | track_id, language_factor_id, status, notes |
vocab_groups | user-authored sets per track | id, track_id, description, tags, seed list |
vocab_items | resolved tokens | group_id, text, lemma, metadata |
attempts | exercise log | id, track_id, grammar_factor_id, vocab_group_id, generator_version, judge_version, prompt_blob, answer_blob |
judge_actions | fine-grained decisions | attempt_id, action_type, payload |
srs_state | decay metrics | entity_type (factor/group/item), entity_id, track_id, score, last_reviewed |
The key idea: we version and store the grammar/vocab factors ourselves when we add a language. LLMs can still suggest subsets, but the authoritative catalog ships with Mnemo.
SRS Algorithm (v1)
- Each entity starts at score
0.3(needs practice) within its track. - After each attempt:
- Correct:
score = min(1.0, score + α). - Close:
score = min(1.0, score + β). - Incorrect:
score = max(0.05, score - γ).
- Correct:
- Natural decay:
score *= e^{-λ * Δt_hours}applied during scheduling. - Scheduler targets entities whose effective score drops below track-specific thresholds.
We’ll iterate on α/β/γ/λ empirically and capture findings in the Journal.
Data Retention & Multi-user Plan
Per-user Data Retained (MVP)
| Category | Details | Notes |
|---|---|---|
| Profile | user_id, display name, primary email (+ optional secondary emails), password hash, native language, timezone | Stored in users. Admin can reset passwords or lock sign-ups. |
| Grammar roadmap | Enabled factors + status + notes | Stored in track_factor_selection. Notes stay plain text for easy export. |
| Vocabulary | Areas + generated words (with shared word table) | vocab_groups, vocab_items, group_words join table so one word can belong to many areas. |
| Practice attempts | Prompt metadata + learner answer + judge verdict | Structured columns in attempts and judge_actions (no opaque JSON). We keep full history until a user deletes it. |
| Journal entries | Freeform reflections | Optional; user-controlled. |
Exports produce JSON/CSV dumps of the tables above. “Forget me” deletes rows immediately.
Storage & Isolation
- SQLite locally, Postgres on Railway. Same schema and migrations in both.
- Every table includes
user_id(andtrack_idwhere relevant) so queries always scope to the current user. - Credentials/LLM keys live in Railway/
.env, never inside the DB. - Backups: Railway keeps daily snapshots; later we can add a “dump to Git repo” toggle if we want versioned backups.
Authentication & Admin Controls
- Simple email/password login for now. No magic links or external OAuth in MVP.
- Admin password (set via env) unlocks a tiny console to delete users, reset passwords, or toggle “allow new sign-ups”.
- Users can change their own email, password, and native language at any time.
2026-03-28 — Kickoff
- Set project codename to Mnemo (short for Mnemosyne, goddess of memory).
- Defined desired learning workflow before coding: grammar roadmaps, plain-English vocab, generator/judge agents, decay-driven scheduling.
- Established tooling expectations: mdBook in
md/, PR-only workflow, SSH deploy key for code pushes, PAT for PR/issue actions. - Next up: refine docs with concrete examples, then start translating them into schema + prompt contracts.