Repository navigation
Plan integration follow-on (D-E): age-aware nudge and a learner-issued retire/snooze for an overdue item #26
Description
Origin
- Owner decision D-E (
docs/architecture/plan-integration/HANDOFF-2026-09-16.md§2): "an overdue item unrelated to the plan must not sit as an alternate indefinitely. Fact: due score already grows+1/day(cap +30), so it overtakes the +12 bias in ~2 weeks; missing are an age-aware nudge line and a retire/snooze action for a due card (only backlog topics can beresolvedtoday)." - Evidence: rubric row 2 (
receipts/now-rubric-2026-09-16.md), owner verdict yes — clear the overdue review first — with the note that an unrelated overdue item must be proposed explicitly or be retirable by the learner. - Full proposal, written from the code on
maina5b9f903:proposals/2026-09-16-overdue-nudge-and-retire.md. Follows on from Integrate Study Plans across Now, Web architect, and MCP #7 (plan integration); not a blocker of Integrate Study Plans across Now, Web architect, and MCP #7.
What exists today (read from the code)
- A due item is a
study_progressrow whoselast_seenreaches an interval inREVIEW_INTERVALS(history/progress.py: 1, 3, 7, 14, 30 days); astrugglingrow is always due. There is nonext_reviewcolumn and no "not due" state — a row stays due, and stays anowcandidate, until it is studied again (record_progress) or deleted. learning/decision.pyscores a due candidate100 + min(days_ago, 30)(+35 struggling, +15 learning); a plan-related candidate addsPLAN_RELATED_BIAS = 12. An unrelated item overtakes a plan-related one on age alone after ~2 weeks — silently: nothing tells the learner why it moved up, and nothing offers "I have moved on from this".resolvedexists only for parked backlog topics (record_topic_progress(confidence="resolved")→parking.resolve_parked_topic), not forstudy_progressrows. The Today card and CLInowhave no control on a due item.
What to build
Two additions on the existing candidate — no new ranking rule.
-
Age-aware nudge line. When an eligible due candidate is unrelated to every matchable plan and
days_ago ≥ threshold, with the threshold derived from the constants (the smallestdays_agoat whichmin(days_ago, 30) ≥ PLAN_RELATED_BIASplus the plan-related candidate's own age — no second constant, so it moves with D-D), the candidate carries anudgeline: "Overdue 16 days and unrelated to your plan — do it, or retire it:studyloop review retire <topic> <concept>." Rendered where item 4's completion-review evidence lines render (CLInowbeneath the action, Today card beside it, MCP payload as an additive key). A fact about age, not a proposal to rank differently. -
Retire / snooze door for a due item, through the seam every surface uses:
| Door | Meaning | Mechanism | Undo |
|---|---|---|---|
| retire | "done with this concept for now" | retired_at timestamp on the study_progress row (or a confidence value the due predicate excludes — decided at RED with the migration); no longer due; history kept; gone from every consumer of spaced_repetition_due (now CLI/Today/MCP get_next_action, recap, studyloop review, the plan evaluation's due count) |
record_progress on the concept clears it |
| snooze | "not this week" | snoozed_until date; not due before it; normal intervals after |
expiry, or explicit un-snooze |
Exposed like the plan lifecycle: a CLI verb (studyloop review retire|snooze), a Today-card control on the due item, one MCP tool beside record_study_progress (the mirror of record_topic_progress(confidence="resolved") for cards).
Constraints:
- Learner-issued only — nothing retires or snoozes on the learner's behalf (the nudge proposes, the learner decides; same shape as item 4's consensual close).
- History kept — excluded from the due set, never deleted;
get_study_historyandunverified_milestonesstill see it. - Plan-aware, not plan-bound — the nudge fires only for items unrelated to every matchable plan; a plan-related overdue item is the plan's business (item 4's closing review counts it).
- Golden unchanged — the no-plan golden has no due items; the nudge is an additive key on a due candidate.
- Threshold derived, not a new constant.
Out of scope
REVIEW_INTERVALSand howdays_agois computed.- Reranking — the overtaking-on-age behaviour is correct (row 2); what is missing is the explanation and the exit.
- Flashcard/quiz
card_reviews(a different store with its own SM-2 scheduler inreview_db.py); a retire door for those is a separate ticket if wanted.
Acceptance criteria
- RED: a due unrelated item past the derived threshold carries the nudge line on CLI, Today and MCP; a plan-related one does not; one below the threshold does not.
- RED:
retireremoves the row from the due set and fromnowcandidates, keeps its history row, andrecord_progresson the same concept clears it;snoozeexcludes it until the date and not after. - The migration lives in
agent_session_tools.migrationsand the clean-rebuild closure classifies the new column/state (the 2026-09-12 rebuild caught two tables the migrations did not create; do not repeat that). - Golden
now_plan_no_active.jsonbyte-identical;test_docs_plan_integration_contract.pygreen with the doc sentence added todocs/study-plans.md"Plan-aware now" anddocs/cli-reference.md. - A D-16 rubric row: row 2's world advanced 16 days; the owner asked whether the nudge line is one they would act on.
Blocked by
- Nothing. Independent of D-D (the threshold derivation is written so it follows D-D's derived bias when that lands).
Definition of Done
- Full unit suite green;
just lint;just typecheck. - Council review of the RED and the arbitration before merge (one commit per accepted finding).
- Docs, CLI reference and MCP/CLI specs updated in the same change.
Activity