diff options
Diffstat (limited to 'docs/design/fsrs-spec.org')
| -rw-r--r-- | docs/design/fsrs-spec.org | 209 |
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 |
