feat(staleness): auto-fire is_stale via a bounded file-existence revalidation pass - #443
Merged
Merged
Conversation
…lidation pass The injection banners now surface is_stale (age · grade · stale), but on file grounds that flag was only ever SET by the manual validate_memory tool — so a memory referencing a moved/deleted file stayed is_stale=FALSE until someone ran it by hand. harness-comparison rev.2 measured exactly this: Harness B served facts months stale with no stale flag. - handlers/consolidation/memory_staleness_pass.py: revalidate_staleness() pages non-stale, file-referencing memories, re-checks path existence via an injected resolver, and marks is_stale using the same pure core.staleness.assess_staleness + store.mark_memory_stale the tool uses. Bounded by a scan cap with an id cursor. Mark-ONLY: never de-stales, so it cannot fight the active-forgetting (Rac1) circuit that also writes is_stale; rehabilitation stays with the explicit validate_memory tool. Existence-only; content-change detection (hashing) is a separate #110 seam. - scripts/memory_staleness_revalidate.py: dry-run-by-default entry point (mirrors scripts/memory_domain_backfill.py), --apply to write, reusing validate_memory's path resolver. Script-invoked — not on the commit critical path or the hot consolidate cycle. - tests: mark-only, skip-no-refs, skip-already-stale, never-destale, and scan-limit behavior with a fake store + injected resolver (DB-free). Addresses fleet-watch #110 (the "is_stale must actually fire" seam). ruff + craftsmanship gate clean; pass scenarios verified locally; full pytest in CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017fnomaXS71hgxMw2zr7HGR
Craftsmanship gate flagged it as a new unsourced constant (§8). Annotate it as a chosen per-run bound mirroring the existing domain-backfill cap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017fnomaXS71hgxMw2zr7HGR
12 tasks
10 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to the freshness-stamping work (#442). That PR made the injection banners show
is_stale(age · grade · stale); this PR makes the flag actually fire on file grounds. Todayis_staleis only ever set by the manualvalidate_memorytool, so a memory referencing a moved/deleted file staysis_stale=FALSEuntil someone runs the tool by hand — which is precisely whatharness-comparisonrev.2 measured (Harness B served facts months stale with no stale flag).revalidate_staleness()pages non-stale, file-referencing memories, re-checks path existence via an injected resolver, and marksis_staleusing the same purecore.staleness.assess_staleness+store.mark_memory_stalethe tool uses.Two safety decisions:
consolidation/forgetting.pythat also writesis_stale. Rehabilitation stays with the explicit, human-invokedvalidate_memory.memory_domain_backfillprecedent.Existence-only; content-change detection (a file that still exists but diverged) needs per-ref hashing and is a separate #110 seam.
Addresses cdeust/fleet-watch#110.
Type of change
Test plan
Authoring env lacks
pydantic/numpy/DB, so the full suite runs in CI. Verified locally:New tests (
tests_py/handlers/consolidation/test_memory_staleness_pass.py, DB-free with a fake store + injected resolver): marks only the memory with a missing ref; never de-stales; skips no-ref and already-stale rows; respects the scan limit. All three scenarios executed and passing locally (ran the test functions directly).ruff check+ruff format --check+ the craftsmanship gate: clean.All existing tests pass — deferred to CI.
New tests added for new behavior.
Mutation check: flipping mark-only→bidirectional (would fight forgetting), dropping the no-ref/already-stale guards, or ignoring the scan cap are all caught.
Manual verification of the pass logic against a fake store.
Audit notes
Coding-standards compliance
core.staleness).# source:notes.Breaking changes
None. New pass + script; no existing behavior changed. Nothing runs it automatically yet (dry-run script only) — wiring a trigger is a deliberate follow-up.
Reviewer checklist
Generated by Claude Code