fix: summarize conversations only once idle, and refresh summaries as they grow#109
Open
minyek wants to merge 1 commit into
Open
fix: summarize conversations only once idle, and refresh summaries as they grow#109minyek wants to merge 1 commit into
minyek wants to merge 1 commit into
Conversation
… they grow
Summaries were write-once and could freeze a still-active conversation into a partial mid-session snapshot that was then never refreshed, so the one-line summary shown beside search results for long or resumed conversations was misleading. This fixes both halves: defer summarizing until a conversation has gone idle, and refresh a summary when its transcript later grows. (Search itself was always complete — it indexes exchanges; this fixes only the displayed summary.)
- Coverage header `__COVERAGE__ {bytes,lastExchange,schema}` records the archived JSONL byte size a summary reflects.
- shouldQueueForSummary re-queues a real summary once the archive grows past its covered bytes (transcripts are append-only).
- Quiescence gate (isQuiescent + EPISODIC_MEMORY_SUMMARY_QUIESCENCE_HOURS, default 1) only (re)summarizes after the conversation has been idle, measured from the last exchange's timestamp, so a mid-session snapshot is never frozen.
- Legacy header-less summaries get a no-LLM baseline stamp on upgrade — no mass re-summarization.
- A re-summary failure preserves the prior real summary; an error sentinel is written only for a first-time failure.
- search strips the coverage header from the displayed summary.
Gating and writing are shared (needsSummary, writeErrorSentinelIfNew, summarizeIfQuiescent) across the indexer paths and the sync loop; the _HOURS config defaults convert to ms at a single boundary.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Summaries are write-once. A conversation that gets summarized while still in
progress keeps a summary of only its early portion forever — every later turn
is invisible to the summary. For long-running or resumed conversations, the
one-line summary shown next to search results is therefore misleading.
The fix has two complementary halves: defer summarizing a conversation until
it has gone idle (so a still-active conversation is never frozen into a partial
snapshot), and refresh a summary when its transcript later grows. Note the
behavioral change from the defer half: a conversation is no longer summarized
until it has been idle for the quiescence window (
EPISODIC_MEMORY_SUMMARY_QUIESCENCE_HOURS,default 1h), so very recent conversations intentionally have no summary yet.
(Search results themselves were always complete — search runs over indexed
exchanges, kept fresh via
INSERT OR REPLACE. This fixes only the summarydisplayed alongside them.)
Symptom
There is no error in the logs — the symptom is silently wrong output:
<archive>/<project>/<session>-summary.txtfor an active or long conversationdescribes only the first handful of exchanges.
stats/verifycount the file as "summarized", so it is never revisited.Environment
sessions.
Steps to Reproduce
syncagain (or let the SessionStart background sync run).<archive>/<project>/<session>-summary.txt.even though the transcript has grown substantially.
Root Cause
shouldQueueForSummaryonly re-queued a conversation when its sentinel wasmissing or a stale error marker. Once a real summary existed, the
conversation was treated as done permanently:
summary reflected, so transcript growth was undetectable.
refreshed at exchange 500 — the file only ever grows, but nothing acted on
that growth.
in-progress conversation would freeze a mid-session snapshot that is itself
immediately stale.
Fix Summary
__COVERAGE__ {"bytes":<n>,"lastExchange":"<iso>","schema":1}— recording thearchived JSONL byte size the summary reflects (
formatSummaryFile/parseSummaryFile,buildCoverage,writeSummary).shouldQueueForSummary(path, currentArchiveBytes)re-queues a real summary when the archive has grown past its covered bytes
(append-only ⇒ a size increase means new content).
isQuiescent+EPISODIC_MEMORY_SUMMARY_QUIESCENCE_HOURS(default
1) only (re)summarize after the conversation has been idle for thewindow, so a mid-session snapshot is never frozen. Idle is measured from the
last exchange's timestamp — the archive copy's mtime is reset on every
sync and is unreliable.
current-size baseline (
ensureCoverageBaseline) — a header rewrite, no LLMcall — so existing installs gain a growth baseline without a mass
re-summarization on upgrade.
summary; an error sentinel is written only for a first-ever failure, so a
transient SDK error never discards a good summary (
writeErrorSentinelIfNew).searchstrips the coverage header from the summary shownalongside results.
Internally, the gate + write + error-sentinel logic is now shared
(
needsSummary,writeErrorSentinelIfNew,summarizeIfQuiescent) across thethree indexer paths and the sync loop, and the
_HOURSconfig defaults convertto milliseconds at a single boundary (
MS_PER_HOUR).Tests added
test/summary-coverage.test.ts— coverage header round-trip and legacy baseline stamping.test/summary-quiescence.test.ts— quiescence verdicts and_HOURSenv parsing.test/summary-sentinel.test.ts— growth-aware gate decision table;needsSummary/writeErrorSentinelIfNew.test/sync-resummary.test.ts— growth-driven re-summary, no-op when unchanged, preserve-on-failure.test/search-summary-strip.test.ts— the header never leaks into the displayed summary.sync-error-sentinel,show, andsynctests for the new header/gate.Fix Verified
dist(isolated temp dirs, real summarizer):"bytes":8237."bytes":41069.Caveat / Notes
is deliberate: transcripts are append-only and the per-sync coverage scan is
~23 ms even against a multi-GB archive, so no recency cutoff or minimum-delta
threshold is needed. The quiescence gate bounds work to one re-summary per
idle-after-growth episode.