Project Info
Inspiration
Growing up, we had a family friend on the autism spectrum who struggled with reading and speaking aloud. We watched how much early intervention mattered for kids like him, and how much of that intervention depended on having access to the right specialists at the right time. That gap stuck with us. When we started building Fluently, we wanted to create something that could do what a speaking specialist does. We aren't aiming to replace the human connection, but instead, make that level of precision accessible to every family, regardless of income or location. The earlier a reading difficulty is caught and understood, the better the outcome. Fluently is built on that belief.
What it does
Fluently listens to your child read aloud and does what a reading/speaking specialist does, in real time. A child reads a passage out loud. Every word lights up on screen: green if correct, red if an error, yellow if they hesitated. At the end of 60 seconds, Fluently produces a report that doesn't just count mistakes but explains what they mean. Is this a decoding issue or a phrasing fluency issue? These have different causes and different interventions, and most reading tools can't tell the difference. A 2D PassageMap lets you navigate material by complexity and register, with Claude generating fresh passages on demand. After each session, Redis vector search finds the optimal next passage based on where errors were concentrated, moving the reader harder in exactly the right dimension. Over multiple sessions, Fluently builds a longitudinal model of the reader's error patterns, shifting from a snapshot of today to a genuine reading profile that gets sharper every session.
How we built it
Part 1: Deterministic pipeline The scoring engine is fully deterministic and AI-free. Deepgram Nova-3 streams word objects with start, duration, and confidence fields. A custom Levenshtein alignment function matches the transcript against the expected passage word-by-word, classifying each word as correct, substitution, omission, insertion, hesitation (\(> 500\text{ms}\) pause), or acoustically uncertain (confidence \(< 0.75\), excluded from all error metrics to avoid penalizing accent variation). A separate metrics pass computes WCPM benchmarked against DIBELS 8th Edition grade-level thresholds (Grade 2: \(\geq 125\), Grade 4: \(\geq 141\), Grade 6: \(\geq 135\), accuracy threshold \(\geq 96\%\)), pause placement using compromise.js to identify syntactic boundaries in the target text, and self-correction rate as its own positive signal separate from the error taxonomy. Part 2: AI layer Claude receives a structured JSON object (never raw audio or transcript) and returns a plain-language report calibrated to the reader's DIBELS tier (intensive: at risk, strategic: some risk, core: on track), the passage's register (informal passages don't penalize contractions or casual phrasing), and any persisting error patterns across prior sessions. The prompt runs in one of three modes: snapshot (first session), comparison (second session), or pattern-recognition (third session onward), each with distinct framing language. In pattern-recognition mode, Claude receives a full markdown table of historical metrics and is explicitly instructed to re-read every number from the table rather than recall from context. This eliminated hallucinated historical values during testing. Part 3: Redis AI integration Every session stores a 5-dimensional skill vector $$\mathbf{v} = [\text{complexityHandling},\ \text{registerHandling},\ \text{wcpmPercentile},\ \text{pausePlacementScore},\ \text{selfCorrectionRate}]$$ and full error metrics in Redis. After each session, computeNextTarget() identifies the weakest map-axis dimension (complexity or register only, since non-map dimensions like WCPM are addressed through Claude's exercise recommendations rather than passage movement) and computes the optimal next position in the 2D skill space. The target only escalates on an advance recommendation. On retry, the position stays fixed so the reader consolidates at the same level rather than compounding difficulty. A KNN search (FT.SEARCH) finds the nearest existing passage. If no close match exists within distance \(0.1\), Claude auto-generates a fresh passage at the exact target coordinates and stores it in Redis, growing the library organically with every session. The full reader history is fetched from Redis before every Claude call, enabling longitudinal pattern recognition across sessions. Part 4: PassageMap Instead of a grade picker, a draggable 2D SVG canvas lets users place a pin anywhere across the full K–adult complexity and casual–formal register space. Each pin placement calls Claude to generate a fresh ~70-word passage at those exact coordinates. After a session, a dashed blue arrow on the map shows where the recommendation moves the reader next, visually grounding the concept of "harder in the right dimension" in something a parent or child can immediately understand.
Challenges we ran into
Accent fairness A child who pronounces "th" as "d" should not have that counted as an error. We implemented confidence score filtering and switched to accent-agnostic English recognition, then found and fixed a subtle bug where uncertain words were excluded from error counts but still dragging down the accuracy denominator silently, penalizing the reader anyway through a different metric. Longitudinal prompt hallucination In early testing, Claude would misstate historical WCPM values when given prior session data as prose. Switching to a structured markdown table with an explicit instruction to re-read every number from the table rather than recall from context eliminated the issue entirely. Redis vector search projection When running KNN search against our passage index, the query was returning passage identifiers and titles as literal undefined strings. The issue was a subtle mismatch between which fields Redis indexes for search and which fields it actually returns in query results.
Accomplishments we're proud of
A fully deterministic scoring pipeline where Claude interprets but never detects Accent fairness built into the confidence filtering layer so no child is penalized for how they speak Redis powering genuine vector search across a 2D pedagogical skill space, not just caching Longitudinal error tracking that shifts Claude's diagnostic language after three sessions
What we learned
The most powerful thing you can do with an LLM is constrain what it has to guess. Every time we moved a computation out of Claude and into a deterministic function, the output got more reliable and the AI layer got more useful. Real equity has to be designed into the architecture, not added as an afterthought. Accent fairness required deliberate decisions at the data layer, not just the UI. And longitudinal context changes everything: a system that remembers is fundamentally different from one that scores.
What's next
Expanding to ESL adult learners with register-specific passage sets calibrated to workplace and academic English Support for speech therapy use cases including fluency disorders, apraxia, and progressive speech conditions like Huntington's disease, where tracking subtle degradation in prosody and phrasing over time could serve as an early clinical signal Difficulty regression on retry: backing off in the weak dimension before re-attempting, which is what reading specialists actually do
Fluently
Oral reading fluency assessment tool. A child reads a passage aloud, Deepgram transcribes with word-level timestamps, a deterministic Levenshtein alignment pipeline scores the reading against the expected passage, and Claude generates a plain-language diagnostic report.
Fluently also tracks a reader's skill profile across sessions in Redis (vector search over an AI-generated passage library) to recommend the next passage's difficulty and register, and to give Claude longitudinal context ("this is the student's 3rd session — has phrasing improved?") instead of grading every session in isolation.
Prerequisites
- Node.js 18+
- Redis Stack (not plain Redis — vector search needs the RediSearch module, which plain Redis doesn't include)
- A Deepgram API key
- An Anthropic API key
Installing Redis Stack (macOS)
brew tap redis-stack/redis-stack
brew install --cask redis-stack-server
Start it manually before running the app (it's a cask, so brew services doesn't manage it):
redis-stack-server
Leave that running in its own terminal tab. It listens on redis://localhost:6379 by default.
For other platforms, see redis.io/docs/install/install-stack — or point REDIS_URL at a hosted Redis Cloud database (free tier supports RediSearch) if you don't want to run it locally.
Setup
# 1. Install dependencies
npm install
# 2. Add API keys
cp .env.example .env.local
# Fill in DEEPGRAM_API_KEY, ANTHROPIC_API_KEY, and REDIS_URL in .env.local
# (REDIS_URL=redis://localhost:6379 if you're running Redis Stack locally per above)
# 3. Make sure redis-stack-server is running (see Prerequisites)
# 4. Run dev server
npm run dev
Then open http://localhost:3000.
Scripts
| Command | What it does |
|---|---|
npm run dev | Start the Next.js dev server |
npm run build | Production build |
npm run start | Run the production build |
npm run lint | ESLint |
Pages
/— landing page/practice— the core flow: pick a passage (drag a point on the complexity/register map, or read an existing one), record yourself reading it, get a diagnostic report and a recommended next passage/progress— longitudinal view of a reader's session history
Architecture
See docs/architecture.md for full data flow, and docs/ROADMAP.md for current build status vs. what's planned.
The pipeline:
- Deepgram streams word-level transcription with timestamps
- Levenshtein alignment scores every word (correct / substitution / omission / insertion / uncertain)
- Metrics computation derives WCPM, error counts, pause placement, self-corrections
- A skill vector (complexity handling, register handling, WCPM, pause placement, self-correction) is computed per session and stored in Redis
- Redis vector search (
FT.CREATE/FT.SEARCH) matches the reader's next-best passage target against a library of AI-generated passages, or generates a new one if nothing close exists - Claude receives a structured JSON object (metrics + DIBELS tier + prior-session history table) and generates the diagnostic report
Claude never sees raw audio or transcript — only structured data from the deterministic pipeline.
Tech Stack
- Next.js 14 (App Router), TypeScript, Tailwind CSS
- Deepgram SDK (streaming, word timestamps)
- Anthropic SDK (diagnostic report generation, passage generation)
- Redis Stack (
redisnpm package) — vector search over the passage library, per-reader session history - compromise.js (syntactic boundary detection for pause placement)
- gsap (animated passage-map dot grid)
Hackathon — UC Berkeley AI Hackathon 2026
Track: Ddoski's World Sponsors: Deepgram, Anthropic, Redis
Analysis
View
Metric
- 37
- 7
- 7
Figures cover GitHub contributors during the hackathon window. A co-authored commit counts in full for each author, so per-member totals add up to more than the whole-team figures.
Technology
- AnthropicIn code
- CSSIn code
- JavaScriptIn code
- Next.jsIn code
- ReactIn code
- RedisIn code
- Tailwind CSSIn code
- TypeScriptIn code
8 of 8 appear in the indexed code.
AI coding agents
- Claude CodeCommits
Detected from committed agent config files and commit authorship. Absence of a signal is not proof an agent was unused.
Codebase size
Source size
145 KB
Source files
31
Counts recognized source files only; vendored directories, binaries and lockfiles are excluded, so this is smaller than the repository on disk.
Repository
appleorange/fluently
38 files · 3.3 MB · @ 564a59b
Structure
Interface
14 files · 37%Screens, components and styles rendered to the user.
API & routing
4 files · 11%Request entry points: routes, handlers and controllers.
Application logic
10 files · 26%Domain rules, services and shared utilities.
Supporting
Layers are inferred from where files sit in the tree, not from reading the code. A project that names its directories unconventionally will read oddly here — open the file browser to check anything the diagram implies.
Languages
- TypeScript96%
- Markdown3%
- CSS2%
- JavaScript0%
Share of indexed source by file size. Binary and vendored files are excluded.
Dependencies
package.json
npm · 19- @anthropic-ai/sdk
- @deepgram/sdk
- compromise
- diff-match-patch
- gsap
- next
- react
- react-dom
- redis
- +10 more
Declared in the repository’s manifests at the indexed commit. A declared package is not proof it is used, and runtime dependencies are listed first.
Export this project's context (description, README, evidence, key source files) to chat with an AI agent elsewhere.