DEV Community

Cover image for Making flashcards takes longer than studying, so I built a lazy study coach on open-weight models
Love Yadav
Love Yadav

Posted on

Making flashcards takes longer than studying, so I built a lazy study coach on open-weight models

Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady Submission 🦥

This is a submission for the Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady

What I Built

EchoDeck is a flashcard coach for people who want to learn without running a study system.

Flashcards work, but the chores around them are what make people quit: writing the cards, deciding what to review today, and keeping a streak alive. EchoDeck takes those chores over.

  • It writes the deck. Paste your notes and a local open-weight model turns them into flashcards.
  • It decides what to review. A Leitner-box spaced repetition engine brings each card back just before you would forget it.
  • It quizzes you and grades you. You answer by voice or by typing, and the model judges your answer and maps it to a rating (again, hard, good, easy). You never have to grade yourself.
  • It keeps your streak. All you have to do is show up.

EchoDeck started as an Alexa+ MCP add-on for hands-free study on an Echo Show. For this challenge I added a fully local mode that runs on your own machine with no internet connection and no cloud account. Both modes share the same scheduling engine and the same data file, so nothing about your progress depends on which mode you use.

It is for students, language learners, and anyone cramming for an exam or certification who would rather spend their effort answering questions than building cards.

Demo

EchoDeck runs on a laptop, so the quickest way to see it is to run it. This is the whole setup:

# 1. Start Ollama with a small open-weight model
ollama pull llama3.2        # or: ollama pull qwen2.5-coder:3b
ollama serve

# 2. Run EchoDeck in local mode
cd server
npm run build
MODE=local npm start
Enter fullscreen mode Exit fullscreen mode

Then open http://127.0.0.1:3000/:

  1. Paste some notes into New Deck from Notes and a deck is generated for you.
  2. Switch to Study. Each question is read aloud, and you answer by voice or by typing.
  3. The model grades your answer, shows its reasoning, reveals the back of the card, and updates your Leitner box and streak.

If the model is down or returns something unusable, self-rating buttons appear so a study session never gets stuck.

EchoDeck local mode grading a correct answer: the learner typed

EchoDeck local mode grading a wrong answer: the learner typed

Code

GitHub logo loveyadav1015 / EchoDeck

Build flashcard decks by voice, then review them just in time with spaced repetition and daily streaks.

EchoDeck

Voice-first flashcard coach with spaced repetition — an Alexa+ MCP add-on server.

EchoDeck lets learners create study decks, get quizzed, and track progress entirely by voice through Alexa+. It uses Leitner-box spaced repetition to schedule reviews and provides a visual card UI for Echo Show devices.

ES Module Choice

This project uses ES modules throughout:

  • "type": "module" in package.json
  • "module": "Node16" and "moduleResolution": "Node16" in tsconfig.json
  • All imports use explicit .js extensions (TypeScript resolves these to .ts at compile time)
  • The UI is bundled by Vite (ESM native)

Prerequisites

  • Node.js 24 or newer (node --version)
  • npm (comes with Node.js)

Install

cd server

npm install @modelcontextprotocol/sdk @modelcontextprotocol/ext-apps express zod
npm install -D typescript tsx vitest vite vite-plugin-singlefile @types/node @types/express
Enter fullscreen mode Exit fullscreen mode

After installing, verify:

node --version        # Must be >= 24.0.0
npm ls --depth=0      # All packages should be listed without ERR
Enter fullscreen mode Exit fullscreen mode

Also check that the installed zod version satisfies…

How I Built It

The server is TypeScript (ES modules, Node 24) with Express and zod, and it exposes its features as tools over the Model Context Protocol (MCP). Data lives in a JSON file with atomic writes and corrupt-file recovery. The UI is a single-file Vite build that works both as an Echo Show card and as a standalone browser app.

The open-source AI pieces

Piece What it does Open tool
LLM Generates cards from notes and judges answers Ollama, with small open-weight models (llama3.2, qwen2.5-coder:3b, mistral)
Speech to text Transcribes spoken answers whisper.cpp adapter, with browser speech recognition as a fallback
Text to speech Reads questions aloud Piper adapter, with browser speech synthesis as a fallback

The key design decision: the model never touches the schedule

Leitner scheduling and streak math are plain deterministic code. The LLM does only two language jobs: it creates cards, and it judges an answer and returns a rating. That rating is validated with zod and then passed into the existing SRS function. A wrong or malformed model output can never corrupt your review schedule, and the scheduling tests stay exact.

What's in the local mode

  • Provider abstractions (server/src/local/): LlmProvider, SttProvider and TtsProvider interfaces with Ollama, whisper.cpp and Piper implementations. Core domain and storage code has zero dependency on any provider, so swapping a model is a setting (OLLAMA_MODEL), not a rewrite.
  • CardGenerator: chunks long notes, asks the model for structured JSON, and pipes the result through the existing validateCards step so limits, trimming and de-duplication apply to AI-generated cards exactly as they do to hand-made ones.
  • AnswerJudge: maps a learner's answer to the Leitner rating scale. It extracts JSON robustly, retries once on malformed output, and then falls back to asking the user to self-rate.
  • REST API (mounted under /api in local and both modes): status, decks, generate-deck, study/next, study/judge, study/self-grade, stt and tts.
  • Mode selection: MODE=alexa|local|both, defaulting to alexa, so existing behavior is unchanged. In local mode the server binds to 127.0.0.1 by default.
  • A new MCP tool, generate_deck_from_notes, available when the local controller is configured. The four original tools are untouched.

Lessons from making small models behave

Small models (3B to 8B parameters) drift on formatting. They return a single object instead of an array, or wrap JSON in markdown code fences even when told not to. Three things made it reliable:

  1. Ask for an explicit root object ({"cards": [...]}) instead of a bare array.
  2. Parse flexibly across arrays, wrapper keys and single objects before giving up.
  3. Never let the AI near deterministic business logic like intervals or streaks.

The answer-leak rule

The back of a card is never rendered in the DOM or returned over HTTP before you submit an answer. /api/study/next returns the front only, and the back is revealed in the response to /api/study/judge. This holds in both the REST API and the UI, and it is covered by tests.

How I tested it

  • 120 Vitest tests across 10 files, plus a clean TypeScript typecheck. They cover SRS math (15), streaks (13), validation (15), the store (12), the domain service (21), MCP metadata (2), latency (4), card generation and answer judging with retries and fallbacks (19), the Whisper and Piper adapters (8), and the REST API including mode switching, answer-leak prevention and loopback privacy (11). The model, STT and TTS providers are mocked, so CI needs no models installed.
  • Alexa+ smoke test: 7 of 7 checks pass in MODE=alexa.
  • Local smoke test: 8 of 8 checks pass against a real Ollama instance running qwen2.5-coder:3b. It covers health, provider status, card generation from notes, confirming next_card omits the back, answer judging with model feedback, revealing the back, and streak persistence.

What I verified and what I didn't

I want to be upfront about this. The whole local flow (card generation, judging, SRS, streaks, answer-leak prevention) was verified end to end with a real local model. The whisper.cpp and Piper adapters are covered by unit tests with mocked servers, but I did not run the real binaries, because neither was installed on my machine. I tested voice input and output through the browser's built-in speech features instead. Those browser features are convenient, but depending on your browser, speech recognition may use an online service. If you need strictly offline voice, use the whisper.cpp and Piper path. Typed answers are always fully local.

Why Does Open Innovation Matter?

My first version of EchoDeck only worked through Alexa+. That made it great on an Echo Show, and it also made me notice what I'd given up: all of the intelligence belonged to someone else's platform. Moving the language work to open-weight models changed what was possible.

  • Your notes and weak spots stay on your machine. A flashcard deck is a record of what you're learning, what you keep forgetting, and when you study. In local mode, card generation and answer judging go to a model on 127.0.0.1. The server binds to loopback by default, and the Ollama client enforces a loopback-only address.
  • It works with no internet. The generate-and-study loop needs only Ollama and the EchoDeck server, with no account and no API key.
  • It costs nothing per session. Spaced repetition only works if you do it every day, and a per-use API bill is a good way to make people quit.
  • I can swap models with one setting. A 3B model runs on a modest laptop, and a larger one can grade more strictly. I changed models during development without touching application code.
  • I can change how it behaves. Because I own the prompts, the schemas and the judging logic, I could add the retry-then-self-rate fallback and tune the JSON handling for small models. A closed voice platform doesn't let you change how it reasons.
  • No lock-in. Alexa+ mode is still there, unchanged, if you want hands-free study on an Echo Show. Local mode simply means it isn't required.

The Lazy Payoff

EchoDeck removes the three chores that make flashcards a hassle:

  • No card writing. Notes in, flashcards out.
  • No deciding what to study. The Leitner engine picks the next card.
  • No grading yourself. The model rates your answer, and if it can't, one tap on a self-rating button keeps you moving.

After the one-time setup, a study session is: open the page, answer what you're asked, and close it.

Prize Categories

  • Best Use of TabPFN : Use TabPFN, Prior Labs' tabular foundation model, to forecast, predict, classify, or spot anomalies from historical data like a CSV. Prior Labs strongly recommends the latest version, TabPFN-3.5: it's open-weight and free to use once you sign the non-commercial license at platform.priorlabs.ai, and you can then run it locally. The API adds features like Thinking Mode. Prior Labs favors projects that use TabPFN inside a wider prediction agent (with or without the MCP server).
  • Best Use of GitHub Copilot : Build with Copilot's coding agent, the Copilot CLI, or the new Copilot app, automate your project with GitHub Actions, or review contributors' PRs with Copilot.
EchoDeck: Local Open-Model Mode with Ollama, Whisper, Piper
You

Task: Add a local open-model mode to EchoDeck (alongside Alexa+, not replacing it)

Context

EchoDeck is a TypeScript (ES modules, Node 24) MCP server in server/ that powers a voice-first flashcard coach with Leitner-box spaced repetition. Today it only works as an Alexa+ add-on: Alexa does the speech-to-text, understanding, answer judging, and text-to-speech, and EchoDeck only exposes MCP tools (create_deck, next_card, rating/progress tools, streaks).

I'm entering the Hacktoberfest "Open-Source AI, Slow and Steady" challenge. The open-source AI has to be what makes the project work. So add a second, fully local mode that uses open-weight models, and keep the Alexa+ path working exactly as it does now.

Step 0: Read before you write

Do not assume the code structure. First inspect server/ (entry point, MCP tool registration, service, srs, streak, validation, store, tests, package.json, .env.example, Dockerfile) and README.md. Then write a short plan (files to add/change, interfaces, risks) and follow it. Reuse existing service functions and conventions (ESM imports with .js extensions, zod validation, atomic store writes, logging style).

Goal

A user can run EchoDeck with no internet and no cloud account:
1. Generate a deck from notes. Paste or upload text, and a local LLM creates flashcards (this is the "lazy" feature).
2. Study by voice or text locally. Cards are read aloud, the user answers by speaking or typing, and a local LLM judges the answer and maps it to an SRS rating.
3. Same SRS, same data. Decks, boxes, and streaks use the existing deterministic Leitner logic and the same data file in both modes.

Open-source stack (all optional, all configurable)

  • LLM: Ollama's HTTP API (default http://[REDACTED]:11434), model configurable. Use a small default (e.g. a ~3-8B Llama, Qwen, or Mistral variant) so it runs on a laptop.
  • Speech to text: whisper.cpp (or an equivalent local Whisper server/CLI) behind an adapter.
  • Text to sp <truncated 2511 bytes> providers **mocked, so CI needs no models. Cover: card generation (valid, malformed, over-limit, empty, duplicate), answer judging (correct, wrong, malformed JSON, retry then fallback), provider-unavailable fallback, mode selection, and that no outbound calls happen in local mode.
  • Add an optional smoke script (e.g. scripts/local-smoke-test.sh) that runs against a real local Ollama if present and skips cleanly if not.
  • Run npm test and npm run typecheck and fix everything before finishing.

Docs

  • Add a "Local mode (open-source AI)" README section: prerequisites, how to install Ollama, whisper.cpp and Piper, recommended small models, env vars, how to run, and a Docker/compose option if practical.
  • Add a short architecture note describing both modes and where the LLM is and isn't used.
  • Be accurate. Only document what you actually implemented and verified. Do not invent benchmarks, speeds, or accuracy numbers.

Working style

  • Make small, logical commits with clear messages.
  • Add as few new runtime dependencies as possible, and justify each one.
  • If something is ambiguous or a requirement conflicts with the existing code, choose the least invasive option and note it in your final summary.

Definition of done

  • MODE=alexa behaves exactly as before.
  • MODE=local works end to end with Ollama running and no internet: generate a deck from pasted notes, study it, get answers judged, and see SRS boxes and streaks update in the same data file.
  • All tests and the typecheck pass.
  • Final summary lists the files changed, how to run local mode, what you verified by hand, and anything left unverified.
Agent

I have launched the local smoke test against the live local Ollama model (qwen2.5-coder:3b), and will review the results as soon as it completes.

Local Open-Model Mode for EchoDeck

We have added a fully local, offline open-model mode to EchoDeck alongside the existing Alexa+ MCP server. EchoDeck now runs with no internet connection and no cloud account using open-weight AI models for card generation and answer evaluation, while preserving the exact same deterministic Leitner spaced repetition engine and atomic data persistence.

For detailed architecture breakdown and decisions, refer to the Implementation Plan.


What Was Implemented

1. Provider Abstractions (server/src/local/)

2. Real Smoke Tests Verified

  • Alexa+ MCP Smoke Test (scripts/smoke-test.sh):
    • Verified 7/7 checks pass in MODE=alexa (HTTP 200 on initialize, create_deck, next_card, grade_answer, and get_progress).
  • Local Open-Model Smoke Test (scripts/local-smoke-test.sh):
    • Ran against real local Ollama running qwen2.5-coder:3b at http://[REDACTED]:11434.
    • Verified 8/8 checks pass: /health, /api/status, end-to-end card generation from notes, verification that back is omitted from next_card, answer judging with model feedback, back revelation, and streak persistence.

What Was Hand-Verified vs. Unverified

  • Verified by hand / local machine:
    • MODE=alexa backward compatibility with original MCP clients and smoke script.
    • MODE=local end-to-end flow with live Ollama qwen2.5-coder:3b.
    • Answer-leak prevention across both UI and REST API.
    • Vitest test suite and TypeScript compiler.
  • Left unverified:
    • Live whisper.cpp binary and Piper binary (mocked in unit tests; tested with Web Speech browser fallbacks as neither binary was pre-installed in the Linux host environment).

Git Commits Made

  • a070d72: feat(local): add local AI provider abstractions (Ollama, Whisper, Piper) and controller
  • 3e8c0a4: feat(api): add local mode REST endpoints and integrate with MCP server
  • 7bf2554: feat(ui): add dual-mode web app for local study and note flashcard generation
  • ae1836c: test(local): add Vitest suites and smoke test, update architecture and README docs
You

Save this chat as a DevRelay agent session and give me the embed tag and nice work

Agent

Top comments (0)