Repository navigation
Description
Nothing in a session joins all the writes that happen after a turn ends. Tests that measure "nothing changed after this turn" can see a late write, and quit or teardown have the same gap: there's no single point after which a finished turn will write nothing more. Low priority. Today this shows up as a rare test flake (TestAppIdleReleaseI1I2OpenViewKeepsItsFence, about 1 in 200 under load), when the setup turn's pace record is captured and published after the test starts counting.
Writers that can run after a turn has ended (app/v3 at 2c78de1):
- Deferred turn-journal writes.
internal/session/loop.go:971defersjournalTurnPace(appendPace,sessionfile.go:3277-3281), andloop.go:936/954defersjournalMarkRead. They run aftersealTurn(loop.go:1835) has settled and announcedTurnEnded(loop.go:1855-1856). - Naming.
intentname.go:257-260registerstitleJobs,:288-290writesintent_name, and the second-chance drain is at:305-307,:408and:420, started fromagent.go:2222.title.go:186-198and:380cover ordinary naming and publishing the title. - Memory and knowledge.
loop.go:1365startslearnFromTurnafterEventTurnDone.memory.go:867-893runs undermemoryJobs. The standing scanner is atknowledge.go:129/251, and job compilation is atloop.go:348-352andknowledge_usage.go:60/96/133. - Turn cleanup writes.
agent.go:2156-2283hands back tasks, drains the queue and retires questions, and queues team traffic after closingdone(:2235).- Team traffic is written at
teamevent.go:92/126, joined at:184. placemeta.go:126-138SettleWritesjoins six metadata writers.- Provider receipt accounting is tracked separately at
usage_pending.go:31.
- Capture scheduling.
sessionfile.go:3449-3450onWriteandloop.go:1863-1865Tail.Wrotefeed the trailing timer atinternal/cellstore/sealwatch.go:176-189, which runsOnTailafter a quiet period (:205-213). That can start a new capture aftercellstore.WaitCapturehas returned.
Why each existing signal falls short:
EventTurnDone(loop.go:1355) andTurnEndedfire before naming, memory, pace and mark-read.Agent.WaitIdlewaits ondone, which closes atagent.go:2235without joiningtitleJobsormemoryJobs, and before team cleanup.- Turn stream closure (
agent.go:2155,remote/server.go:1635-1651) covers synchronous defers but nottitleJobs,memoryJobsor the trailing seal timer. Agent.SettleWritescovers its six owners only, not turn completion, naming or memory.waitForTitle(title.go:455) is a shutdown join with a timeout.WaitReceiptscovers only accounting already admitted.cellstore.WaitCapture(pipeline.go:77-94) joins one existing capture and doesn't prevent a later one.- SealWatch quiet is time-based, not writer completion.
OnTurnEndruns before pace. CloseandStopWorkcancel the work instead of letting it finish.
Proposed seam (not implemented): WaitPostTurnWrites(ctx, turn) in internal/session, next to startTurnLocked, reachable from tests through the test transport, since the agent runs in another process. It would, in order:
- join the turn goroutine through its last cleanup;
- join that turn's
titleJobsandmemoryJobs, plus any admitted receipt or knowledge work that writes to the journal; - drain
SettleWrites; - flush SealWatch's pending tail through
OnTail, so every capture has been admitted.
Only after that are WaitCapture and a peer-head comparison a sound "turn fully settled" check. The same join would give quit and teardown a definite end to a turn's writes. A wait for a single record kind, for idle, or for a quiet timer is not enough, because the next post-turn writer reopens the gap.
Found while fixing the idle-release tests (#2467). Low priority.
🤖 Generated with Claude Code
Activity