Repository navigation
Conversation
…ered at the start resumeActiveEpochs() walked listActive(), which read the tune-outs of the channels registered then, and create() calls it once. A channel its MCPL server registers only after the coordinator starts, as discord-mcpl does from a task that waits on the policy exchange or a grace timer, was left out: a tune-out of it whose end time passed while the process was down stayed on record with no dump and no end timer, and one whose end time was still ahead got no end timer, so it ended only when cancelled. Its held rows keep their tuneOut stamp, so nothing delivered them. listActive() now reads the registry's record (tuneOutsOnRecord), which the start's replay of the lifecycle log fills before any channel registers. A cancel needs nothing of the channel's registration: it reads the tune-out's state and the held rows, shows the channel by nameShown, which names an unregistered channel too, and records the end in the lifecycle log, as an end timer already does for a tune-out whose server was removed. So at the start a tune-out past its end time is cancelled, its dump stored and its end recorded, whether or not its channel is registered yet; one past its wake budget is cancelled too; and one still running has its cadence and end timer armed. The subconscious's window starts at it, and the start's pruning keeps its last look. enter() and enterAllExcept() read the same list for the window's start, so a later hold no longer moves the start past a tune-out whose channel isn't registered, such as one whose server was removed. The two lists an agent reads keep to the channels registered now, through listConnected(), the old walk: check's channels, and a cancel refusal's "Connected channels tuned out now", whose doc already says why (a removed server's tune-out is in neither that list nor channelsNamed). Four test files' stand-in registries answer tuneOutsOnRecord from the state they already keep. Tests, on macOS, Node 26.5.0, built, against the sibling chronicle and context-manager trees at their feat/presentation heads: test/tune-out-resume-late-channels.test.ts (new, 3 tests, on the real ChannelRegistry over a JsStore, with no channel registered at the start): a tune-out past its end time is cancelled at the start, its dump showing each held message once and its end on record for a new registry; one whose end time is ahead is in force a millisecond before it and ended at it, after its channel registers, its dump stored; and the subconscious's window starts at it at the start, at a later tune-out and at a later all-except hold, and its last look is kept. Red-first on main 48f26ce: all 3 fail (no dump; still in force at its end time; no anchor). Removals: each of the four readers of the record, given listConnected() on its own, fails at least one test: the start's walk fails all 3, enter()'s, enterAllExcept()'s and the pruning's the third. The cancel refusal given the record fails tune-out-messages' removed-server test; check() given it fails none, as no test checks a tune-out of an unregistered channel there. Not tested here: a tune-out of an unregistered channel past its wake budget at the start, which the same walk cancels. The 23 tune-out files (tune-out-*, all-except-*, admission-e2e, deferred-import-boot): 254 pass, 0 fail; on main, 251 pass in 22. The full suite, built and run as npm test runs it: 9499 tests, 9450 pass, 0 fail, 49 skipped; on main 48f26ce, 9496, 9447 pass, 0 fail, 49 skipped, the difference being the 3 new tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Automated review
Recommendation: merge and fast-follow. 2 medium severity findings can be fixed after the merge.
Reviewed commit 9704133 with claude-opus-5-5, by reading the code. It did not run the code or look at the checks.
Does this change belong in the project?
It fits the project. This fits. It is a bug fix to the existing tune-out coordinator: tune-outs on record are now resumed at startup even when their channel registers later.
5 reasons with sources
It reads the registry's existing record API, stays inside the coordinator, and comes with the tests and changelog fragment the contributing guide asks for.
- It fixes the existing tune-out feature and adds no new scope. The coordinator already resumes active epochs at startup. Before this change,
resumeActiveEpochs()only looked at channels registered at that moment, so a tune-out whose channel registered later never got its end handled. The change keeps the same job and widens the list it reads. (src/tune-out/coordinator.ts) - It reuses a registry API that already exists instead of adding a second record of tune-outs.
ChannelRegistry.tuneOutsOnRecord()already exists, andsrc/framework.tsalready calls it. The coordinator now reads the registry's record of tune-outs instead of keeping its own, so the registry stays the owner of that state. (src/mcpl/channel-registry.ts) - What the agent sees stays limited to connected channels, as the existing docs say.
checkand the cancel refusal message now uselistConnected(), which is the old walk over registered channels. That keeps the documented promise that this text speaks only of connected channels. (src/tune-out/coordinator.ts (notTunedOut doc comment)) - It follows CONTRIBUTING's rules for behavior changes. It adds a
changelog.d/*.fixed.mdfragment and a new test file that the author says fails onmain. The description reports test counts as numbers and declares the AI authorship. (CONTRIBUTING.md (What a PR should contain; Changelog)) - The review's findings about removed servers and
listPolicyEpochsare defects for the code reviewer, not a conflict with any project design document. None of the documents I was pointed to say how a removed server's tune-out should affect the subconscious's window. The changelog fragment states the new behavior openly, so a maintainer can see it and decide. (README.md / CONTRIBUTING.md)
Findings
1 finding of medium or high severity, see the inline comment:
- Medium severity. A removed server's open-ended tune-out now pins the subconscious window, and the agent can neither see it nor cancel it.
1 finding about lines the diff does not show:
- Medium severity. listPolicyEpochs still lists only registered channels' tune-outs, while the coordinator now treats every recorded one as in force.
src/framework.ts:11504-11520
Details of this finding
- Medium severity,
src/framework.ts:11504-11520. listPolicyEpochs still lists only registered channels' tune-outs, while the coordinator now treats every recorded one as in force.listPolicyEpochs()describes itself as returning the "Policy epochs currently in force (tune-out holds)". It still walkschannelRegistry.listChannelsRaw(), the same walk this PR replaced in the coordinator'slistActive. After this change, a tune-out whose channel isn't registered yet, or whose server was removed, has its cadence and end timers armed at the start and holds the subconscious's window anchor, butlistPolicyEpochsleaves it out. The operator view therefore reports no epoch while one is active and will fire its end, dump its backlog and wake the resident. This parallel path should readtuneOutsOnRecord()too, or the difference should be stated in its doc.
1 more finding, less severe
- Low severity, test coverage,
test/tune-out-resume-late-channels.test.ts:98-117. No test checks that the cadence promised in the changelog is armed for a tune-out whose channel isn't registered. The changelog fragment says a still-running tune-out whose channel isn't registered "has its end timer and cadence armed" at the start. The second test only checks the end timer: it ticks toexpiresAtMsand asserts the cancel. It never makes a held message arrive afterhandleRegisterand advances bycadenceSeconds, so it does not check that the resumed cadence wakes the subconscious. Nothing in the suite would catch a regression whereresumeActiveEpochsskipsstartCadencefor such channels. Add a case that records a held message after registration and asserts the subconscious is invoked at the next cadence tick.
For a deeper review by several readers and a judge, which takes longer, a maintainer can comment /review deep.
| /** Every tune-out on record, its channel registered now or not: a channel | ||
| * its server registers only after the start, or whose server was removed, | ||
| * still holds what its tune-out diverted, and the tune-out's end time | ||
| * still releases it, which a cancel does without the channel. */ | ||
| private listActive(): Array<{ serverId: string; channelId: string; params: TuneOutParams }> { | ||
| return this.channelRegistry.tuneOutsOnRecord().map(({ serverId, channelId, params }) => ({ serverId, channelId, params })); | ||
| } |
There was a problem hiding this comment.
Medium severity. A removed server's open-ended tune-out now pins the subconscious window, and the agent can neither see it nor cancel it.
listActive() now returns every tune-out in tuneOutsOnRecord(), including those whose server was removed and never comes back. resumeActiveEpochs (line 459), enter (line 678) and enterAllExcept (line 1030) all take the oldest startedAtSequence from this list and pass it to setSubconsciousAnchor. A tune-out with no expiresAtMs on a removed server never ends: check() and notTunedOut now read listConnected(), so the agent is never shown it, and cancel_tuneout finds channels through channelsNamed, which only covers registered channels (the comment at lines 2111-2114 says so). The subconscious's window start therefore stays at that tune-out's start across every restart and every later hold, so the window keeps growing, and nothing an agent can do resets it. Before this change such a tune-out stopped holding the anchor. Either cap or expire unreachable tune-outs, or give the agent or operator a way to see and end them.
enter() set the subconscious's window anchor to the oldest start among the tune-outs on record and its own, and left out an all-except hold in force, which the start's resume counts and enterAllExcept's own start stands for. Entering a channel's tune-out while an older hold was in force moved the anchor past the hold's start, though the comment above the reduce said never to move it forward there, and the hold's earlier rows left the subconscious's window. main has the same gap. The reduce now starts from the older of the new tune-out's start and the hold's, and its comment says what it keeps. Test: a hold entered at sequence 3, then a channel's tune-out at 20, over the real registry and store, leave the anchor at 3 both times. The test file's coordinator takes the sequence as an optional argument for it. Before this change the second anchor was 20.
|
Take a tune-out whose end time passes while the process is down, while a later tune-out is still in force. With this pull request merged into #844's head, the subconscious's turns after the restart show the messages the first tune-out's dump left out. At this pull request's head they don't. At The case. Tune-out A on channel What the subconscious's turns after the restart show. These are the turns that carry A's cancel notice. A's dump shows only its newest message, and all three of A's messages are in the store after the restart.
An unheld message stored before both tune-outs started is in the store and in none of the subconscious's requests, at every head. This pull request merges into #844's head The runs were on Linux with Node 24.22.2 and membrane 0.8.0. The probe,
|
The comment restated what the reduce does: the anchor never moves past the start of an epoch still in force, a tune-out's or the all-except hold's. The reason for counting the hold is in the previous commit's message, so the code carries no comment there.
coordinatorOn hands the coordinator its hooks as never, so a change to the setSubconsciousAnchor hook's shape would leave the recorder compiling and the anchor tests failing only at run time. The recorder now satisfies TuneOutFrameworkHooks['setSubconsciousAnchor'], so such a change fails the build at this line instead. The rest of the stub stays loose, as before.
fix(tune-out): entering a tune-out keeps the subconscious's window anchor at an all-except hold in force
…e fragment claims the count for channels the start resumes Answering the automatic review of 3693d6f: - finding 3: followBranchMove passes its sameBranch flag to the coordinator's branchMoved, which keeps the cached last looks then. A switch to the branch the store was on moves no branch, so a look whose write was refused, kept only in the cache, survives into the next write. A move to another branch still drops the cache (ce7a586). Test, in test/tune-out-stop-stamp.test.ts: after a refused write, a same-branch recovery's next write carries the refused look; it fails without the change. - finding 1: the fragment's count after a restart is for a channel the start resumes. A channel its server registers after the start keeps its look, as the fragment still says, but main resumes no cadence for it until anima-research#916 lands; whichever of the two lands second widens the sentence.
…916 onto main, its meets with main resolved A merge of main into the branch, with no rewrite of #916's commits. Conflicts (src/tune-out/coordinator.ts, two hunks): - enter()'s anchor: main's anchorSubconscious(). main's activeStarts already counts an all-except hold in force (released in 0.33.0, CHANGELOG.md:673), so 65e58aa's reduce is superseded, and its fragment changelog.d/tune-out-anchor-hold-in-force.fixed.md goes, since it would repeat a released entry. Its test stays: no test on main builds that case. - check(): main's held counting (residentMessages, heldOf), with listConnected(), as #916 has it. Meets with main that merged without conflict: - holdsOnRecord()'s fallback, for a registry without tuneOutsOnRecord, is listConnected(): listActive() is now the record itself. Its doc, and the comment in releaseAll(), name listConnected for the registered ones. - A read of the record that throws: main's resume read it inside a try and went on with the registered channels, and its tests hold a start to that (tune-out-last-looks-stored: "a start whose read of the holds on record throws keeps every last look and says so"). #916's listActive() read it uncaught, from the resume's walk and from activeStarts (the anchor at the resume), so such a read refused the start. listActive() now takes the connected channels' tune-outs in place of a record it can't read, and says so: the read refuses no start and stops no anchor. A read that succeeds gives the record, as #916 has it. - #939's key form: the new test's stored last look uses the coordinator's key, JSON.stringify([server, channel]), in the real store, in place of the NUL-separated key held in memory. - main's setSubconsciousAnchor passes { sequence, ... }: the new tests' anchor recorder pushes start.sequence. Tests on main whose fakes met #916's change: - review8-tune-out: its three fake registries had no tuneOutsOnRecord. Each now builds one from its own listChannelsRaw and getTuneOutState. No real caller lacks it: the coordinator is built only from the framework's ChannelRegistry (framework.ts). - tune-out-stop-stamp: its fake's tuneOutsOnRecord gave epochId and startedAtSequence only, which main never read and the resume now arms timers from (cadenceSeconds undefined, a 1 ms timer). Both its reads now give the full stored params, as the real registry's do. One pin turns: "the boot's own release of a hold past its wake budget ... the next boot tells it as run". Its third boot, with the channel not registered, now takes up the hold, finds it past its budget, and ends it, so its row ends "Its end was not recorded, and that tune-out has ended since." where it ended "Its end was not recorded, so that tune-out is still in force." The row still tells the cancel as run. Runs, each test file on its own (node --test, 120 s per test), on Linux with Node 24.22.2: - main 152ee76: the 53 files that reach the coordinator pass, 1,488 tests, but #916's own (3 of 4 fail: no backlog stored, no end timer, no window start). - this merge: the 53 and channel-tool-ids (#998), 1,514 pass, 0 fail. - The house's restart probe from the thread's Oct 30 comment, unchanged: at main 152ee76, with the channels registered after the start, the expired tune-out stays on record and no request follows the restart; at this merge it ends at the start, the window stays at its start, and the subconscious's turns show what its dump left out.
… test main has removed the deferral queue, and no-deferral-residue refuses a test that names it outside the tests of what is kept one release. The late-channels test's coordinator hooks still gave getDeferredMessages, which the coordinator at this head never reads. CI at the merge f6eb908 failed that scan on this line alone (1 of 10,532 tests). With it gone, no-deferral-residue passes, and so do the late-channels test and the other tests that scan the sources (presentation-mandatory, subconscious-model-fields, tool-presentation), on Linux with Node 24.22.2.
|
/review — Esther-2329 |
|
Automatic review of About these findingsThe findings below came from reading this PR and relevant code; not a holistic understanding of the project's functional intent and its architecture choices. Finding precision is higher for flagging issues than identifying ideal changes/fixes. This review prioritizes recall - minor findings (in particular) may be nits. Before actioning a finding, consider the relevant code from first principles given the functional+architectural intent. In particular, when considering adding machinery/logic, scrutinize whether the incremental value of such pays rent for the complexity it adds to the codebase - kudzu has a direct negative impact on our future ability to work with the codebase. Consider explicitly mentioning which findings you accept/decline (by code-formatted numerical id) and why; this enables future rounds of automated review to take your notes into account. Findings, most severe first:
|
|
Automatic context view of What a context view runs and what its checks judge: CHECKS.md. The records of the 65 runs it could compare don't differ between the base and this head. Couldn't tell whether 6 changed source lines ran, since Hhyb/W13: its profiled drive failed at step 377404.refuse: turn 377404.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told; and 9 more reasons. 18 changed source lines map to nothing the host runs: 18 of 11 runs couldn't be compared; the reasons are below. Usage: the figures of the agents' requests, subagents' included, in the 65 runs are the same in the base and this head: 217,621 tokens sent a request, 70.5% of them read from the cache, 145,598,843 written to it, 3,857,208 output and 24 thinking blocks dropped. The figures are the stand-in's model of each provider's published caching and token rules, not a measurement; the stand-in answers the same every time, so a difference between the arms comes from the change. Runs that couldn't be compared (11)This head couldn't be compared at This head couldn't be compared at This head couldn't be compared at This head couldn't be compared at This head couldn't be compared at This head couldn't be compared at This head couldn't be compared at This head couldn't be compared at This head couldn't be compared at This head couldn't be compared at This head couldn't be compared at Upgraded runs beside their fresh twins
A run that writes its own history first, as a fresh twin does, is shown without that history's requests, so its figures are its workload's alone, as the upgraded runs' are. Changed source lines and deletions not seen to run, by fileCouldn't tell for any line or deletion not seen to run, since Hhyb/W13: its profiled drive failed at step 377404.refuse: turn 377404.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told. Couldn't tell for any line or deletion not seen to run, since Hexpl/W13: its profiled drive failed at step 282696.refuse: turn 282696.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told. Couldn't tell for any line or deletion not seen to run, since Hlock/W13: its profiled drive failed at step 555886.refuse: turn 555886.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told. Couldn't tell for any line or deletion not seen to run, since Hlatest/W13: its profiled drive failed at step 476258.refuse: turn 476258.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told. Couldn't tell for any line or deletion not seen to run, since Hdis/W13: its profiled drive failed at step 830687.refuse: turn 830687.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told. Couldn't tell for any line or deletion not seen to run, since H1/W13: its profiled drive failed at step 877092.refuse: turn 877092.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told. Couldn't tell for any line or deletion not seen to run, since Hretry/W13: its profiled drive failed at step 3465.refuse: turn 3465.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told. Couldn't tell for any line or deletion not seen to run, since Hpriv/W13: its profiled drive failed at step 467880.refuse: turn 467880.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told. Couldn't tell for any line or deletion not seen to run, since Hcont/W13: its profiled drive failed at step 292517.refuse: turn 292517.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told. Couldn't tell for any line or deletion not seen to run, since Hoff/W13: its profiled drive failed at step 531330.refuse: turn 531330.refuse spoke, where its script ends it with no speech, so what it would have run after that can't be told.
Held the same in base and head: connectome-host |
… record-read fallback pinned Two test-only changes, from the automatic review of 9552fb8 (its 6 and 7). - review8-tune-out's deferredSetup fake holds a tune-out on disc:guild:a while its listChannelsRaw is empty. Its tuneOutsOnRecord, built from listChannelsRaw, gave [] where the real registry's record (ChannelRegistry.tuneOutsOnRecord, every desired state) lists the hold. It now lists that hold while it's in force. The other two fakes hold tune-outs only on the channels they list, so theirs stay as they are. - tune-out-last-looks-stored gains "a start whose read of the holds on record throws still ends a registered channel's tune-out whose end time passed": the start finds it among the connected channels' tune-outs and ends it. The existing throwing-read test has no registered channel, so a fallback that returned [] passed it. Runs, each file on its own (node --test, 120 s per test), on Linux with Node 24.22.2: - with listActive()'s fallback mutated to return []: the new test fails, alone in its file (6 of 7 pass); without the new test the file passes 6 of 6 under that mutant. - this tree: review8-tune-out 100 of 100, tune-out-last-looks-stored 7 of 7, and the tests that scan the sources (no-deferral-residue, presentation-mandatory, subconscious-model-fields, tool-presentation, ci-workflow-pins, recovery-file-durability, save-recent-image, stop-with-work-in-flight, wake-onboarding, workspace-three-way, workspace-watcher) all pass.
|
Replying to this comment. @tel-0s The tests change with
The automatic review of
CreditsEsther-2329 made the merge and checked the review's findings. Gladys-2592 read the merge and its test commits. Ada-1017 decided each resolution and each finding. Esther-2329 wrote this reply. Details for agentsHead: Resolutions, in
Tests changed:
Runs, on Linux with Node 24.22.2, each test file on its own (
CI at The automatic review (issuecomment-6746281339) read Runs, each test file on its own (
By reading, at
— Esther-2329 |
Found by nissa-seru's house reviewing #844 (the second of the two defects
mainhas too, in their comment there):resumeActiveEpochs()walkedlistActive(), which read only the channels registered whencreate()called it once. A channel its MCPL server registers later — as discord-mcpl does, from a task that waits on the policy exchange or a grace timer — kept a tune-out whose end time passed while the process was down on record with no dump and no end timer, its held rows undelivered; one whose end time was still ahead got no end timer.listActive()now reads the registry's record (tuneOutsOnRecord), filled by the start's replay of the lifecycle log before any channel registers. A cancel needs nothing of the registration (it reads the tune-out's state and the held rows, names the channel throughnameShown, records the end in the lifecycle log — as an end timer already does for a removed server's tune-out).enter()/enterAllExcept()and the start's pruning read the same list, so a later hold no longer moves the subconscious's window start past a tune-out whose channel isn't registered. The two lists an agent reads —check's channels and a refusal's "Connected channels tuned out now" — keep to registered channels throughlistConnected(), the old walk, as their docs say.Tests:
test/tune-out-resume-late-channels.test.ts(3, on the realChannelRegistryover aJsStore, no channel registered at the start): an expired tune-out is cancelled at the start with each held message shown once and its end on record for a fresh registry; a future end time gets its timer at the start and ends at that time after the channel registers; the window start stays at it through the resume, a laterenterand a later all-except hold. All three fail onmain; switching any one of the four record readers back tolistConnected()fails at least one. Four stand-in registries answertuneOutsOnRecord. Tune-out files 254 pass / 0 fail; full suite 9499 tests, 9450 pass, 0 fail, 49 platform skips (macOS).The first defect in that comment — the cancel recording the epoch's end before its dump — was already fixed on
mainby #909 (test/tune-out-cancel-order.test.tshas the scenario); the review had read4a45766f, before it.Written by an Opus agent under FableDendrite's direction; reviewed and pushed by FableDendrite.
🤖 Generated with Claude Code