Add memory-diag quality command for objective review of memory-system
mechanisms and active memory content. The command is read-only and
non-authoritative, providing evidence, heuristic flags, and review
questions without making quality judgments or suggesting mutations.
Key components:
- quality-review-model.ts: builds ReviewBoardReport with provenance,
re-absorption detection, mechanism facts (rejection, reinforcement,
eviction/caps, identity/dedup), and memory content facts
- formatters/quality.ts: human and JSON output with separate
system-mechanism and memory-content sections
- commands/quality.ts: command entry point with --json, --verbose,
--no-emoji, --raw options
- cli.ts: parser whitelist for quality accepting --workspace, --json,
rejecting mutation/filter flags
co-author: code-execute-agent, comprehensive-code-reviewer,
systems-architect, creative-disruptor
Closes docs/plans/2026-05-11-memory-diag-quality-review-board.md
Wave 1 — Compaction prompt improvement:
- Add three wording-reuse bullets to buildCompactionPrompt() under
CRITICAL MEMORY RULES: do not create rephrased duplicates, reuse
existing wording exactly when re-emitting, only emit new memories
when the fact is new, materially corrected, or more specific.
- This attacks the root cause of zero reinforcement: compaction
generating variant text for the same durable fact.
Wave 2 — Bug fixes:
- Bug #2: Add placeholder comment to superseded_existing branch in
decision dedupe (unreachable until v1.5.4 numbered refs). Preserve
as const type assertions.
- Bug #3: Add memory_migration_superseded evidence event type. Both
P0 and quality cleanup migrations now produce evidence events for
superseded entries. loadWorkspaceMemory appends migration evidence
on first-load migrations only (idempotent via migration IDs). No
historical backfill.
- Bug #4: Add documentation comment explaining that feedback identity
key returns exact key (absorbed_identity currently impossible for
feedback). Add test verifying this behavior.
Wave 3 — Validation baseline script:
- Add scripts/dev/validate-identity-keys.ts: read-only script that
scans workspace memory stores, computes exact/identity key
collisions, and reports reinforcement statistics. Baseline matches
audit: 0 exact collisions, 0 identity collisions, 0 reinforcement
events across 123 active memories.
Identity extension is gated on measurement: if the prompt change
produces measurable reinforcement (reinforcementCount > 0), identity
extension may be unnecessary. Decision dedupe stays exact-only
(Wave 4 deferred).
Implement OQ-2 decision: allow at most one reinforcement per memory
identity per UTC calendar day. Same-day reinforcement is blocked
regardless of session or interval. This prevents repetitive-task
gaming where a daily recurring task could reach MAX_COUNT=6 in hours.
Guard order: same-session → calendar-day → 1-hour → max-count
(existing guards kept as defense-in-depth)
1 hour guard is redundant within same day but preserved for
sub-hour edge cases.
Phase 4 Tasks 4.1-4.3:
- memory-diag health --json: machine-readable MemoryDiagJSON output
- memory-diag explain: per-memory render status with strength, reasons,
evidence event IDs
- memory-diag trace --memory <id>: lifecycle history from evidence events
and relations (superseded_by, reinforced_by)
- MemoryRenderStatus type with 9 statuses
- All diagnostics are read-only, no storage mutations
- Privacy-safe: redacted text previews, no raw secrets
- 270 tests pass, typecheck pass
Move retention constants and math to a focused src/retention.ts module:
- All half-life, reinforcement, dormancy constants
- TYPE_FACTOR, SOURCE_FACTOR, USER_IMPORTANCE_FACTOR
- RETENTION_TYPE_MAX (renamed from TYPE_MAX)
- calculateInitialStrength, calculateEffectiveHalfLife,
calculateRetentionStrength, calculateDormantDays,
calculateEffectiveAgeDays, reinforceMemory
No behavior changes. retention.ts imports only types from types.ts.
Workspace-memory.ts still owns storage, consolidation, and rendering.
- Remove SAFETY_CRITICAL_FACTOR = 6.0 from workspace-memory.ts
- Remove safetyFactor from calculateInitialStrength() - all memories now
fade according to the same rules
- Remove safetyCritical bypass from applyTypeMaxCaps() - safetyCritical
entries compete normally under TYPE_MAX caps
- Preserve safetyCritical?: boolean in LongTermMemoryEntry type for
backward compatibility (no producer sets it to true)
- Update memory-diag to show deprecation warning instead of capacity alert
- Update tests: add backward-compatibility fixture test, deprecation
strength test, normal cap competition test
- Update docs/architecture.md, RELEASE_NOTES.md, CHANGELOG.md,
docs/configuration.md
Phase 1.5 complete: safetyCritical is now a deprecated field with no
active behavior. Safety rules belong in user-controlled agent.md files.
P0.1 - Fix dormant effective age formula:
- Use overlap logic: only apply dormancy to entry's lifetime
- Formula: activeDays + dormantOverlapDays * 0.25
- calculateDormantDays now returns total days (not excess past grace)
- Test: 28 dormant days → 17.5 effective days
P0.2 - Remove hard stale pruning:
- Remove isPrunableByAge from enforcement
- Remove rejected_stale from accounting reasons
- Elimination now by cap competition only
P0.3 - Integrate reinforcement:
- Call reinforceMemory in dedupe absorption path
- Call reinforceMemory in promotion duplicate path
- Update retentionClock on reinforcement
A1 - Retention clock reset on reinforcement
A4 - Fix tests to encode correct formula
Wave 2c - Dormant workspace tracking:
- Add lastActivityAt to WorkspaceMemoryStore
- Implement calculateDormantDays with 14-day grace period
- Wire dormant days into retention-strength calculation
Wave 3 - Reinforcement:
- Add lastReinforcedSessionID to LongTermMemoryEntry
- Implement reinforceMemory with guards (same-session, 1hr interval, max 6)
- Set retentionClock on memory creation in extractors.ts and plugin.ts
Tests: 219 → 222, all pass
Problem: clearPendingMemories() and recordPromotionRejections() would
incorrectly clear or mutate owned entries during global unowned promotion.
Fixes:
1. clearPendingMemories() now respects owner/unowned scope:
- global clearUnowned only clears unowned same-key entries
- owned same-key entries are preserved
- explicit global clear-all-by-key fallback still works
2. recordPromotionRejections() now has includeUnownedOnly option:
- global unowned rejection only increments/exhausts unowned entries
- owned same-key entries are preserved
3. Added regression tests:
- global unowned clear keeps owned same-key entries
- global unowned rejection only exhausts unowned same-key entries
Tests: 182 pass, 0 fail