Skip to content

fix(memory): word/sentence-boundary aware fact truncation - #12383

Open
LeMonBLOCK wants to merge 1 commit into
diegosouzapw:release/v3.8.51from
LeMonBLOCK:fix/memory-extraction-boundary-truncation
Open

fix(memory): word/sentence-boundary aware fact truncation#12383
LeMonBLOCK wants to merge 1 commit into
diegosouzapw:release/v3.8.51from
LeMonBLOCK:fix/memory-extraction-boundary-truncation

Conversation

@LeMonBLOCK

Copy link
Copy Markdown

Bug

src/lib/memory/extraction.ts truncates extracted memory facts with raw
character-offset slicing and no boundary awareness:

  • sanitizeMatch() did raw.trim().replace(/\s+/g, ' ').slice(0, MAX_FACT_LENGTH)
    — a hard cut that can land mid-word.
  • capExtractionText() did text.slice(-MAX_EXTRACTION_TEXT_LENGTH) on the
    front edge when input exceeds MAX_EXTRACTION_TEXT_LENGTH (64KB) — same
    problem, opposite edge.

These facts later get injected into LLM context as <system-reminder>
memory blocks, so a mid-word/mid-clause cut produces garbled fragments
(e.g. text starting with a stray closing parenthesis, or ending with no
punctuation and a half word).

Fix

  • sanitizeMatch() now backs the cut index off to the nearest clean
    boundary within a lookback window: prefers a sentence-ending mark
    (./!/?) so the fact reads as a complete clause, falls back to a
    plain whitespace/word boundary, and only falls back to the original hard
    cut when no boundary exists in the window (e.g. one long unbroken run of
    characters).
  • capExtractionText() applies the equivalent boundary-aware trim on the
    front edge of the kept tail (extends the cut forward to the next
    whitespace boundary rather than slicing mid-word).

This mirrors the boundary-aware truncation already used by
open-sse/services/compression/lite.ts (issue #8169) for tool-result
truncation, so the codebase now has one consistent pattern for this class
of problem.

Testing

  • New: tests/unit/memory-extraction-boundary-truncation.test.ts — covers
    a long match cut at a word boundary, a match cut preferentially at
    sentence-ending punctuation, short strings passing through untouched,
    the no-boundary-in-window fallback, and capExtractionText's
    boundary-aware tail behavior (both under and over the 64KB cap).
  • Ran existing tests/unit/memory-extraction.test.ts alongside the new
    file — all 28 tests pass, no regressions:
    ℹ tests 28
    ℹ pass 28
    ℹ fail 0
    
  • npx tsc --noEmit -p tsconfig.json — no new type errors introduced by
    this change.

No unrelated files were touched.

sanitizeMatch() and capExtractionText() previously did raw character-offset
slices (slice(0, MAX_FACT_LENGTH) / slice(-MAX_EXTRACTION_TEXT_LENGTH)) with
no boundary awareness, producing garbled mid-word/mid-clause fragments that
get injected into LLM context as memory facts.

- sanitizeMatch() now backs the cut off to the nearest sentence-ending
  punctuation (. ! ?) within a lookback window, falling back to a plain
  whitespace boundary, falling back to the original hard cut only when no
  boundary exists nearby.
- capExtractionText() applies the equivalent boundary-aware trim on the
  front edge of the kept tail.

Mirrors the boundary-aware truncation pattern already used by
open-sse/services/compression/lite.ts (diegosouzapw#8169) for tool-result truncation.

Adds tests/unit/memory-extraction-boundary-truncation.test.ts covering
word-boundary cuts, sentence-boundary preference, short-string passthrough,
the no-boundary-available fallback, and capExtractionText's tail behavior.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant