feat: autoCaptureContextTurns rolling context window for extraction - #966
feat: autoCaptureContextTurns rolling context window for extraction#966gorkem2020 wants to merge 4 commits into
Conversation
ed2d3e4 to
9409506
Compare
|
Recomposed against current master after #988 merged: the branch now carries a single commit with only this family's delta (rolling pair window: trimTurnsToUserCap, dedupePairWindow, the autoCaptureContextTurns knob and pair-window wiring). The two earlier commits are gone because their content already landed on master via #942 and #965. Full suite green locally. |
rwmjhb
left a comment
There was a problem hiding this comment.
The rolling-context direction is useful, and the focused suites, full suite, build, generated output, and current CI are green. On head 9409506, however, the new feature still has three behavioral blockers:
-
The default configuration never retains assistant replies. The auto-capture loop appends assistant turns to
messageLoopTurnsonly whencaptureAssistant === true(index.ts:4229-4247), while that option defaults to false.autoCaptureContextTurnsis documented as retaining user turns “with their assistant replies,” but under the normal configuration the rolling window contains only user turns, so it cannot provide the assistant-side context needed to resolve references such as “yes, that one.” The integration test feeds A1/A2/A3 but asserts only U1/U2/U3. Please collect assistant replies as context independently of whether assistant messages are eligible memory sources, keepcaptureAssistantEligiblecontrolling extraction attribution, and assert the replies appear in later prompts with the default setting. -
dedupePairWindowcan delete a legitimate current user turn. After prior and current turns are concatenated, an older identical-text pair plus a newer reply-less user turn is treated as replay; the later current occurrence is discarded because grouping ignores message identity/origin. A user intentionally repeating the same text is therefore absent from that extraction. Preserve the current call's occurrence (or use message IDs/origin to distinguish replay) and add a prior-pair/current-repeat regression. -
A remember flow overwrites the accumulated rolling window. When
rememberPrependedTurnsis non-empty, prior pairs are intentionally not prepended for that call, butautoCaptureRecentPairTurns.set(sessionKey, finalConversationTurns)still replaces the stored window with the remember-only transcript. This silently drops older retained pairs, especially whenautoCaptureContextTurnsexceeds the small referent window. Keep the accumulated window independent from the protected-prefix extraction transcript and add a remember-flow retention regression.
Non-blocking lifecycle cleanup: autoCaptureRecentPairTurns is not cleared by the terminal session-end sweep, and autoCaptureSessionIds is currently dead singleton state.
|
All three blockers plus the lifecycle note addressed in 12f289f:
All three regressions are red-proofed against the pre-fix sources; full local suite green. |
rwmjhb
left a comment
There was a problem hiding this comment.
Thanks for addressing the previous three blockers and lifecycle cleanup. The new regressions confirm those exact cases, and the full suite, build/dist parity, and current CI are green. On head 12f289f, deeper review found two remaining source/isolation blockers plus related state-safety gaps:
-
Retained user turns are still ordinary extraction sources, not context-only.
index.tskeeps current-onlyconversationText, but passesfinalConversationTurnscontainingpriorPairTurnsintoextractAndPersist. SmartExtractor prefers those turns when rendering the transcript, and every retained user is emitted as a normal<user_message>; the extraction prompt permits candidates from every such block. The watermark controls which texts initiate a run, not what the LLM may extract. Old facts can therefore be extracted repeatedly, consume the five-candidate limit, and crowd out the current turn. Please carry source eligibility per turn, render retained user/assistant blocks explicitly as context-only, and add an end-to-end assertion that retained-only facts cannot independently produce candidates. -
The rolling window is keyed only by
sessionKey, not by agent. Literal keys such asglobalcan be shared across agents, and missing identity falls back to the shared stringunknown. The adjacent remember window already usesrememberWindowKey(agentId, sessionKey)for this reason. With retention enabled, one agent's transcript can enter another agent's extraction and become durable memory in the receiving scope. Key this state by agent plus session, disable retention for unattributable keys, and add shared-globaland missing-session isolation regressions. -
Assistant context can consume the transcript budget before the current user source. Assistant replies are woven after their anchors, and ordinary runs keep the newest rendered tail. A trailing assistant block larger than
extractMaxCharscan consume the entire budget and omit the preceding current user turn. Reserve budget for current source turns first; truncate/drop retained or context-only blocks before them, with an oversized-reply integration test. -
Terminal cleanup still has alias/race gaps. The early sweep uses only
ctx.sessionKey, while the later flush resolvessessionIdaliases; it can also return on an empty flush before the second deletion. An in-flight capture may repopulate the map after early cleanup. Resolve the same logical key, await in-flight runs, then delete before any no-work return; cover sessionId-only and in-flight reset cases.
Additional hardening worth covering in the same state machine: overlapping captures can overwrite newer windows, same-current-call repeated text can still be deleted, and non-adjacent identical exchanges evade pair deduplication.
|
Round 2 addressed in 85e7e49:
On the two remaining hardening notes: same-call repeated text is deduplicated by design (identical text redelivered within one payload is host replay, the origin-aware rule only protects cross-boundary repeats), and non-adjacent identical exchanges are found by the full backward scan; happy to extend either if you see a concrete failing shape. Full local suite green including the new regressions. |
…PR 966, composed with master's reconcile-with-kept-texts and protected-prefix contracts)
…re pair dedup, remember-safe window Review round: assistant replies join the transcript and rolling window as context even with captureAssistant off (source eligibility unchanged, prompt rules already restrict sources to user blocks in that mode); dedupePairWindow keeps a current-call repeat of a prior pair instead of deleting the newest input; a remember flow updates the accumulated window from its own turns rather than overwriting it with the remember-shaped transcript; terminal and session-end sweeps clear the pair window; dead autoCaptureSessionIds singleton state removed.
…, source-first budget, race-safe teardown Round 2: retained window turns and captureAssistant-off replies render as context_only blocks the extraction prompt forbids sourcing candidates from; the rolling window is keyed by agent plus session (retention disabled for unattributable ids) so shared literal keys cannot bleed transcripts across agents or scopes; the transcript budget serves source turns before context blocks so an oversized reply cannot evict the current user turn; session teardown resolves the sessionId alias, sweeps synchronously, and re-sweeps after in-flight runs settle with an epoch guard that voids straggler stores.
85e7e49 to
eda0765
Compare
rwmjhb
left a comment
There was a problem hiding this comment.
Reviewed head eda0765. The focused tests, full suite, and current CI are green, and the previous isolation fixes move this in the right direction. Four correctness issues remain:
-
A repeated current-call user turn can be silently lost. In
dedupePairWindow, the reply-less duplicate after a paired duplicate is retained only when the older pair came from the prior window. For a same-call sequence such as userdeploy it(id 1), assistant reply, userdeploy it(id 3), the helper returns only ids 1 and 2.finalConversationTurnstherefore omits the newest source turn, while the watermark still advances through the eligible text count, so that occurrence is never offered to a later extraction. Please preserve the newest/current source occurrence and add this exact same-call regression. -
autoCapturePairWindowEpochgrows without bound under the default disabled configuration. ThecontextTurns === 0path creates or increments an entry for every agent/session, but pruning exists only inside thecontextTurns > 0store path and session teardown never deletes epoch entries. Avoid allocating this state while the feature is disabled, delete it during teardown, and keep epoch/window eviction coupled so pruning cannot reset the generation while a retained window survives. -
Context-only source isolation is prompt-only and the grounding pass removes the distinction.
SmartExtractordoes not validate source provenance, whilebuildGroundingRejudgePromptconvertscontext_only_*tags back to ordinary speaker tags. A candidate grounded solely in retained context can therefore pass the second judge despite violating the extraction contract. Preserve enforceable provenance through validation or otherwise reject context-only-only candidates, with an end-to-end regression. -
A null-anchored assistant context block can disable remember-referent budget protection.
weaveContextOnlyAssistantTurnsinserts that block at index 0 before the strict protected-prefix scan. Because the assistant is not inreferentTurnSet,protectedPrefixTurnsbecomes 0 and an over-budgetremember thatflow can evict the actual referent. Compute protection independently of the woven context position (or preserve explicit protected indices) and cover the leading-assistant case.
Requesting changes on this head.
… hygiene, rejudge provenance, context-transparent protected prefix 1. dedupePairWindow keeps a genuinely repeated CURRENT-call user turn (distinct messageId) instead of collapsing it into the earlier same-call pair; a literal same-turn echo still collapses. 2. Pair-window epoch state: no allocation under the disabled default (terminal boundary and cleanup paths gate or has()-guard their bumps), session teardown deletes epoch entries once in-flight runs settle (final sweep pass), and epoch eviction is coupled to window eviction so pruning can never reset a generation whose window survives. 3. buildGroundingRejudgePrompt keeps the context_only wrappers and carries the context-is-never-a-source rule, so the rescue pass enforces the same source-isolation contract as the first pass. 4. The protected referent prefix counts SOURCE turns only, at both the scan (countProtectedReferentPrefix) and the budget split, so a null-anchored woven context reply cannot void the remember-referent guarantee. Regressions for all four in test/pair-window-retention.test.mjs; the stale normalize-away assertions in test/grounding-rejudge.test.mjs updated to pin the preserved-tags contract. debugAutoCaptureWindowStateSizes exported for the epoch-hygiene assertions.
|
Round 3 addressed on head 510c29f, all four accepted:
Full suite and manifest verifier green; focused file 16/16. |
rwmjhb
left a comment
There was a problem hiding this comment.
Re-reviewed head 510c29f. The four issues from the previous round are addressed:
- a distinct same-call repeated user turn now survives as a source occurrence, with the exact user/assistant/user regression;
- the disabled default no longer allocates epoch state, teardown removes entries after in-flight work settles, and window/epoch eviction is coupled;
- context-only tags and the non-source rule now survive into the grounding rejudge instead of being normalized away;
- protected referent budgeting counts source turns, so a leading woven assistant context block cannot void the remember prefix.
The focused tests, full suite, and current GitHub CI pass. A fresh TypeScript compile also matches the committed dist output byte-for-byte. Approving this head.
Non-blocking follow-ups: the epoch read and guarded store are currently in one synchronous block, so the guard cannot observe an intervening mutation and the concurrency machinery can likely be simplified; avoid making debugAutoCaptureWindowStateSizes permanent public API; and gate terminal epoch allocation for an explicit unknown agent. Context-only exclusion remains model-enforced rather than deterministically source-attributed, so that residual behavior should stay covered and be revisited if the extraction schema gains source IDs.
Problem
In steady state (history-carrying sessions extract every turn) each extraction transcript contains only that call's own turns. The extractor never has the surrounding conversation, so it cannot resolve references ("yes exactly, that one", "make it the second option") and either drops the fact or stores a hollow one.
Change
A rolling pair window retains conversational context across extractions:
autoCaptureContextTurns(new knob): 0 (the default) disables retention entirely and preserves stock behavior byte-for-byte; 1 to 10 sets the window size, decoupled from theextractMinMessageswarm-up gate.trimTurnsToUserCap, which never trims this call's own unextracted turns out of their transcript and never leaves an orphan assistant turn ahead of the window's first user turn.dedupePairWindowrepairs double-included pairs: a below-threshold deferral keeps content alive on two paths (the rolling buffer and the watermark rollback or ingress re-queue), and the repair collapses duplicate exchanges at pair granularity while keeping legitimately repeated user messages whose replies differ.Tests
test/pair-window-retention.test.mjs(full-pipeline harness over a mock LLM and embedding server): retention across successful extractions, window bounding, knob-0 wipe, absent-knob default. Unit coverage fortrimTurnsToUserCapanddedupePairWindowintest/auto-capture-cleanup.test.mjs; manifest schema checks pin the 0 default. Registered in the test chain and CI manifest.Stacked on #965; the diff includes its predecessors until they merge.