8
SRS rungs · 1 to 240 days
2
LLM providers · env-flip rollback
A private beta, measured by decisions and craft rather than traction. The interesting part of an early bet is the reasoning, not a number I do not have yet.
The problem & the bet
You have drilled the cards. You still cannot speak. Anki and WaniKani built you a huge passive vocabulary, but recognition is not production, and the moment you have to actually say something in Japanese, nothing comes out.
Kaiwaflow’s bet is to own that one job: the output halfof a learner’s practice. Bring the words you already study, and an AI partner (Sensei) builds real conversations around them, weaving in the ones you are due to review on purpose, so the vocabulary finally comes out of your mouth. It is explicitly complementary. Keep your Anki, keep your tutor. This is the place you use what they put in.
THE GAP other tools leave open
recognize ########## "I know this word" <- Anki / WaniKani stop here
produce ##........ "...but I froze when I had to say it"
HOW KAIWAFLOW CLOSES IT (Living Recall)
import deck -> word comes due -> Sensei steers the topic so YOU must say it
^ |
+------- SRS credit <- you produce it unaided <------+Why this bet
I built and sunset two products before this one. Exityear and Vectorcache both taught the same lesson: I could ship a correct product, but the market was the problem. Both landed in saturated spaces with entrenched incumbents and no durable wedge for a solo builder. So the question for the next bet was not “can I build it,” which I had answered twice. It was “is there a job nobody has closed?” Choosing what to build on differentiation instead of buildability is the muscle those two sunsets trained.
It is also personal. [Personal hook, replace me: your real one-line reason, e.g. you studied Japanese for years and still froze the moment you had to speak.] Kaiwaflow’s Living Recall loop is the wedge: the one job the incumbents (Anki, Duolingo, tutors) structurally do not do, which is turning passive vocabulary into speech.
How Living Recall works
A chat turn is one streamed model call with defensive parsing on both ends. Your due words are selected by a plain database query, not a vector search, and woven into the prompt. As Sensei streams back, the server pulls structured signals out of the text and, when you have genuinely produced a due word unprompted, credits it back to your schedule.
KAIWAFLOW: one chat turn
========================================================================
INPUT browser speech-to-text (live) or typed text
[ Whisper route built, shipped dark: Pro-gated + unwired ]
|
v POST /api/chat (Server-Sent Events)
GATES auth . plan/scenario gate . daily chat cap (free)
daily token cap 500k [fail CLOSED on read error]
|
v
PROMPT ~52KB builder: persona / tone / mode (7 modes)
ASSEMBLY + context, tiered full | summary | minimal | off
+ DUE WORDS (PocketBase filter query, not vectors)
|
v
PROVIDER lib/llm.ts, one env var flips the backend:
SEAM openrouter (OpenAI format) -> Gemini flash-lite
@google/genai -> Gemini (rollback)
max_tokens = 1500 + 2048 thinking headroom
idle-timeout abort, re-armed on every chunk
|
v token-by-token stream
PARSE assume the model lies:
<correction>{json}</correction> stale / punctuation guards
<recalled>WORD</recalled> recall signal
drop truncated + hallucinated <filler> tags
| |
clean prose recall signal
v v
OUTPUT stripForTts -> STATE SRS credit (positive-only,
Azure -> Gemini -> browser TTS still-due words only)
SHA-256 disk cache, usage metered and awaited
atomic tmp + rename BEFORE the stream closesThe import path is four steps. The AI touches only the second one:
1. EXPORT 2. IMPORT 3. ACTIVATE 4. CREDIT
your Anki / WaniKani one AI pass pulls due words woven into say it unaided
JPDB / Migaku deck -> words + context -> real spoken chat -> -> SRS advances
(you skim and fix) (you speak, not tap) (Living Recall)The schedule itself is a plain interval ladder. Nail a word and it fades toward once a year. Miss it and it comes back tomorrow.
Days until a word comes back Good = +1 rung Easy = +2 Again = back to day 1
240 | __________
120 | ________|
60 | ______|
30 | ______|
14 | _____|
7 | _____|
3 | _____|
1 |_____|
+-----+-----+-----+-----+-----+-----+-----+-----+
1st 2nd 3rd 4th 5th 6th 7th 8th clean productionThe schedule is the only progress bar, and the learner never sees it. The app rewards you by getting quieter as you succeed.
Decisions & trade-offs
The interesting part is not the feature list. It is the calls made under real constraints, and what they cost.
01Credit production, not recognition (Living Recall)
- Decision
- Make the spaced-repetition credit event unprompted production in a live conversation, not a self-graded 'did you remember this' card flip. A word only advances when you actually say it, unaided.
- Trade-off
- Recognition is trivial to detect; production is not. The signal comes from an unreliable model, so the loop is deliberately asymmetric: it only ever credits (never penalizes), only credits words still due, and treats the model's recall tag as a hint to verify, not a verdict.
- Outcome
- The schedule finally measures the thing learners care about, which is 'can I speak this word.' The worst failure mode is a real recall going uncredited, never a corrupted schedule.
- If I did it again
- The credit is a flat 'good.' I would weight it by how long the word had been dormant, and add a per-turn idempotency key so a re-delivered stream event can never double-count.
02The output-half wedge: complement, do not compete
- Decision
- Position Kaiwaflow as the output half of an existing stack and tell users to keep Anki and their tutor, rather than build another all-in-one Japanese app.
- Trade-off
- A narrow tool is a harder pitch than a full course, and it depends on the very incumbents it refuses to replace. But it owns the one job none of them do: turning passive vocabulary into speech.
- Outcome
- The whole product, especially the import pipeline, plugs into the existing stack instead of fighting it. That is a cleaner wedge than competing with Duolingo on breadth.
- If I did it again
- The risk is discovery: a complement lives or dies on the incumbent's export. I would invest earlier in frictionless Anki and WaniKani import and in proving the wedge with real learners before widening scope.
03Anti-gamification as a segment bet
- Decision
- Cut the entire gamification playbook: no streaks, no XP, no levels, no guilt loops.
- Trade-off
- Gamification exists because it drives daily active use, so I am giving up a proven retention lever on a bet that my segment, adults who froze despite years of study, is repelled by it.
- Outcome
- The SRS schedule is the only progress system, and the learner never sees it. Intrinsic 'keep your words warm' replaces extrinsic streak anxiety.
- If I did it again
- This is a bet I have to prove, not a truth. If activation stalls, the test is whether a calm 'your words are staying warm' receipt beats a streak without quietly becoming one.
04Fail-closed cost caps for a solo-run LLM product
- Decision
- Put a hard daily token cap on every account, free and paid, as an abuse breaker on the LLM bill, and reason about which direction each cap should fail.
- Trade-off
- A database read hiccup now denies chat instead of letting spend run unbounded. I accept rare false denials so the spend guard can never be switched off by the same outage that makes abuse most likely.
- Outcome
- The bill has a per-user daily ceiling that survives a partial database failure. The cap fails closed on a read error but open on a genuine 'no usage yet today,' so normal traffic stays fast and open.
- If I did it again
- At scale I would move the counter to a database-atomic increment to close a lost-update window, and warn a legitimate power user before the wall instead of at it.
Architecture at a glance
One box. A single Next.js app is the backend-for-frontend; PocketBase (an embedded Go binary over SQLite) is the database and auth; Caddy terminates TLS and splits traffic. Deployed by Coolify onto one VPS. It is a vertical-scaling design, chosen on purpose for a private beta, and the honest scale answer is Postgres plus a stateless app tier.
BROWSER (React 19) furigana + TTS playback + browser STT
|
| HTTPS, pb_auth cookie on every request
v
+-----------------------------------------------+
| CADDY :443 (TLS, reverse proxy) |
| /pb/* -> PocketBase /* -> Next.js |
+---------------+-------------------+-----------+
/pb/* | | /*
v v
+---------------------+ +------------------------------+
| POCKETBASE :8090 | | NEXT.JS 16 BFF :3000 |
| Go + SQLite (0.25) | | middleware: local JWT check |
| auth (pw/OTP/magic)|<--| api routes: requireUser + |
| 46 migrations | | plan gates + cost caps |
| hooks: trial, beta |-->| /api/chat -> SSE stream |
| gate, backups | | /api/tts -> cached audio |
+---------------------+ +----+--------+--------+-------+
| | |
v v v
Gemini / Azure / Stripe
OpenRouter Gemini billing
(LLM) TTS
[ built, shipped dark: OpenAI Whisper STT, Pro-gated. Live STT is the browser. ]
Deploy: Coolify builds two images (app, pocketbase+migrations); Caddy fronts them.The part I am proudest of is not the topology, it is the money. Every LLM call is metered, and the abuse breaker that reads that meter fails safe.
POST /api/chat
|
v requireUser -> effectivePlan
daily chat cap (free: 30/day) --------------> over? 429
|
v checkDailyTokenCap (all plans, 500k/day)
read llm_usage_daily:
no row yet -> used = 0 (fail OPEN, correct: really zero today)
read error -> used = cap (fail CLOSED -> 429)
|
v under cap
stream Gemini -> SSE tokens -> client
|
v (stream ends)
await meter usage (retry-safe upsert, cost priced at write time)
|
v
close stream <- metering is flushed BEFORE close, so tomorrow's cap
can never be undercounted by a dropped writeEngineering for trust
Most of the hard engineering here is governing an unreliable collaborator: the model. I treat every tag it emits as hostile input. Recall credit is positive-only and gated on words still due, with an anti-mimicry guard so parroting a word Sensei just said does not count. The prompt fences user-supplied persona and topics as data with a non-negotiable trailing instruction, and structured extraction runs with provider fallbacks turned off so attacker-influenced content cannot be rerouted to a model that formats differently.
I also do not trust the model’s Japanese readings. A deterministic morphological analyzer (kuroshiro and kuromoji) runs in the browser to verify and repair the furigana it emits, fixing truncations like 育[はぐ]む into 育[はぐく]む. And the whole model backend sits behind a one-module provider seam, so a vendor outage or price change is a one env-var rollback, not a code change.
The method under the hood
The product is a deliberate implementation of second-language-acquisition research, turned into mechanics. Comprehensible input becomes furigana that fades per kanji as your level rises. The output hypothesis becomes the recall loop, which forces production rather than recognition. Corrective feedback becomes a modeled correction (a recast) shown as a collapsible chip, never a red pen mid-sentence. Spaced retrieval becomes the interval ladder. The theory is the spec.
The business
Free is a real taste: unlimited free chat, browser voice, every Sensei dial, and up to 100 saved words at 30 chats a day. Pro is a flat $19 a month or $180 a year, with a 15-day trial that needs no card. In the code, the abuse breakers (token and character caps that protect the bill) are kept separate from the monetization levers (the 100-word line that sells Pro), because blurring the two is how you either bankrupt yourself or annoy a paying customer.
Distribution is an invite-gated private beta with an off-app waitlist. That is a deliberate growth choice, not an absence of one: I am metering demand and cost while I prove the loop. The activation funnel is already instrumented, from first seed to a word recalled in conversation, so what I watch is whether the recall loop closes. For a product this early, that is the honest version of a traction chart.
An honest note on how it was built
Kaiwaflow is built solo with heavy AI assistance. What I own, and what this page is about, is the thesis, the design of the recall loop, the trade-off calls, the sequencing, and governing the AI itself: the guardrails, the fail-safe caps, and the defensive parsing that keep an unreliable model from corrupting the product. Those are the parts a code generator does not decide for you.