aboutsummaryrefslogtreecommitdiff
path: root/docs/design/fsrs-spec.org
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design/fsrs-spec.org')
-rw-r--r--docs/design/fsrs-spec.org209
1 files changed, 160 insertions, 49 deletions
diff --git a/docs/design/fsrs-spec.org b/docs/design/fsrs-spec.org
index 76ec4db..0b0d9eb 100644
--- a/docs/design/fsrs-spec.org
+++ b/docs/design/fsrs-spec.org
@@ -78,20 +78,40 @@ Locked product decisions for v1. These resolve the design questions, but
implementation is still blocked on the research prerequisites in =Status=
above (source pin, equation cross-check, reference vectors).
-| # | Decision | Value |
-|---+----------+-------|
-| 1 | FSRS version pin | FSRS-4.5 (17 parameters), with the v4.5 default tuple from the fsrs4anki algorithm wiki |
-| 2 | Default parameters | See =Version pin= below — the exact v4.5 tuple |
-| 3 | Forgetting curve | v4.5: =DECAY = -0.5=, =FACTOR = 19/81= |
-| 4 | Quality mapping | Again iff quality ≤ =org-drill-failure-quality=; otherwise Hard (≤3), Good (4), Easy (5). At the default threshold of 2 this is 0/1/2 → Again, 3 → Hard, 4 → Good, 5 → Easy |
-| 5 | State shape | =cl-defstruct org-drill-fsrs-state= |
-| 6 | Result shape | =cl-defstruct org-drill-fsrs-result= |
-| 7 | DRILL_CARD_WEIGHT applies to FSRS | Yes — same delta interpolation as SM/Simple8: =next = max(1.0, last + (computed − last) / weight)= on success intervals; weight 2 = more frequent review. The =Again= same-day path (interval 0) bypasses weight |
-| 8 | Failure semantics | =Again= schedules same-day (=next-interval = 0=); D/S/R state still updates |
-| 9 | First-FSRS-review cold-start message | No message (silent transition) |
-| 10 | Malformed =DRILL_FSRS_*= behavior | User-facing error; scheduling untouched (data-loss safety) |
-| 11 | =org-drill-fsrs-desired-retention= as file-local | Yes — safe-local, bounded 0 < x < 1 |
-| 12 | DRILL_FSRS_* property ownership | Add to =org-drill-scheduling-properties=; undo/strip/copy/migration cover them via the existing list |
+| # | Decision | Value |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 1 | FSRS version pin | FSRS-4.5 (17 parameters), with the v4.5 default tuple from |
+| | | the fsrs4anki algorithm wiki |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 2 | Default parameters | See =Version pin= below — the exact v4.5 tuple |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 3 | Forgetting curve | v4.5: =DECAY = -0.5=, =FACTOR = 19/81= |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 4 | Quality mapping | Again iff quality ≤ =org-drill-failure-quality=; otherwise |
+| | | Hard (≤3), Good (4), Easy (5). At the default threshold of 2 |
+| | | this is 0/1/2 → Again, 3 → Hard, 4 → Good, 5 → Easy |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 5 | State shape | =cl-defstruct org-drill-fsrs-state= |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 6 | Result shape | =cl-defstruct org-drill-fsrs-result= |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 7 | DRILL_CARD_WEIGHT applies to FSRS | Yes — same delta interpolation as SM/Simple8: =next = |
+| | | max(1.0, last + (computed − last) / weight)= on success |
+| | | intervals; weight 2 = more frequent review. The =Again= |
+| | | same-day path (interval 0) bypasses weight |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 8 | Failure semantics | =Again= schedules same-day (=next-interval = 0=); D/S/R |
+| | | state still updates |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 9 | First-FSRS-review cold-start message | No message (silent transition) |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 10 | Malformed =DRILL_FSRS_*= behavior | User-facing error; scheduling untouched (data-loss safety) |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 11 | =org-drill-fsrs-desired-retention= as file-local | Yes — safe-local, bounded 0 < x < 1 |
+|----+--------------------------------------------------+--------------------------------------------------------------|
+| 12 | DRILL_FSRS_* property ownership | Add to =org-drill-scheduling-properties=; |
+| | | undo/strip/copy/migration cover them via the existing list |
+|----+--------------------------------------------------+--------------------------------------------------------------|
* Open questions
@@ -145,10 +165,14 @@ The four-rating scale FSRS uses internally:
| Rating | Name | Meaning |
|--------+-------+------------------------|
-| 1 | Again | failed recall |
-| 2 | Hard | recalled with struggle |
-| 3 | Good | recalled normally |
-| 4 | Easy | trivial recall |
+| 1 | Again | failed recall |
+|--------+-------+------------------------|
+| 2 | Hard | recalled with struggle |
+|--------+-------+------------------------|
+| 3 | Good | recalled normally |
+|--------+-------+------------------------|
+| 4 | Easy | trivial recall |
+|--------+-------+------------------------|
org-drill uses a 0–5 quality scale. The mapping (locked, see Agreed
decisions #4) honors =org-drill-failure-quality= for the Again boundary,
@@ -167,9 +191,13 @@ At the default threshold of 2 this is the familiar table:
| org-drill quality | FSRS rating |
|-------------------+-------------|
| 0, 1, 2 | Again (1) |
+|-------------------+-------------|
| 3 | Hard (2) |
+|-------------------+-------------|
| 4 | Good (3) |
+|-------------------+-------------|
| 5 | Easy (4) |
+|-------------------+-------------|
A custom =org-drill-failure-quality= shifts the Again boundary the same way
it does for the SM-family schedulers: a threshold of 3 sends quality 3 to
@@ -183,13 +211,18 @@ could let users remap the success grades; v1 keeps them fixed.
Per-card persisted state lives in four properties plus the existing
=DRILL_LAST_REVIEWED=:
-| Property | Type | Meaning |
-|-------------------------+--------+----------------------------------------|
-| =DRILL_FSRS_STABILITY= | float | =S=, the memory-stability estimate |
-| =DRILL_FSRS_DIFFICULTY= | float | =D=, the difficulty estimate (1..10) |
-| =DRILL_FSRS_REVIEWS= | int | review count under FSRS (≥ 0) |
-| =DRILL_FSRS_LAPSES= | int | failures (rating = Again) under FSRS |
-| =DRILL_LAST_REVIEWED= | time | reused from the existing SM property |
+| Property | Type | Meaning |
+|-------------------------+-------+--------------------------------------|
+| =DRILL_FSRS_STABILITY= | float | =S=, the memory-stability estimate |
+|-------------------------+-------+--------------------------------------|
+| =DRILL_FSRS_DIFFICULTY= | float | =D=, the difficulty estimate (1..10) |
+|-------------------------+-------+--------------------------------------|
+| =DRILL_FSRS_REVIEWS= | int | review count under FSRS (≥ 0) |
+|-------------------------+-------+--------------------------------------|
+| =DRILL_FSRS_LAPSES= | int | failures (rating = Again) under FSRS |
+|-------------------------+-------+--------------------------------------|
+| =DRILL_LAST_REVIEWED= | time | reused from the existing SM property |
+|-------------------------+-------+--------------------------------------|
=DRILL_LAST_REVIEWED= is written by the shared reschedule flow
(=org-drill.el=:1930, unconditional after the algorithm dispatch), not by
@@ -291,14 +324,22 @@ participate in every workflow that already handles SM scheduling
properties. V1 adds them to =org-drill-scheduling-properties= so the
existing helpers cover them without per-call plumbing:
-| Workflow | Helper / call site | Behavior |
-|----------+---------------------+----------|
-| Rating snapshot for undo | =org-drill--snapshot-entry-data= | Captures =DRILL_FSRS_*= alongside SM properties |
-| Undo last rating | =org-drill-undo-last-rating= → =org-drill--restore-entry-data= | Restores =DRILL_FSRS_*= verbatim |
-| Strip single entry | =org-drill-strip-entry-data= | Removes =DRILL_FSRS_*= |
-| Strip all entries | =org-drill-strip-all-data= | Removes =DRILL_FSRS_*= across the scope |
-| Copy / share to marker | =org-drill--copy-scheduling-to-marker= | Migrates =DRILL_FSRS_*= |
-| Org property completion | =org-drill-scheduling-properties= consumers | =DRILL_FSRS_*= appear in completion |
+| Workflow | Helper / call site | Behavior |
+|--------------------------+---------------------------------------------+---------------------------------------------|
+| Rating snapshot for undo | =org-drill--snapshot-entry-data= | Captures =DRILL_FSRS_*= alongside SM |
+| | | properties |
+|--------------------------+---------------------------------------------+---------------------------------------------|
+| Undo last rating | =org-drill-undo-last-rating= → | Restores =DRILL_FSRS_*= verbatim |
+| | =org-drill--restore-entry-data= | |
+|--------------------------+---------------------------------------------+---------------------------------------------|
+| Strip single entry | =org-drill-strip-entry-data= | Removes =DRILL_FSRS_*= |
+|--------------------------+---------------------------------------------+---------------------------------------------|
+| Strip all entries | =org-drill-strip-all-data= | Removes =DRILL_FSRS_*= across the scope |
+|--------------------------+---------------------------------------------+---------------------------------------------|
+| Copy / share to marker | =org-drill--copy-scheduling-to-marker= | Migrates =DRILL_FSRS_*= |
+|--------------------------+---------------------------------------------+---------------------------------------------|
+| Org property completion | =org-drill-scheduling-properties= consumers | =DRILL_FSRS_*= appear in completion |
+|--------------------------+---------------------------------------------+---------------------------------------------|
*Implementation.* Add the four FSRS property names to the
=org-drill-scheduling-properties= defvar list. No new branching;
@@ -414,15 +455,22 @@ Two new helpers parallel the existing item-data round-trip:
The implementation separates pure functions from property IO so each
layer is independently testable:
-| Layer | Functions | Tested in |
-|-------+-----------+-----------|
-| Pure quality mapping | =org-drill--fsrs-rating-from-quality= | mapping tests |
-| Pure retrievability | =org-drill--fsrs-retrievability= | reference-vector tests |
-| Pure initial state | =org-drill--fsrs-initial-state= | reference-vector tests |
-| Pure update step | =org-drill--fsrs-update= | reference-vector tests |
-| Pure interval calc | =org-drill--fsrs-interval= | reference-vector tests |
-| Property IO | =org-drill-get-fsrs-state=, =org-drill-store-fsrs-result= | round-trip tests |
-| Orchestration | =org-drill-determine-next-interval-fsrs= | integration tests |
+| Layer | Functions | Tested in |
+|----------------------+-----------------------------------------------------------+------------------------|
+| Pure quality mapping | =org-drill--fsrs-rating-from-quality= | mapping tests |
+|----------------------+-----------------------------------------------------------+------------------------|
+| Pure retrievability | =org-drill--fsrs-retrievability= | reference-vector tests |
+|----------------------+-----------------------------------------------------------+------------------------|
+| Pure initial state | =org-drill--fsrs-initial-state= | reference-vector tests |
+|----------------------+-----------------------------------------------------------+------------------------|
+| Pure update step | =org-drill--fsrs-update= | reference-vector tests |
+|----------------------+-----------------------------------------------------------+------------------------|
+| Pure interval calc | =org-drill--fsrs-interval= | reference-vector tests |
+|----------------------+-----------------------------------------------------------+------------------------|
+| Property IO | =org-drill-get-fsrs-state=, =org-drill-store-fsrs-result= | round-trip tests |
+|----------------------+-----------------------------------------------------------+------------------------|
+| Orchestration | =org-drill-determine-next-interval-fsrs= | integration tests |
+|----------------------+-----------------------------------------------------------+------------------------|
The =cl-case= branch in =org-drill-smart-reschedule= is the
orchestration layer. Everything below the branch is pure or
@@ -648,13 +696,76 @@ updated to "not implementation-ready as of Review 2" before this response.
* Review and iteration history
-| Iteration | Date | Contributor | Role | What changed or was recommended | Why |
-|------------+------------+---------------+-----------+----------------------------------+-----|
-| Draft v0 | 2026-05-27 | Craig Jennings | Author | Initial draft with 6 DECIDE markers and recommendations baked in for each. Same shape as the stats-dashboard spec. | Establish a written design before any FSRS code lands. |
-| Review 1 | 2026-05-28 | External reviewer | Reviewer | Four blocking findings (version/tuple/equation mismatch, scheduler/state result shape, property ownership across strip/undo/copy/migration, learning/relearning scope) plus two medium, plus UX/Architecture/Robustness observations and full test-strategy expansion. | An external read of the spec against the existing codebase caught the v4-vs-v4.5 drift and several missing secondary-workflow paths. |
-| Response 1 | 2026-05-28 | Claude Code | Responder | All four blocking findings resolved; medium-priority items accepted; new sections added (Agreed decisions, Scheduling property ownership, Validation and malformed-state behavior); test strategy expanded to four files; v4 → v4.5 tuple and equations corrected; struct shapes locked; DRILL_CARD_WEIGHT confirmed to apply (Craig's call). | Convert the spec from "not implementation-ready" to "implementation-ready" via the spec-response workflow. Removes invented-product-behavior risk for the implementer. |
-| Review 2 | 2026-05-28 | Codex | Reviewer | Marked the spec as needs-research before implementation; identified unresolved =py-fsrs= source pin/formula/vector prerequisites, =DRILL_CARD_WEIGHT= semantics reversed against current org-drill, and a quality-mapping conflict with =org-drill-failure-quality=. | Applying the updated spec-review workflow to the moved =docs/design/= spec caught contradictions introduced while preserving Review 1 decisions. |
-| Response 2 | 2026-05-31 | Claude Code | Responder | Corrected =DRILL_CARD_WEIGHT= to the real SM/Simple8 delta-interpolation (Craig's call to match existing semantics); resolved the quality mapping to honor =org-drill-failure-quality= for the Again boundary with a fixed success sub-mapping (Craig's call); reframed status/open-questions to "Needs research" with the three blocking prerequisites stated honestly; fixed =DRILL_LAST_REVIEWED= ownership (M1) and the algorithm-symbol coverage (M2); flagged the update equations as paraphrased-and-unverified. | Fix the three blockers and two mediums from Review 2. The B2 reversal traced to a wrong verbal description of SM weight handling in Response 1; this response grounds every claim in the cited =org-drill.el= line. |
+| Iteration | Date | Contributor | Role | What changed or was | Why |
+| | | | | recommended | |
+|------------+------------+-------------------+-----------+------------------------------+-----------------------------|
+| Draft v0 | 2026-05-27 | Craig Jennings | Author | Initial draft with 6 DECIDE | Establish a written design |
+| | | | | markers and recommendations | before any FSRS code lands. |
+| | | | | baked in for each. Same | |
+| | | | | shape as the stats-dashboard | |
+| | | | | spec. | |
+|------------+------------+-------------------+-----------+------------------------------+-----------------------------|
+| Review 1 | 2026-05-28 | External reviewer | Reviewer | Four blocking findings | An external read of the |
+| | | | | (version/tuple/equation | spec against the existing |
+| | | | | mismatch, scheduler/state | codebase caught the |
+| | | | | result shape, property | v4-vs-v4.5 drift and |
+| | | | | ownership across | several missing |
+| | | | | strip/undo/copy/migration, | secondary-workflow paths. |
+| | | | | learning/relearning scope) | |
+| | | | | plus two medium, plus | |
+| | | | | UX/Architecture/Robustness | |
+| | | | | observations and full | |
+| | | | | test-strategy expansion. | |
+|------------+------------+-------------------+-----------+------------------------------+-----------------------------|
+| Response 1 | 2026-05-28 | Claude Code | Responder | All four blocking findings | Convert the spec from "not |
+| | | | | resolved; medium-priority | implementation-ready" to |
+| | | | | items accepted; new sections | "implementation-ready" via |
+| | | | | added (Agreed decisions, | the spec-response workflow. |
+| | | | | Scheduling property | Removes |
+| | | | | ownership, Validation and | invented-product-behavior |
+| | | | | malformed-state behavior); | risk for the implementer. |
+| | | | | test strategy expanded to | |
+| | | | | four files; v4 → v4.5 tuple | |
+| | | | | and equations corrected; | |
+| | | | | struct shapes locked; | |
+| | | | | DRILL_CARD_WEIGHT confirmed | |
+| | | | | to apply (Craig's call). | |
+|------------+------------+-------------------+-----------+------------------------------+-----------------------------|
+| Review 2 | 2026-05-28 | Codex | Reviewer | Marked the spec as | Applying the updated |
+| | | | | needs-research before | spec-review workflow to the |
+| | | | | implementation; identified | moved =docs/design/= spec |
+| | | | | unresolved =py-fsrs= source | caught contradictions |
+| | | | | pin/formula/vector | introduced while preserving |
+| | | | | prerequisites, | Review 1 decisions. |
+| | | | | =DRILL_CARD_WEIGHT= | |
+| | | | | semantics reversed against | |
+| | | | | current org-drill, and a | |
+| | | | | quality-mapping conflict | |
+| | | | | with | |
+| | | | | =org-drill-failure-quality=. | |
+|------------+------------+-------------------+-----------+------------------------------+-----------------------------|
+| Response 2 | 2026-05-31 | Claude Code | Responder | Corrected | Fix the three blockers and |
+| | | | | =DRILL_CARD_WEIGHT= to the | two mediums from Review 2. |
+| | | | | real SM/Simple8 | The B2 reversal traced to a |
+| | | | | delta-interpolation (Craig's | wrong verbal description of |
+| | | | | call to match existing | SM weight handling in |
+| | | | | semantics); resolved the | Response 1; this response |
+| | | | | quality mapping to honor | grounds every claim in the |
+| | | | | =org-drill-failure-quality= | cited =org-drill.el= line. |
+| | | | | for the Again boundary with | |
+| | | | | a fixed success sub-mapping | |
+| | | | | (Craig's call); reframed | |
+| | | | | status/open-questions to | |
+| | | | | "Needs research" with the | |
+| | | | | three blocking prerequisites | |
+| | | | | stated honestly; fixed | |
+| | | | | =DRILL_LAST_REVIEWED= | |
+| | | | | ownership (M1) and the | |
+| | | | | algorithm-symbol coverage | |
+| | | | | (M2); flagged the update | |
+| | | | | equations as | |
+| | | | | paraphrased-and-unverified. | |
+|------------+------------+-------------------+-----------+------------------------------+-----------------------------|
* References