You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(memory): deterministic buffer turn-summaries (no LLM) (#16)
* feat(memory): deterministic buffer turn-summaries (no LLM)
The tier-2 buffer is injected into the system prompt every turn but each
entry was a naive truncation of raw text (message[:97]+"..." or
shorten(s,100)) — a byte slice that can split a UTF-8 rune and usually
captures only filler ("Sure, I'll help. Let me start by..."), leaving the
buffer with almost no recall signal.
Centralize summarization in MemoryManager.AppendBuffer via a new
deterministic, no-LLM, rune-safe summarizeForBuffer: strip fenced/inline
code, conservative markdown, collapse whitespace, drop a leading filler
clause (only when substance remains), and excerpt to 200 runes at a
sentence/word boundary. All 8 call sites now pass raw text.
Placement is load-bearing: summarization lives in AppendBuffer, not
Buffer.Append, so RestoreBuffer (which calls Buffer.Append directly with
already-summarized lines) never re-processes persisted summaries. Old
session buffers restore verbatim — no migration. shorten() is retained
for its non-buffer callers (session labels, tool-result display).
Tests: table-driven heuristic cases (code/markdown/filler/multibyte/
hard-cut), an AppendBuffer no-mid-word-cut regression, and a verbatim
RestoreBuffer guard for the invariant.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(memory): harden buffer excerpt against early-terminator collapse
Adversarial review (AI Verification Protocol) found two defects in
summarizeForBuffer:
- D-01 (critical): when the only sentence terminator in the window was an
early abbreviation/version/domain ("e.g.", "v1.2", "node.js"), the
excerpt collapsed to a few runes (e.g. "e.g.…") — destroying the
summary. Now a sentence cut is only preferred when it lands at least
halfway through the window; otherwise fall back to the word boundary
near the cap.
- D-02 (minor): an unclosed code fence (unmatched by the fenced-block
regex) left a stray backtick in the output. Residual backticks are now
stripped after inline-code unwrapping.
Adds regression tests for both.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(memory): make LLM test mocks concurrency-safe
CI's `go test -race` flagged a write/write data race on mockLLM.lastUser
in TestConsolidateOnEnd_FiresAtSessionEnd. Root cause:
OnSessionEndWithProvenance legitimately calls the LLM from two goroutines
at once — synchronous episode extraction plus the background
consolidation goroutine — but the test mocks held unsynchronized shared
state.
- mockLLM: guard lastUser with a mutex; add getLastUser() accessor.
- countCallsLLM: replace the non-atomic *int counter with atomic.Int64
and a calls() accessor (same latent race, same OnSessionEnd trigger).
Test-only change; no production code affected. Verified with
`go test ./internal/memory/... -short -race -count=30` and the full
`go test ./... -short -race` suite.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
0 commit comments