Review intervals are set by FSRS (Free Spaced Repetition Scheduler). Cards you know well come back later; shaky ones come back sooner.
No state, replayed from the log
data/reviews/*.jsonl is the only truth. A card’s schedule is derived by replaying that card’s log every time.
Keeping state separately creates a second truth and they always drift apart. In the system this replaced, 23 days of study had left only 5 commits — 18 days of history had vanished.
Two consequences follow from the design.
- No fuzz on intervals. Randomness would change the result on every replay
- New cards are treated as “can be shown right now”
Each log line carries at least card_id, review_time (Unix ms) and review_rating (1–4). One file per day, append-only.
Do not hand-edit, sort or delete anything under
data/. Log lines for deleted cards stay (they are the only copy of the history).
The defaults
| Value | |
|---|---|
| Algorithm | FSRS-6 (21 parameters) |
| Desired retention | 0.9 |
| Short-term steps | 1 min / 10 min (10 min for relearning) |
| New cards per day | 20 (Anchor Pro lifts the cap) |
On the defaults, all three platforms produce the same intervals from the same log. A brand-new card reads “Again 1 min / Hard 6 min / Good 10 min / Easy 8 days”.
Changing the parameters (data/fsrs.json)
There is no input field in settings. Having said that the repository is the source of truth, a per-device setting would be a second truth. Put data/fsrs.json in the repository and it is read on every sync.
{
"w": [ 21 numbers ],
"desired_retention": 0.9,
"learning_steps": ["1m", "25m"],
"relearning_steps": ["10m"],
"maximum_interval": 36500
}
- Keys are snake_case, so output from the usual optimisers or Anki pastes in directly (camelCase is also accepted)
- Every field is optional, and a broken file never stops the app. It falls back to the defaults and says in settings what it declined and why
wis accepted at lengths 17 / 19 / 21 only. Anything else falls back, with a note in settings- Each sync diffs it; if it disappears remotely, it is removed from the device too
Specifying a 19-element
wdrops iOS to FSRS-5, where it stops readinglearning_steps(short-term becomes a fixed 1/5/10 minutes). Mac and Android honour the steps even at 19. Settings surfaces the discrepancy. Write nowat all and all three run FSRS-6.
Settings showing “FSRS-5” is therefore correct reporting that you passed a 19-element w.
Optimising from your own log (Mac)
Settings → “Optimise from your log” on the Mac build learns parameters from your own review history.
- It shows you how the intervals would change before you accept (changing the parameters recomputes every card’s due date at once)
- Accepting rewrites
data/fsrs.jsonand sends it on the next sync. The same settings then land on your iPhone - Only reviews whose previous grade was on a different day are used as input. Same-day repeats have zero elapsed time and carry no information about forgetting
- Roughly 400 reviews to try it, 1,000 to be stable. You can run it below that and throw the result away
- Optimising works in the packaged app only (about once a month is plenty)
iOS and Android only read the file.