Skip to content

fix(intelligence): integrate retention fields into runtime paths - #1084

Merged
wayyoungboy merged 3 commits into
oceanbase:mainfrom
knqiufan:fix/retention-fields-runtime-integration
Jun 29, 2026
Merged

fix(intelligence): integrate retention fields into runtime paths#1084
wayyoungboy merged 3 commits into
oceanbase:mainfrom
knqiufan:fix/retention-fields-runtime-integration

Conversation

@knqiufan

Copy link
Copy Markdown
Contributor

Summary

Fixes #1082

reinforcement_factor, initial_retention, and current_retention were written during process_memory_metadata() but never consumed in the core runtime paths (should_forget(), on_get(), search ranking). This meant:

  • All memories with the same memory_type were forgotten at the same time regardless of importance
  • "Review reinforcement" (boosting retention on access) was not implemented
  • current_retention was a static display value that never changed

Changes

  • should_forget() now computes effective_retention = initial_retention * decay_factor so higher-importance memories survive longer than low-importance ones
  • New reinforce() method boosts current_retention with diminishing returns when a memory is accessed at or after its next_review time, and advances review_count/next_review/last_reviewed
  • on_get() integration triggers reinforcement on review-due access and preserves current_retention during periodic reprocessing (access_count % 5) instead of resetting it
  • New calculate_current_retention() provides a unified real-time effective retention calculation for search ranking and display
  • process_search_results() uses effective_retention (= initial_retention * decay_factor) instead of raw decay_factor for ranking

Files Changed

File Change
src/powermem/intelligence/ebbinghaus_algorithm.py Added _resolve_initial_retention(), reinforce(), calculate_current_retention(); modified should_forget()
src/powermem/intelligence/plugin.py on_get() now triggers review reinforcement and preserves retention during reprocessing
src/powermem/intelligence/intelligent_memory_manager.py process_search_results() uses effective_retention
tests/unit/intelligence/test_retention_runtime.py 9 new tests covering all changes
tests/unit/intelligence/test_ebbinghaus_decay_rate.py Updated 4 assertions for renamed field (decay_factor -> effective_retention)

Test plan

  • 9 new unit tests pass: test_retention_runtime.py
  • All 82 intelligence-related unit tests pass (zero regressions)
  • Full unit test suite: 563 passed (18 pre-existing env-specific failures unrelated to this change)

…anbase#1082)

reinforcement_factor, initial_retention, and current_retention were written
during process_memory_metadata() but never consumed in the core runtime
paths (should_forget, on_get, search ranking). This commit:

- should_forget() now computes effective_retention = initial_retention *
  decay_factor so higher-importance memories survive longer
- New reinforce() method boosts current_retention with diminishing returns
  when a memory is accessed at or after its next_review time
- on_get() triggers reinforcement on review-due access and preserves
  current_retention during periodic reprocessing (access_count % 5)
- New calculate_current_retention() provides unified real-time retention
  for search ranking and display
- process_search_results() uses effective_retention instead of raw decay

Fixes oceanbase#1082
@wayyoungboy

Copy link
Copy Markdown
Member

I found one runtime issue that I think should be addressed before merging.

reinforce() now updates current_retention, but the runtime consumers still ignore that field:

  • should_forget() computes effective_retention = initial_retention * decay_factor
  • calculate_current_retention() returns initial_retention * decay_factor

In on_get(), a due review can call reinforce(normalized) and populate intel_updates, but the following forget check still calls should_forget(normalized) with the pre-reinforcement memory. That means a memory whose old initial_retention * decay_factor is below the working threshold can still be forgotten on the same access that just boosted current_retention. Search ranking has the same gap because it goes through calculate_current_retention(), so reinforced memories are ranked as if the boost never happened.

Could we either apply the reinforcement result before the forget check and make current_retention part of the effective-retention calculation, or clarify that current_retention is only display metadata? As written, this seems to miss the goal of integrating retention fields into the runtime paths.

Address review feedback from oceanbase#1084: current_retention was written by reinforce() but never consumed by should_forget() or search ranking.

- should_forget() now uses max(initial_retention * decay_factor, current_retention) so reinforced memories are protected from premature forgetting

- on_get() applies reinforcement result to normalized memory before the forget check, preventing the same-call reinforce-then-forget race

- calculate_current_retention() returns max(base, stored current_retention) so search ranking reflects reinforcement boosts

- 4 new tests covering reinforcement-protects-from-forgetting and search-ranking-reflects-reinforcement scenarios
@knqiufan

Copy link
Copy Markdown
Contributor Author

@wayyoungboy Thanks for the thorough review! You're absolutely right, current_retention was being written but never read by the runtime consumers. I've pushed a follow-up commit (dcaf714) that addresses the issue:

Changes:

  1. should_forget() now computes effective_retention = max(initial_retention * decay_factor, current_retention), so a reinforced memory won't be forgotten if its boosted retention is still above the threshold.
  2. on_get() now applies the reinforcement result to the normalized memory dict before calling should_forget(), eliminating the race where a memory could be reinforced and then immediately forgotten in the same access.
  3. calculate_current_retention() similarly returns max(base, stored current_retention), so search ranking via process_search_results() correctly reflects reinforcement boosts.

Tests added (4 new, 76 total intelligence tests pass):

  • test_should_forget_respects_stored_current_retention — stored current_retention protects from forgetting
  • test_on_get_reinforced_memory_not_forgotten_same_call — reinforce-then-forget race is resolved
  • test_calculate_current_retention_reflects_reinforcement — max(base, stored) semantics
  • test_search_ranking_reflects_reinforced_current_retention — reinforced memory ranks higher

All 76 intelligence-related unit tests pass with zero regressions.

@wayyoungboy wayyoungboy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes because the latest follow-up makes current_retention a permanent floor for all memories created through the normal metadata path. process_memory_metadata() initializes current_retention to initial_retention, and this code then uses max(initial_retention * decay_factor, current_retention). Since the stored current_retention is not itself decayed, a realistic working memory with initial_retention=0.5 remains at an effective retention of 0.5 even after its time-decayed base retention has fallen below the 0.3 forget threshold, so should_forget() returns false indefinitely. The same floor is exposed through calculate_current_retention() and search ranking.

The new tests miss this because the should-forget and base retention cases hand-build metadata without the current_retention field that real memories always get. I think the fix needs to decay stored current_retention, store only review boosts separately, or otherwise avoid treating the initialized current_retention as a non-decaying lower bound.

Treat stored current_retention as a timestamped retention snapshot instead of a permanent floor, so normal memories can still decay while recent review boosts remain effective.
@knqiufan

knqiufan commented Jun 27, 2026

Copy link
Copy Markdown
Contributor Author

@wayyoungboy Thank you for the careful review!

Fixed in b740973.

What changed:

  • current_retention is now treated as a timestamped retention snapshot, not a permanent floor.
  • calculate_current_retention() decays stored current_retention from last_reviewed (falling back to created_at) before returning an effective runtime value.
  • should_forget() now uses the unified real-time retention calculation, so normally initialized working memories can still fall below the forget threshold.
  • reinforce() now boosts the decayed real-time retention rather than a stale stored snapshot.
  • on_get() applies the full reinforcement update (current_retention, last_reviewed, review_count, next_review) to the normalized memory before the same-call forget check.

Regression coverage added:

  • Real process_memory_metadata() output with initialized current_retention == initial_retention decays and can be forgotten.
  • Search ranking decays initialized current_retention instead of exposing a fixed floor.
  • reinforce() uses decayed current retention before applying the boost.

Verification:

  • python -m pytest tests/unit/intelligence/test_retention_runtime.py tests/unit/intelligence/test_ebbinghaus_decay_rate.py -> 39 passed
  • python -m pytest tests/unit/intelligence -> 79 passed
  • git diff --check -> clean

Please take another look when convenient.

@wayyoungboy wayyoungboy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rechecked the current head after the latest retention fix. The previous blocker appears addressed in the diff:

  • current_retention is now treated as a timestamped snapshot and decays from last_reviewed / creation time instead of acting as a permanent floor.
  • should_forget() now uses the unified real-time retention calculation.
  • reinforce() boosts the decayed real-time value, and on_get() applies the full reinforcement update before the same-call forget check.
  • The added tests cover initialized retention decaying below the forget threshold, search ranking not treating initialized retention as fixed, and reinforcement using the decayed snapshot before boosting.

I did not find a new blocking issue in this static re-review. I am not turning this into an approval in this pass because I did not run the PR test plan locally.

@wayyoungboy
wayyoungboy merged commit 425d72e into oceanbase:main Jun 29, 2026
23 checks passed
@knqiufan
knqiufan deleted the fix/retention-fields-runtime-integration branch July 5, 2026 16:50
222twotwotwo pushed a commit to 222twotwotwo/powercontext that referenced this pull request Aug 23, 2026
…anbase#1084)

* fix(intelligence): integrate retention fields into runtime paths (oceanbase#1082)

reinforcement_factor, initial_retention, and current_retention were written
during process_memory_metadata() but never consumed in the core runtime
paths (should_forget, on_get, search ranking). This commit:

- should_forget() now computes effective_retention = initial_retention *
  decay_factor so higher-importance memories survive longer
- New reinforce() method boosts current_retention with diminishing returns
  when a memory is accessed at or after its next_review time
- on_get() triggers reinforcement on review-due access and preserves
  current_retention during periodic reprocessing (access_count % 5)
- New calculate_current_retention() provides unified real-time retention
  for search ranking and display
- process_search_results() uses effective_retention instead of raw decay

Fixes oceanbase#1082

* fix(intelligence): make current_retention a runtime-effective field

Address review feedback from oceanbase#1084: current_retention was written by reinforce() but never consumed by should_forget() or search ranking.

- should_forget() now uses max(initial_retention * decay_factor, current_retention) so reinforced memories are protected from premature forgetting

- on_get() applies reinforcement result to normalized memory before the forget check, preventing the same-call reinforce-then-forget race

- calculate_current_retention() returns max(base, stored current_retention) so search ranking reflects reinforcement boosts

- 4 new tests covering reinforcement-protects-from-forgetting and search-ranking-reflects-reinforcement scenarios

* fix(intelligence): decay current retention snapshots

Treat stored current_retention as a timestamped retention snapshot instead of a permanent floor, so normal memories can still decay while recent review boosts remain effective.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] reinforcement_factor / initial_retention / current_retention not used at runtime

2 participants