Skip to content

[Bug] meta.rewardDirty is re-set on episode reopen and never cleared when the episode later reaches a terminal state #2370

Description

@chiefmojo

Pre-submission checklist | 提交前检查

  • I have searched existing issues and this hasn't been mentioned before | 我已搜索现有问题,确认此问题尚未被提及
  • I have read the project documentation and confirmed this issue doesn't already exist | 我已阅读项目文档并确认此问题尚未存在
  • This issue is specific to MemOS and not a general software issue | 该问题是针对 MemOS 的,而不是一般软件问题

Bug Description | 问题描述

meta.rewardDirty is written when an episode is reopened, but if that episode subsequently reaches a terminal state through a path other than the ones that clear the marker, the marker is never removed. It is then indistinguishable from a live dirty state to both episodeRewardIsDirty() and a consistency watchdog built on json_type(meta_json,'$.rewardDirty') IS NOT NULL, so the episode is reported as dirty on every run — indefinitely — while being fully resolved.

Observed on a live installation: episode ep_ewndcv3q2ngk, 7 traces.

  • scored heuristically and terminal-skippedmeta.reward.skipped: true, traceCount: 2 (1 turn, below the 2-exchange minimum)
  • reopened 89 s latermeta.rewardDirty.reason: "episode_reopened", reopenedFor: "follow_up"
  • later recovered via recoveryReason: "dirty_reward_rescore", closeReason: "finalized", rTask = 0.0

json_type(meta_json,'$.rewardDirty') still returns object 17 days later:

{"reason":"episode_reopened","reopenedFor":"follow_up","at":<ts>,"previousRTask":0,"previousScoredAt":<ts>}

Manual json_remove(meta_json,'$.rewardDirty') clears every symptom immediately. Note that json_set(meta_json,'$.rewardDirty',NULL) does notjson_type then returns 'null', which is still IS NOT NULL, so any check written as a null-test keeps firing.

How to Reproduce | 如何重现

Not yet reproduced deterministically; the above is a live record. The ordering that appears to be required:

  1. Episode is scored and terminal-skipped → clears the marker (core/reward/reward.ts:135).
  2. Episode is reopened while rTask != null || meta.rewardre-sets the marker (core/session/episode-manager.ts:337). The skip-time clear has already run and will not run again.
  3. Episode later reaches a terminal state through the recovery path, which finalizes it without touching the marker.
  4. Marker persists.

Expected: reaching a terminal/recovered state clears rewardDirty regardless of which path produced it.
Actual: marker persists; the episode is reported dirty forever.

Relevant code (verified against main, de806942)

Set:

  • core/session/episode-manager.ts:337 — on reopen, gated on snap.rTask != null || snap.meta.reward

Clear:

  • core/pipeline/memory-core.ts:1720 — clears only inside the prior-failed-attempts rescan loop, and only for episodes that pass through it this pass
  • core/pipeline/memory-core.ts:1757 — clears skipped && hasRewardDirtyMarker rows, but over a bounded page
  • core/reward/reward.ts:135 and :276 — clear at the moment a skip/terminal reward is written

The last three all clear at a specific moment or over a bounded page. Step 2 above re-dirties the episode after the moment-clear has passed, and the remaining two are the only later opportunities — so an episode that misses both keeps the marker permanently. Whichever of those two should have caught this row did not, which is the part worth confirming on the maintainer side; the durable fix is probably to drop the marker wherever the episode reaches a terminal reward state, rather than at each site that can write one.

Environment | 环境信息

  • memos-local-plugin 2.0.16
  • algorithm.lightweightMemory.enabled: false
  • Linux (Debian-based), Node 22
  • re-verified against upstream main @ de806942

Additional Context | 其他信息

Impact: an operator-facing consistency check reports the row on every run forever. That trains the reader to ignore the check, which is the same class of observability decay that made a separate monitoring review misdiagnose frozen metrics.

Relevant prior issues reviewed: #1847, #1784, #2120, #1966 — all different failure modes (dirty-scan/skip-path fixes, abandoned-episode skip, dirty-closed pagination, ghost-trace over-scoring). I could not find an existing report of the marker surviving on a resolved episode.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

ai:taskDispatched to AI coding agent | 已派发给 AI 编码任务ai:testingAI agent is running tests | AI 正在运行测试area:pluginOpenClaw & Hermesstatus:in-progressSomeone or AI is working on it | 人工或 AI 正在处理types:bugSomething isn't working | 功能异常

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions