fix(session): scope write tracking by session, and stop wiping live sessions - #71
Open
liveoakwag wants to merge 1 commit into
Open
fix(session): scope write tracking by session, and stop wiping live sessions#71liveoakwag wants to merge 1 commit into
liveoakwag wants to merge 1 commit into
Conversation
…essions
`.wolf/hooks/_session.json` is one file per project, shared by every concurrent
session. Two consequences, and the second is why the first was never visible.
1. `files_written` records carry no owner, so every "Session end: N writes across
M files" row in memory.md reports the UNION of all live sessions. The same
applies to the cerebrum-freshness and semantic-summary thresholds, the
STATUS-freshness check and the token ledger - each asks "what did THIS session
do" and was answered with everyone's data. `edit_counts` is keyed by path
alone, so the "edited 3+ times" reminder names files the current session never
opened.
2. `session-start` resets `files_written` and `edit_counts` whenever a
non-continuing session starts - which, with several agents open, is whenever
anyone starts one. That deletes the history of every session already running.
Scoping alone would have been cosmetic: the records would be attributed
correctly and then thrown away before anything read them.
post-write now stamps each record with the harness `session_id` and namespaces
the edit_counts key with it. stop scopes once, at the top, and passes the result
down - deliberately NOT assigning it back onto `session`, because that object is
written back to the shared file and narrowing it in place would delete the other
sessions' records, i.e. cause the very thing being fixed.
session-start carries other sessions' records across the reset. The bound on
growth is deliberately generous on both axes, because a bound whose job is to cap
a file must not become a way to lose live data: 48h (a session running longer is
implausible; 12h is not) and a count ceiling far above any real concurrent load,
applied newest-first. edit_counts keys have no timestamp, so their liveness is
derived from whether their session still has a carried write.
Records predating this carry no `sid` and are ignored rather than shared out -
attributing an unowned record to every session is precisely the defect, and the
population is self-clearing once post-write stamps them.
tests/session-write-scoping.test.ts drives the real hooks as child processes,
since none of this reproduces inside a single process. Reverting the three
sources and rebuilding makes all four cases fail.
node --test tests/session-write-scoping.test.ts 4/4
npm test 30/30
npx tsc --noEmit clean except the pre-existing
src/daemon/cron-engine.ts
TS2503, present on an
unmodified checkout too
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
.wolf/hooks/_session.jsonis one file per project, shared by every concurrent session. Two consequences, and the second is why the first was never visible.1. Records carry no owner. Every "Session end: N writes across M files" row in memory.md reports the union of all live sessions. The same applies to the cerebrum-freshness and semantic-summary thresholds, the STATUS-freshness check, and the token ledger — each asks "what did this session do" and was answered with everyone's data.
edit_countsis keyed by path alone, so the "edited 3+ times" reminder names files the current session never opened.2.
session-startresetsfiles_writtenandedit_countswhenever a non-continuing session starts — which, with several agents open, is whenever anyone starts one. That deletes the history of every session already running. Scoping alone would have been cosmetic: the records would be attributed correctly and then thrown away before anything read them.The change
post-writestamps each record with the harnesssession_idand namespaces theedit_countskey with it.stopscopes once at the top and passes the result down — deliberately not assigning it back ontosession, because that object is written back to the shared file, so narrowing it in place would delete the other sessions' records and cause the very thing being fixed.session-startcarries other sessions' records across the reset. The bound on growth is deliberately generous on both axes, because a bound whose job is to cap a file must not become a way to lose live data: 48h (a session running longer is implausible; 12h is not) and a count ceiling far above any real concurrent load, newest-first.edit_countskeys have no timestamp, so liveness is derived from whether their session still has a carried write.Records predating this carry no
sidand are ignored rather than shared out — attributing an unowned record to every session is precisely the defect, and the population is self-clearing oncepost-writestamps them.Verification
node --test tests/session-write-scoping.test.ts— 4/4npm test— 30/30, 0 failnpx tsc --noEmit— clean apart from the pre-existingsrc/daemon/cron-engine.ts(52,27) TS2503git amonto pristinemain(f64e737) applies cleanly, suite still 30/30The test drives the real hooks as child processes — none of this reproduces inside a single process, since the whole defect is about what several processes sharing one file do to each other. Like #70 it loads from
dist/and builds on demand, because the hooks import each other with relative.jsspecifiers that type-stripping does not rewrite; same open question for you there.Independent of #66, #69 and #70 — cut from
main, depends on none of them.🤖 Generated with Claude Code