Skip to content

fix(memory): stop background scoring from resetting core_memory.updated_at - #184

Merged
Cipher208 merged 1 commit into
masterfrom
fix/rescore-must-not-reset-age
Nov 3, 2026
Merged

Cipher208 merged 1 commit into
masterfrom
fix/rescore-must-not-reset-age

Conversation

@Cipher208

Copy link
Copy Markdown
Owner

Что

core_memory.updated_at — это возраст строки: по нему lifecycle/forgetting.py считает дни для затухания и сравнивает с cutoff архива. Два фоновых пути штамповали его now при каждом изменении оценки, поэтому пересчитанная или часто вспоминаемая строка выглядела вечно свежей: не старела, не затухала, не архивировалась. А фильтр кандидатов свипа (updated_at > now - only_recent_days) снова и снова возвращал её на пересчёт.

Измерено на живых базах до фикса: в Hermes 94 строки старше 60 дней по created_at читались как моложе 30 по updated_at (всего 634 из 973 строк имели updated_at свежее суток).

база строк updated_at свежее суток старше 60 д по created_at, но моложе 30 по updated_at
Hermes 973 634 94
Mimocode 766 330 1
CowAgent 1 000 387 0

Фикс

Обе фоновые ветки обновляют только importance:

  • lifecycle/importance_scheduler.py::_rescore_user — периодический свип;
  • features/replay.py::cls_replay — ночной хук (hooks/user_hooks.py:90-91), буст за recall_useful.

Время пересчёта уже пишется в importance_audit.rescored_at — там ему и место.

features/quality.py::record_feedback сознательно оставлен пишущим updated_at: он достижим только из интерактивного инструмента memory_quality, не из таймера, и явное действие действительно означает «строку только что тронули».

Полный перебор всех писателей core_memory.updated_at в репозитории: фоновых таймеров после правки не осталось.

Радиус

Строки, которые раньше не старели, начнут стареть и попадут в архив. Это цель, а не побочный эффект: created_at цел, данные не теряются, строка просто перестаёт считаться свежей.

Проверка

  • 2823 passed (2821 + 2 новых теста, оба RED→GREEN на дефекте);
  • ruff check чисто, ruff format 750 файлов, mypy 63 = baseline;
  • blind verifier, два прогона. Первый отклонил пакет: нашёл второй ночной путь (cls_replay), который я пропустила, — исправлен здесь же. Второй прогон: 14/14 PASS, регрессий нет (83 теста по quality/blame/branches/compact/forgetting/diagnostics), поведение доказано на живом archive-lookup с контрольной рукой.

Вне объёма

F42 (created_at refresh в graph_miners._insert_edge) — отдельный пакет: нужно решить, что NREM считает жизнью ребра (created_at как рождение против last_evidence), а это меняет семантику ночного прохода.

…ed_at

`updated_at` is the age that `lifecycle.forgetting.decay` reads and that the
archive cutoff compares against. Two background paths stamped it with `now` on
every score change, so a rescored or frequently recalled row looked freshly
written forever: it never aged, never decayed and never archived, while the
scheduler's own candidate filter (`updated_at > now - only_recent_days`) kept
handing it back.

Measured on the live bases before the fix: Hermes had 94 rows older than 60 days
by `created_at` that read as younger than 30 by `updated_at` (634 of 973 rows had
been touched within a day).

Both writers now update `importance` only:
  - `lifecycle/importance_scheduler.py::_rescore_user` (periodic sweep)
  - `features/replay.py::cls_replay` (nightly hook, recall boost)

The rescore time is already recorded in `importance_audit.rescored_at`, which is
where it belongs. `features/quality.py::record_feedback` keeps writing
`updated_at` on purpose: it is reachable only from the interactive
`memory_quality` tool, not from a timer, and an explicit feedback action really
does touch the row.

Rows that previously never aged will now age and become archivable. That is the
fix, not a side effect: `created_at` is untouched and no data is lost.
@coderabbitai

coderabbitai Bot commented Nov 3, 2026

Copy link
Copy Markdown

Important

  • Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@github-actions github-actions Bot added the fix label Nov 3, 2026
@Cipher208
Cipher208 merged commit 235af2f into master Nov 3, 2026
19 checks passed
@Cipher208
Cipher208 deleted the fix/rescore-must-not-reset-age branch November 3, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant