Skip to content

fix(forgetting): the report must say what actually happened - #194

Merged
Cipher208 merged 1 commit into
masterfrom
fix/say-what-happened
Nov 4, 2026
Merged

Cipher208 merged 1 commit into
masterfrom
fix/say-what-happened

Conversation

@Cipher208

Copy link
Copy Markdown
Owner

Что

Три места говорили больше, чем правда.

1. Оба скрипта открывались фразой «Every removed row is copied to archived_memories». Это неверно: архивируются только эпизоды и факты, а epi_nodes/epi_edges/epi_tags удаляются без архива. Контент узла — производный текст (он уже живёт в эпизоде или факте), и маршрута восстановления для узла нет, так что возвращать нечего. Новые докстринги говорят ровно это и честно называют цену: после прогона граф не восстанавливается из архива.

2. purge_speaker_layer_leaks писал на каждый удалённый узел audit-строку с archive: archived_memories:speaker_layer_leak — журнал утверждал, что есть копия в корзине, а её нет. Теперь запись узла: {"deleted": true, "table": "epi_nodes"}, без ключа archive. Утверждение «EVERY REMOVAL LEAVES A TRAIL» исправлено: трек есть у фактов, эпизодов и узлов; рёбра и теги уходят вместе с узлом без собственного трека. Существующие записи audit_log не переписываются.

3. Отчёт восстановления говорил обратное — что значение вернулось, хотя не вернулось. EpisodicMemory.save дедуплицирует по (layer, user_id, summary) и возвращает существующую строку как есть, поэтому её собственные created_at/emotional_weight остаются. Замер: архивное время 1700000000.0, выжившая строка 1700099999.0 — отчёт утверждал, что время вернулось. _restore_episode теперь читает выжившую строку обратно и сообщает неприменившиеся поля в not_recorded.

И это же чтение происходит в обоих режимах. В dry_run отчёт оставался оптимистичным: обещал, что время и вес вернутся, а реальный проход потом говорил, что нет. Это тот самый дрейф, который ветка core_memory закрыла для проверки ключа, задавая вопрос в обоих режимах. dry_run теперь выполняет ровно тот дедуп-запрос, что и save(), и сообщает тот же not_recorded, ничего не записывая.

Проверка

  • 2867 passed;
  • ruff чисто, format 756 файлов, mypy 42 = baseline;
  • blind verifier: дефект дрейфа найден и подтверждён замером, докстринги сверены с кодом построчно; после второго круга фиксера паритет подтверждён независимым замером: dry_run и реальный проход дают идентичный not_recorded, выжившая строка хранит своё время;
  • A2 и B проверены end-to-end на скрэтч-базах через реальные пути.

Вне объёма

memory_type=None → честный отказ unroutable, измерено — не дефект. Архивация графа — отдельное решение.

Three places stated more than is true, in code, in the audit trail, and in the
restore report.

Both purge scripts opened with "Every removed row is copied to
`archived_memories`". False: only episodes and core facts are archived;
`epi_nodes`, `epi_edges` and `epi_tags` are deleted outright. A node's content is
derived text — it already lives in an episode or a fact — and there is no restore
route for a node, so there is nothing a restore could return. The new docstrings
say exactly that, and state the cost plainly: after the purge the graph cannot be
rebuilt from the archive.

`purge_speaker_layer_leaks` then wrote one audit row per deleted node claiming
`archive: archived_memories:speaker_layer_leak` — the log asserted a bin copy that
does not exist. The node row now records `{"deleted": true, "table": "epi_nodes"}`
and no `archive` key, so a scan of audit_log can never infer a bin copy. The
"EVERY REMOVAL LEAVES A TRAIL" claim is corrected to match reality: facts, episodes
and nodes get a trail; edges and tags go with their node and leave none of their
own.

The restore report had the opposite defect — it said a value came back when it did
not. `EpisodicMemory.save` dedups on `(layer, user_id, summary)` and returns the
surviving row untouched, so a restore over an existing episode keeps that row's own
`created_at` and `emotional_weight`; the archive's values never land. Measured:
archived time 1700000000.0, surviving row 1700099999.0, and the report listed
`created_at` as returned. `_restore_episode` now reads the surviving row back and
reports any field that did not apply in `not_recorded`.

That read-back is the same question in both modes. In dry_run the report used to
stay optimistic — it claimed the time and weight would come back while the real
pass then said they did not. That is the drift the core branch fixed for the key
check, where the question is asked in both modes so preview and pass cannot answer
differently. Dry_run now runs the exact dedup query `save()` would run and reports
the same `not_recorded` as the pass, without writing anything.
@coderabbitai

coderabbitai Bot commented Nov 4, 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

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

@github-actions github-actions Bot added the fix label Nov 4, 2026
@Cipher208
Cipher208 merged commit d5b09ea into master Nov 4, 2026
19 checks passed
@Cipher208
Cipher208 deleted the fix/say-what-happened branch November 4, 2026 13:53
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