Skip to content

Memory: skip markdown placeholder snippets during short-term promotion #80582

Description

@felixboenkost-droid

Summary

Short-term memory promotion can treat markdown skeleton placeholders (for example a bare - under a daily note heading) as promotable/relocatable snippets. In the affected case, promotion later appended empty or near-empty blocks into MEMORY.md because a grounded candidate could be rehydrated onto a placeholder line.

Repro shape

A daily note can contain normal bootstrap sections with empty bullets:

## Tagesnotizen
-

## Entscheidungen
-

If a grounded/dreaming candidate points at one of those placeholder lines, it can pass through candidate recording/ranking/apply. During relocation, broad substring matching can also match a summarized candidate against an empty or structurally meaningless markdown range.

Actual

Placeholder-only snippets such as - or markdown skeleton ranges can be recorded/ranked/applied, and promotion can append low-value empty blocks into long-term memory.

Expected

  • Bare markdown placeholders (-, *, +) should not be stored as short-term promotion candidates.
  • Markdown skeleton ranges made only of headings plus empty bullets should be ignored.
  • Candidate relocation should only use substring matching when both sides are meaningful enough to avoid matching onto placeholders.

Patch sketch

Add placeholder filtering near existing dreaming-contamination filtering:

function isMarkdownPlaceholderLine(raw: string): boolean {
  const line = raw.trim();
  if (!line || /^[-*+]$/.test(line)) return true;
  if (/^#{1,6}\s+\S/.test(line)) return !/\s[-*+]\s+\S/.test(line);
  return false;
}

function isPlaceholderShortTermSnippet(raw: string): boolean {
  const snippet = normalizeSnippet(raw);
  if (!snippet || /^[-*+]$/.test(snippet)) return true;
  const lines = raw.split(/\r?\n/);
  const nonEmpty = lines.map((line) => line.trim()).filter(Boolean);
  return nonEmpty.length > 0 && lines.every(isMarkdownPlaceholderLine);
}

function isUnpromotableShortTermSnippet(raw: string): boolean {
  const snippet = normalizeSnippet(raw);
  return !snippet || isPlaceholderShortTermSnippet(raw) || isContaminatedDreamingSnippet(snippet);
}

Then use isUnpromotableShortTermSnippet in:

  • store normalization
  • recordShortTermRecalls
  • recordGroundedShortTermCandidates
  • rankShortTermPromotionCandidates
  • candidate range relocation/apply path

Also make substring relocation require meaningful content:

function isMeaningfulRelocationSubstring(raw: string): boolean {
  if (isPlaceholderShortTermSnippet(raw)) return false;
  const tokens = normalizeSnippet(raw)
    .split(/[^A-Za-z0-9_]+/)
    .filter((token) => token.length >= 3);
  return tokens.length >= 3 && tokens.join("").length >= 18;
}

if (isMeaningfulRelocationSubstring(targetSnippet) && windowSnippet.includes(targetSnippet)) {
  return { matched: true, quality: 2 };
}
if (isMeaningfulRelocationSubstring(windowSnippet) && targetSnippet.includes(windowSnippet)) {
  return { matched: true, quality: 1 };
}

Regression tests to add

Suggested coverage in extensions/memory-core/src/short-term-promotion.test.ts:

it("treats markdown skeleton placeholders as non-promotable", () => {
  expect(__testing.isPlaceholderShortTermSnippet("-")).toBe(true);
  expect(__testing.isPlaceholderShortTermSnippet("## Tagesnotizen\n-\n## Entscheidungen\n-")).toBe(true);
  expect(__testing.isPlaceholderShortTermSnippet("- Keep gateway restarts supervised.")).toBe(false);
});
it("does not record placeholder-only grounded candidates for promotion", async () => {
  // daily note contains only `## Tagesnotizen` and `-`
  // recordGroundedShortTermCandidates(...) with snippet `-`
  // rankShortTermPromotionCandidates(...) should return []
});
it("does not rehydrate section summaries onto empty bullet placeholders", async () => {
  // candidate snippet is a real section summary
  // source range points at a stale/empty placeholder bullet
  // applyShortTermPromotions(...) should apply 0 promotions
});

Notes

I have a local patch with these helper functions and regression tests if maintainers want it converted into a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions