Skip to content

Surface prunable-memory backlog in the forge-loop doctor memory_integrity probe #429

@hadamrd

Description

@hadamrd

Problem

_memory_integrity_check in src/forge_loop/control/doctor.py reports the
memory store's breakdown (decisions/active, rejected_paths, episodes,
skills/procedural) and returns PASS whenever the store opens cleanly. It has
no visibility into the prunable-memory backlog — the active episodic items
that are not load-bearing and accumulate monotonically (one per merged/failed
issue) until they drown the load-bearing decisions in boot context (the failure
named by parent epic #426).

Concrete example: a loop that has run 300 issues holds ~600 non-load-bearing
episodes alongside ~30 decisions. doctor today prints
episodes=600 and returns PASS — the maestro resuming after context loss
gets a green light and no signal that compaction is overdue. The prunable
backlog must become an explicit, inspectable health signal so compaction can be
gated on something visible.

Dependency

is_load_bearing_memory(item) is the pure predicate added by sibling #427
(same epic #426). "Prunable" = active items where is_load_bearing_memory(item)
is False. This ticket consumes that predicate; it does not define it.

Acceptance criteria

  • A named module-level threshold constant (e.g. PRUNABLE_MEMORY_WARN_THRESHOLD)
    is declared once in doctor.py (no bare literal at the call site), matching the
    existing named-constant convention (PASS/FAIL/WARN, *_REMEDIATION).
  • A named compaction remediation string constant (e.g. COMPACT_REMEDIATION) is
    declared alongside the other *_REMEDIATION constants and suggests compacting
    prunable memory.
  • _memory_integrity_check's detail line additionally reports the prunable count
    (active items where is_load_bearing_memory(item) is False) alongside the
    existing decisions/active / rejected_paths / episodes / skills/procedural
    breakdown. The existing fields are preserved unchanged (do not rename or drop).
  • When prunable count > threshold: probe status is WARN, detail names
    the prunable count, and remediation is the compaction remediation (non-None).
  • When prunable count <= threshold: behaviour is unchanged — status is
    PASS and remediation is None (only the detail string gains the new field).
  • The absent-store (WARN, not-applicable), corrupt-store (FAIL) and
    unreadable-store (FAIL) branches are untouched and still behave as before.
  • The probe remains read-only: no writes to .forge, consistent with the
    module-level invariant in doctor.py's docstring.

Test matrix

Add to tests/test_doctor_control_plane.py (the memory_integrity unit group
around test_reports_counts_on_populated_store, lines ~267-296):

  • Unit — backlog reported (PASS): seed a store with a few load-bearing items
    and a sub-threshold number of prunable episodes; assert status == PASS,
    remediation is None, and detail contains the prunable-count field.
  • Unit — backlog crosses threshold (WARN): seed prunable episodes above
    the threshold; assert status == WARN, detail names the prunable count and
    mentions compaction, and remediation is the compaction constant (non-None).
  • Unit — boundary: exactly-at-threshold count stays PASS (proves the strict
    > comparison, not >=); threshold+1 flips to WARN.
  • Unit — existing fields preserved: the WARN detail still contains
    decisions/active=, rejected_paths=, episodes=, skills/procedural=.
  • Adversarial / sad-path: corrupt memory.db still returns FAIL (the new
    prunable logic must run only on the clean-open path and never mask the
    corrupt-store FAIL branch). Reuse the existing test_fails_cleanly_on_corrupt_db
    pattern.
  • Integration: forge-loop doctor --json over a seeded above-threshold store
    emits the memory_integrity object with status == "warn" and a non-null
    remediation, and the human table renders the prunable count + remediation.

Out of scope

File pointers

  • src/forge_loop/control/doctor.py_memory_integrity_check (~L162-202);
    add the threshold + remediation constants near the existing *_REMEDIATION
    block (~L49-58).
  • tests/test_doctor_control_plane.pymemory_integrity unit group (~L267-296),
    the --json integration test (~L445+), and the human-table test (~L554+).
  • src/forge_loop/memory/__init__.py / src/forge_loop/memory/store.py /
    models.py — source of is_load_bearing_memory (from Add a pure is_load_bearing_memory(item) predicate for curated memory #427); read-only here.
  • src/forge_loop/memory/store.pySqliteMemoryStore.list_active(kind=...)
    is how the probe already enumerates items; reuse it.

Original report

Parent: #426

Part of epic: "Bound curated memory with a load-bearing-preserving compaction arc"

Extend the existing _memory_integrity_check in control/doctor.py so its detail
line additionally reports the prunable-memory count (active items where
is_load_bearing_memory is False) alongside the current
decisions/rejected/episodes/skills breakdown, and returns WARN with a compaction
remediation when that count crosses a threshold. One mechanism: enrich the
existing probe; no new probe, no new command. Primary acceptance: with prunable
count above the threshold the probe status is WARN and its detail names the
prunable count and suggests compaction; below it, behaviour is unchanged (PASS).
~60 net LOC incl. tests.

Customer story: A maestro resuming forge-loop after context loss can only
gate compaction on what it can see; this makes the prunable-memory backlog an
explicit, inspectable health signal in doctor.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions