Four shared packages decide what a learner sees and when: learning (memory and mastery), planner (the feed), decisions (logging every choice) and experiments (variants). The client and the server import the same code, so they always agree.
flowchart TB
LOGS[("keeps · attempts · review_logs")] --> REPLAY[replay cards<br/>FSRS]
REPLAY --> CARDS[card state<br/>stability · difficulty · due]
CARDS --> REVIEW[Review queue]
CARDS --> MASTERY[topic mastery]
MASTERY --> PLANNER
INTERESTS[interests<br/>stated + behaviour] --> PLANNER
CATALOG[catalog + build-on graph] --> PLANNER
EXP[experiments<br/>deterministic variant] --> PLANNER
PLANNER[planner<br/>policy: rules@3] --> FEED[Feed]
PLANNER --> DL[(decision log<br/>candidates · scores · propensity · reasons)]
sequenceDiagram
participant L as Logs
participant F as FSRS
participant C as Card
L->>F: keep (t0) creates the card
loop each review in time order (then id)
L->>F: rating, reviewedAt
F->>C: stability, difficulty, next due
end
Note over C: same logs → same card on every device
| Feature | How it works |
|---|---|
| Due forecast | Cards are replayed and bucketed by due day, so Review shows how many come back each day for the next week |
| Daily limit | A cap on new reviews per day; overflow rolls to the next day in due order |
| Memory target | The learner chooses how much they want to remember; a higher target means more frequent reviews |
| Honest recall rate | Shown from actual first-try results, not from the target, so learners see how they are really doing |
Topic mastery combines kept and reviewed boxes, Try results weighted by confidence, and how deep a learner went into each box. It drives the topic maps, the "ready for this?" warnings when someone skips ahead of a box's prerequisites, and the planner.
The planner turns the catalog into a ranked feed at a named decision point (feed.rank).
feed.rank/rules@3 records the candidates, scores, chosen items, a propensity for each and a plain explanation ("Because you like Graphs"), plus the learner's experiment assignments.decision Log propensities from day one. Recording how likely each shown item was to be chosen is what makes offline evaluation of a new policy possible.
warning Today's rules are deterministic, so every propensity is 1. Off-policy evaluation needs some randomness in what is shown; until a little exploration is added, the logs can't score a different policy. See Roadmap.
exposures records which feed items were actually on screen, with their position and visible time.The learner-state job (hourly, incremental) recomputes card summaries and mastery only for learners whose learning mutations arrived since the last run (with an overlap so none is missed), plus anyone marked in learner_dirty (for example after an account merge). Reminders, the retention job and research exports read this table instead of replaying logs on every request.
${sch.nav()}