fix(smem-hygiene): default the Stop hook's session-summary fallback to off - #221
Merged
acidkill merged 1 commit intoSep 7, 2026
Conversation
…o off _extract_session_summary() does not summarise -- it joins the last ~10 non-trivial transcript lines verbatim behind a "Session activity: " prefix. On prose that reads fine; on a real agent transcript it emits harness markers and half-sentences. Measured on the production brain over 24 h: 96 of 158 write-gate rejections (61 %) originated in this one path, e.g. "Session activity: <task-notification>" and "Session activity: Writing objects:". The gate discarded all of them, so nothing corrupt was ever stored -- but the whole week's yield from the hook was 8 accepted entries, every one truncated mid-sentence, six of them ownerless TODOs that came back as session-start context days later. The fallback fires precisely when pattern extraction found nothing, so it is a noise generator conditioned on failure. Gate it behind auto.capture_session_summary, defaulting to false; the write gate already rejects what it produces, so the only thing lost is telemetry noise. Not deleted, because the acidkill#80 idempotency regression suite pins exactly this re-encode path -- that suite now opts in explicitly rather than relying on a default. Its cross-process race test writes the flag into its own config.toml, since the in-process opt-in cannot reach a subprocess. New tests pin both directions plus config round-trip and on-disk emission: a default that nothing asserts is a default that drifts back. (cherry picked from commit 9a4e6ed) The comment on the new field and the test's module docstring are corrected at the same time. Both said the quality gate already discards this output, which is only true where the gate is enabled: WriteGateConfig defaults to enabled=False and mode="off", so on a stock installation nothing rejects it and it is stored as a CONTEXT memory. That is the case this default actually addresses, and saying the opposite two lines from the flag would have been the worse kind of wrong. The same comment also named PreCompact, which has no such fallback.
acidkill
approved these changes
Sep 7, 2026
acidkill
left a comment
Owner
There was a problem hiding this comment.
Reviewed the full diff. The measured 61%-of-rejections number justifies the default flip; both directions are pinned, the idempotency suite opts back in explicitly, and the subprocess racer gets config.toml rather than an in-process toggle. Note for the changelog: default behaviour change, opt-out is auto.capture_session_summary=true.
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.
Summary
auto.capture_session_summary, defaulting tofalse.Why
When pattern extraction finds nothing in a transcript, the Stop hook manufactures a candidate from the last few lines and offers that to the write gate. On a real chat transcript those lines are harness markers and half-sentences, not knowledge — so the gate rejects them, and the work of extracting, encoding and judging them is repeated on every turn that produces no patterns.
The repository already says as much in two places, which is what makes this a default worth changing rather than a preference:
quality_scorer.py's_NOISE_PREFIXESopens with the literal"session activity:", above a comment describing it as an "Empirical denylist from gate_decision false-accepts (2026-07-23): 180x "Session activity:", 30xcapture_state.py's module docstring calls duplicate"Session activity"entries "the dominant source of memory poisoning".Measured on one production brain over twenty-four hours: 96 of 158 write-gate rejections, a little over three in five, originated in this path alone — entries like
Session activity: <task-notification>. That number is from a single brain and I would not lean on it alone; it agrees with the direction of the two notes above, which are the project's own.Changes
unified_config.py: the newauto.capture_session_summaryfield, defaulting tofalse, with the reason recorded beside it.hooks/stop.py: the fallback runs only when the flag is set.tests/unit/test_stop_hook_session_summary.py(new) andtests/unit/test_hook_capture_idempotency.py, which turns the fallback back on explicitly.docs/reference/config.md: regenerated withscripts/gen_config_docs.py, since that file is generated from these defaults and theDocs Freshnessjob checks it.What this does not change
The gate itself.
WriteGateConfigdefaults toenabled: Falseandmode: "off", so on a stock installation the fallback's output is not rejected — it is stored, as aCONTEXTmemory. That is the case this default actually addresses, and it is the common one rather than the exception, which is worth being explicit about: the argument here is not "the gate wastes effort" but "on the defaults, nothing is stopping this".Setting
capture_session_summary = truerestores the previous behaviour exactly. The #80 idempotency regression tests do that deliberately, since the defect they pin lives on this path and needs it switched on to be observable at all.Not touched, on purpose
The extraction helper keeps its name. Renaming it to say "tail" rather than "summary" would describe it better, and it has a single call site, so the rename would be small — but a rename is a different change from a default flip and belongs in its own commit rather than riding along.
Test plan
pytest tests/unit/test_stop_hook_session_summary.py— 6 passed: the default is off, an absent key loads as off, an explicittruesurvives the config round trip,saveemits the key, and the hook stores nothing for pattern-free text with the flag off and does store with it on.pytest tests/unit/test_hook_capture_idempotency.py— 12 passed. Those tests turn the fallback back on explicitly, because the Auto-capture: no idempotency key (Stop/PreCompact re-capture loop) + PreCompact/task_context hooks still bypass dedup #80 defect they exist to pin is only reachable through it.hooks/stop.pyandunified_config.pyreverted tomainand the tests kept, 5 of the 6 new tests fail.pytest tests/ -m "not stress" -n 4— 7289 passed, 48 skipped, 1 xfailed, which ismain's 7283 plus the six tests added here. Two tests intests/unit/test_dashboard_brains_scope.pyfail on this branch and onmainalike: they want a live database and collide with one another under-n. Both pass when that file is run on its own.ruff check src/ tests/clean;ruff format --check src/ tests/reports 740 files already formatted.mypy src/ --ignore-missing-imports— success, no issues found in 354 source files.main.CHANGELOG.mduntouched — left to the release entry, as with fix(storage): bind datetimes in time comparisons so they select by value #191–fix(memory): refresh content-derived fields on compress, restore, and refine #193.Verified by
@RobertSigmundsson