Repository navigation
fix(framework)!: a stop ends its AgentFramework instance, and start() and addModule() are refused from then on (#315) - #881
Merged
nissa-seru merged 4 commits intoOct 29, 2026
Conversation
added 2 commits
October 28, 2026 18:39
…use once a stop() is admitted (anima-research#315) A start() after a settled stop() began a lifecycle that didn't work: the event queue stayed closed, so pushEvent() threw and a tool call's result couldn't be delivered; operator operations and MCPL connects were refused as "the framework is stopping"; the push coalescer, the watchdog and the MCPL servers stayed stopped; and events the queue held at the stop ran in it. The same held after a failed stop(), on the same closed queue. Now a stop ends its instance. From the moment a valid stop() is admitted, start() throws and addModule() rejects, for good. While that stop runs the text stays "<verb> refused: the framework is stopping". Once it has settled, one family of texts says what to do: after a completed stop, create a new AgentFramework on its store (its storePath, if it opened the store itself); after a failed one, call stop() again, since only a stop() that resolves has finished its cleanup and final persistence; after a failed one that left its owned store closed, so that stop() can't be tried again, the error carries that failure as its cause. They replace "owned store is closed" and "closed its context managers, and then failed to close its owned store". addModule() refuses before its prefix checks, so a stopped framework's addModule() always says it has stopped. stop() keeps its cleanup and retry semantics; a stop() whose budget is refused is not admitted and changes nothing. What existed only for a later lifecycle on the same instance goes, each confirmed by reading: - resetCompletedStop()'s clearing of a completed stop: a completed stop() keeps its settled promise, so stop() stays idempotent for good. - ownedStoreManagersClosed, and closeOpenManagers()'s boolean: read only by the refusal it fed. That case is now a failed stop() that can be retried: its store is open and its attempt cleared. - providerAdmissionClosed = false in start(): the field starts false and only stop() sets it, so it is false at every start() that now runs. - The lifecycle generation: with start() refused after a stop, it changed only in stop(), after running is cleared and in the same synchronous run that closes provider admission, with only the retry-wait wakers between, whose frames resume in later microtasks. So the event loop's check added nothing to running, and each turn check's generation half held only when its admission half did. The one difference: a turn that began before the first start(), through runUntilIdle(), was dropped by that start() as "stopped and started again", and now goes on. The "stopped and started again" drop texts go with it. - The withheld-spill gate, closed for the generation a stop ended, is now a flag set at a stop's drain and cleared where each stop() began a new generation, so a retried stop() reopens it from its start until its own drain, as it did. - start() no longer calls the event gate's resumeSleep(): at a first start it only re-armed a timer already armed, since only dispose(), which only stop() calls, stops a sleep's timer, and setSleep() arms it when it is set. A sleep already due whose timer hasn't yet run is now woken by that timer, a moment later, rather than by start(). Kept: the server-address loop in start() (it makes this framework's entries the ones in force where another framework configured the same id since, which a test now pins), heartbeat.start(), openLogical.clear() at stop (a stream a failed stop left running settles against it), the fork unbind at stop (a host reading the router sees it), and the event gate's disposed mark. Text that described a restart of the same instance now says what is true. Tests: stop-ends-its-instance covers the refusals after a completed, a failed, a timed-out, a retried and an unretriable stop (addModule()'s with a name a server's prefix holds too), an invalid budget, a new instance on the stopped one's store running a tool call, and a turn runUntilIdle() began before the first start() reaching the model when start() comes. A retried stop's spills and start()'s address precedence each get a test, and the in-flight refusal from stop()'s first steps covers addModule(). The starts after a stop elsewhere in the suite each keep their property within one stop where they have one, move to a new instance where it holds there, or go where it arose only on reuse.
…e restart a stop no longer allows (anima-research#315) resumeSleep()'s documented purpose was to arm a disposed gate's timers again for a lifecycle that starts after a stop(): the framework's start() called it. A stop now ends its instance, start() no longer calls it, and at a first start it only re-armed a deadline already armed, or woke a sleep already due a moment before its timer would. No host we know of calls it. It's a public method of an exported class, so this is a breaking removal of its own, in a commit that can be dropped to keep the method. With it go the reset of the gate's disposed mark, which only it made, so a disposed gate arms no sleep or self-wake timer for good, and its tests: the two that drove a wake through it, the one for a sleep already over when it ran, and the halves of two others that armed a timer after it. wakeFromSleep() is now called only by the timer that fired.
added 2 commits
October 28, 2026 22:01
Catches anima-research#315's change up with 0.30.0. The one conflict is in start(), where main added `this.earlyTraces = null;` (traces from before start() are replayed to late listeners and dropped at start()) inside the lines this change removes (the provider-admission reopening, the lifecycle generation bump and their comments). The resolution keeps main's line and drops the rest, as the change does on its own.
Once a retried stop() resolves, a store the framework opened itself is as closed as after a completed stop. The failed stop's refusal said only that a new AgentFramework may then use its store, so a host following it straight to a new instance could hand that instance the closed store object. It now says, as the completed stop's refusal does, to use the storePath when this framework opened the store itself.
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 29, 2026
main 0f11975 brings anima-research#864, anima-research#876, anima-research#881 and the other merges since 62a126c. One file conflicted, test/workspace-three-way.test.ts: - its observe.js import takes both sides: this branch's RACY_WINDOW_MS and anima-research#864's ROOT_ON_ANOTHER_DEVICE, ROOT_REPLACED, filesystemKeepsBirthTime and fsTypeKeepsBirthTime; - anima-research#864's new test, "a filesystem keeps a birth time fixed only where it is listed, and only on Linux", goes in before "no restored timestamp hides a change", without the comment above that test, which this branch removed.
nissa-seru
pushed a commit
to rufalupa666-netizen/agent-framework
that referenced
this pull request
Oct 29, 2026
main took ten pull requests after 0.30.0 (anima-research#866, anima-research#864, anima-research#873, anima-research#874, anima-research#877, anima-research#872, anima-research#881, anima-research#875, anima-research#378, anima-research#876). Two places conflicted, and both keep both sides: - src/gate/event-gate.ts: this branch's sleepTurnId field, with main's rewritten note on dispose(), since anima-research#881 removed resumeSleep(). This branch never called resumeSleep(). - test/turn-trigger-provenance.test.ts: this branch's imports. main added an import of MembraneError, which this file already imports, with StreamEvent and YieldingStream, from the same package.
This was referenced Oct 29, 2026
tel-0s
added a commit
that referenced
this pull request
Oct 29, 2026
…instance, inference bounds, the subconscious's own window and binding main at 4a45766 is 11 merges past this branch's last (630e2a3, the 0.30.0 release), with no release among them, so package.json stays 0.30.0 and this branch's floors stand (chronicle ^0.6.0, context-manager ^0.25.0, membrane ^0.8.0), which test/dependency-ranges pins. It brings #881 (a stop ends its AgentFramework instance, #315: start() and addModule() refused once a stop() is admitted; lifecycleGeneration and EventGate.resumeSleep() gone), #378 (inferenceBounds: a model call that stops making progress is cut, and a deadline cut gets one fresh call; the [inference-failed] marker names a timeout and a call made again), #869 (subconscious.physicalWindowTokens and thinkingBinding, for the persistent reader and each reader fork), #876 and #875 (mcpl-down alerts for a server lost for good; stop() ends reconnects), #877 and #874 (a merged start taken back is merged again from its wakes; a start parked behind an auxiliary call waits out a provider hold), #873 and #872 (an outside sender's name marks notice openings; supplied text on one line at six more places), #864 (a workspace mount's root in status) and #866 (discord.js loaded at the first client). Six paths conflicted (8 regions): - src/framework.ts (3): - stop()'s comment on ending each live fork: main's text. Its clause on a start() after this stop() routing to a gone name goes with #315, which refuses that start(). - stop()'s final pass over deferred writes: this branch's reportHeldWritesAtStop stands (no deferral queue, #291). Main changed only that block's comment (a start() refused once a stop() is admitted). - an ephemeral run's disposal: both lines, this branch's forgetPresentation and main's deadlineCuts.delete. - test/reinit-after-branch-move (1): main's form (a new framework on the stopped one's store, #315), with this branch's presentAtEndOf on the first framework's template, so the row written past the framework is in the template's window and the next framework's fork is seeded with it. - test/route-failure-targeting (1): imports: this branch's audienceOf, without JsStore, whose one user (the start-after-stop test) main deleted. - test/stop-with-host-operations (1): the resume-past-its-commit-point test stays deleted, as this branch deleted it: it held the resume's flushDeferredWrites, which went with the deferral queue. Main's change to it was its connection mock's stopReconnecting. - test/stop-with-work-in-flight (1): imports: ContentBlock alone. Main dropped JsStore with its start-after-stop test, and StreamEvent's one user (the write deferred behind a failed retry's entry) is deleted on this branch. - test/turn-continuation (1): main's emptiedBy ('drop' | 'withdrawal') in Internals; deferredMessages stays out, as on this branch. Read onto this branch past the conflicts: - the [inference-failed] marker's new words (a timeout, the call made again, TIMEOUT_ADVICE) merged into this branch's storeOwnRow write: an own row, presented as it is stored. - deadlineCuts.delete is in releaseAgentRuntime, where main put it, beside forgetPresentation, and in the two name-keyed releases main also put it in (an ephemeral run's disposal, disposeConversationAgent with nothing live under the name), as thinkingLossIncidents.forget is. - the subconscious's AgentConfig takes readerModelFields after the primary's inherited fields and before this branch's selfParticipants and presentAs overrides; a reader fork's derive config takes them too. storeSees: no new site. Main opens no context manager: a reader fork's own window and binding ride the derive, whose view is recorded as before, and the subconscious's config goes through createSubconsciousAgent's open. agentSees: no new view decision. Notice tags: main's new and changed agent texts use NOTICE_TAG (inferenceFailed, script, workspaceEvent, conflictsWithDisk); its mcpl-down text is an ops alert, and its [coalescing] and [provider-admission] lines go to stderr. Teardown: deadlineCuts in releaseAgentRuntime (above). Changelog: no release in main's range, so no fragment was consumed; main's 15 new fragments come in as written, and none holds a presentation-side sentence to carry. Tests: built with zero errors; 1 stale compiled helper removed (test/helpers/quiesce-crash-child, whose source went with the deferral queue); fixtures copied. main's 44 changed test files, the five conflicted among them: 2736 tests, 2711 pass, 0 fail, 25 skipped (workspace watcher and root cases that need Linux). presentation*, window-*, dendrite-*, stored-injections, audience-stamp*, dependency-ranges, held-* and tune-out-* (44 files): 605 of 605. Co-Authored-By: Claude Opus 5.5 <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.
After this change,
start()throws andaddModule()rejects once astop()is admitted, for good, whether that stop completes or fails, so a stop ends itsAgentFrameworkinstance.Before, a
start()after a settledstop()could begin a second lifecycle on the same instance, and that lifecycle didn't work (#315). @rergards supported ending the instance instead, as an explicit breaking change (comment on #315), and this is that change, with a breaking changelog entry. A host that wants to go on after a stop creates a newAgentFrameworkon the store. After a failed stop, it first callsstop()again, until a call resolves, unless that stop left its owned store closed, so thatstop()can't be tried again. The refusal's text says which applies. The second commit,a02f169c69, removesEventGate.resumeSleep(), whose documented purpose was that restart, as a second breaking change. On Linux with Node 24.22.1, the 15 test files this change touches pass at its head,c3cb848994. Ata02f169c69, beforemainat 0.30.0 was merged in, 9 of their tests failed on the source ofe85c57db9a, the base then.Problem
At
e85c57db9a,start()andaddModule()are refused while astop()runs (#532). They're also refused after a stop that closed a store the framework owns, or that closed its context managers and then failed to close that store. In every other case, once astop()has settled,start()begins a new lifecycle on the same instance. 0.22.0's changelog says so ("Astart()after a finishedstop()starts a new lifecycle"), and 0.24.0's leaves what it should do as #315's open question.Our runs at that commit show that this lifecycle doesn't work. After a completed
stop()on a store the framework doesn't own, and astart():pushEvent()of an external message or a module's event throwsQueue is closed.maintain refused: the framework is stopping.Turns woken outside the queue did reach the model, in our runs or the base's tests: a sleep's wake, a heartbeat's, a budget restart left pending and a nudge. The nudge's turn then failed at its model's call of a module's tool, with
Queue is closed. After a failedstop(),start()is allowed too, and its lifecycle has the same closed queue.!Closes #315.
Changes
Line numbers are in
src/framework.tsata02f169c69unless another file is named.The refusal.
start()(:4763) andaddModule()(:6913) each first callrefuseOnceStopped(:4877), which reads the instance's stop state (stopState,:3287). Astop()sets that state when it's admitted, and nothing sets it back to'none'. Astop()whose budget argument is refused rejects with aRangeError(:4853) and isn't admitted, so it changes nothing. The refusal's text depends on that stop:start() refused: the framework is stopping, as before;start() refused: this framework has stopped, and a stopped framework doesn't start again. Create a new AgentFramework on its store to go on, with its storePath if it opened the store itself.;stop()may have left its cleanup or final persistence unfinished, and to callstop()again, since only once astop()of this instance resolves should a newAgentFrameworkuse its store, with itsstorePathif this one opened the store itself, or another instance use anything this one shares;stop()can't be tried again, with that stop's failure as the error'scause.addModule()'s texts are the same, with its own name. They replaceCannot start a new lifecycle: owned store is closedandCannot start a new lifecycle: stop() closed its context managers, and then failed to close its owned store.addModule()refuses before it checks the module's name against MCPL servers' tool prefixes (:6914), so a stopped framework'saddModule()says it has stopped even for a name a server's prefix holds.stop()keeps its cleanup and its retry. Its doc (:4801) now says it ends the instance.Removed, since each existed only for a later lifecycle on the same instance:
resetCompletedStop(), whichrefuseOnceStoppedreplaces, so a completedstop()keeps its settled promise;ownedStoreManagersClosedflag andcloseOpenManagers()'s return value, which only fed one of the two refusalsrefuseOnceStoppedreplaces. That case is now refused as after any failedstop();start()'s reopening of provider admission;runUntilIdle()began before the firststart(), and that's still being assembled when thatstart()comes, now goes on, where before it was dropped;start()'s call toEventGate.resumeSleep().The withheld-spill gate was closed for the lifecycle generation a stop ended. It's now a flag (
guardSpillsClosed,:7779) thatstop()clears as it begins (:5028) and sets at its drain (:5296), so astop()tried again after a failed one reopens it until its own drain.Kept:
start()'s loop that puts its MCPL servers' addresses in force (:4773), since it makes this framework's entries the ones in force where another framework has configured the same server id since.start()'s doc (:4742) says what a new instance on the store keeps: what the store holds, and, with the heartbeat on and the same heartbeat state file, the heartbeat's schedule, so a tick or a reminder that fell due meanwhile fires once, shortly after it starts. It says what's lost with the old instance, as on a process restart: an agent's sleep and its wake, a self-wake, the inference requests still queued, a re-armed turn's wait and count, and the redo stack. It names Awake_in_secondsself-wake and a sleep don't survive a restart #627, which is about keeping a sleep and a self-wake across a restart, andquiesce()andresume()for pausing one framework in place.Docs and comments.
docs/tool-result-guard.md's paragraph on stop now says that a stopped framework doesn't start again. The comments that described a restart of the same instance no longer do.The changelog entry,
changelog.d/315-stop-ends-its-instance.breaking.md, names who needs to act and how to migrate, what a new instance keeps and loses, and what's unchanged.EventGate.resumeSleep()removed, ina02f169c69resumeSleep()'s doc gave its purpose as arming a disposed gate's timers again "for a lifecycle that starts after a stop()". Ate85c57db9athe framework'sstart()called it, and with this change nothing insrcdoes. It's a public method of an exported class, so removing it is a breaking change of its own, with its own changelog line. The reset of the gate's disposed mark, which only it made, goes with it, so nothing arms a disposed gate's sleep or self-wake timer again. Its tests go too. We found no call of it on themainbranches of connectome-host (0db863c), connectome-smoke (14b7c55) and antra-tess/chapterx, fetched on Oct 28.Since
a02f169c69e20be12455mergesmainat 0.30.0 (630e2a3a07), which includes feat(dendrite): one registry, lifecycle and fork primitive for agents; admission, reader forks, the all-except scope and speech.locus (#232, #77, #270) #546. One hunk conflicted, instart(), where feat(dendrite): one registry, lifecycle and fork primitive for agents; admission, reader forks, the all-except scope and speech.locus (#232, #77, #270) #546 addedthis.earlyTraces = null;among lines this change removes. The merge keeps that line and removes the others.git diff 630e2a3a07 e20be12455changes the same lines asgit diff e85c57db9a a02f169c69.c3cb848994adds "with its storePath if this one opened the store itself" to the failed stop's refusal. The completed stop's refusal already namedstorePath.Who needs to act
start()oraddModule()afterstop()creates a newAgentFrameworkon the store instead, with the same borrowedstoreor the samestorePath. After a failedstop(), it callsstop()again first, until a call resolves. Where the failed stop left its owned store closed,stop()can't be tried again, and the refusal carries that stop's failure as itscause.EventGate.resumeSleep()creates a newAgentFramework, whose gate is its own.With other open pull requests
By
git merge-treeon Oct 29, this head containsmainat 0.30.0 (630e2a3a07). Of the open pull requests that merge clean onto630e2a3a07, one conflicts with this head: #844, which has its own section below. Of those that already conflict with630e2a3a07, this head adds conflicting regions to four:0fdee5bc0c) and fix: a turn whose compile is refused for budget waits for compression, then runs #361 (5e41150d0a), from one region to two insrc/framework.ts;b2f6b4adbf), one region insrc/gate/event-gate.ts;e91cfb6852), from three regions to four insrc/framework.ts, and from four to seven insrc/gate/event-gate.ts.With #844
@tel-0s, by
git merge-treeon Oct 29, #844 (219a23706e), a draft, conflicts with this head in four files:src/framework.ts, in two comments instopOwned()that this change rewords. feat(presentation): one record, a presentation index per agent — the deferral queue replaced (#291 step 3) #844 rewords the first too, and removes the second with the code after it.test/reinit-after-branch-move.test.ts, in a test where this change replaces starting the stopped framework again with a newAgentFrameworkon its store. feat(presentation): one record, a presentation index per agent — the deferral queue replaced (#291 step 3) #844 adds apresentAtEndOf()call there.test/route-failure-targeting.test.tsandtest/stop-with-work-in-flight.test.ts, in their imports.Tests
test/stop-ends-its-instance.test.ts, new, with eight tests:stop(),start()andaddModule()are refused, andstop()stays settled without tearing down again;stop()of a framework never started ends it too, with an owned store and with a borrowed one;stop(), both are refused through each retry, until one resolves, and then for good;stop()whose wait on the event loop timed out,start()is refused, and the retry waits for that loop before it resolves;stop()that closed its owned store, the refusal carries that stop's failure, whichstop()rejects with again;stop()whose budget is refused leaves the lifecycle as it was, so a running framework still adds modules, and one never started starts;AgentFrameworkon a stopped one's borrowed store takes an event and runs a tool call;runUntilIdle()began before the firststart(), held in a module'sgatherContext()until after thatstart(), reaches the model.The first four also check
addModule()with a module whose name a server's tool prefix holds.test/start-during-stop.test.ts: a call made instop()'s first synchronous steps is refused foraddModule()as well asstart().test/tool-result-guard.test.ts: astop()tried again lets withheld spills write until its own drain.test/tool-result-blocks.test.ts:start()puts its servers' addresses in force over another framework's for the same id.The restart's working cases, as tests at the base pinned them:
test/event-gate-sleep.test.ts, "a framework stopped while its agent sleeps wakes nothing until it's started again, then wakes the agent" now checks only that it wakes nothing once stopped, and "a sleep that ended while stopped wakes the agent in one turn with a request already pending at start" is removed.test/stop-with-work-in-flight.test.ts, "a budget restart taken up after a stop() and a start() marks its agent inferring again" is removed.test/shutdown-errors.test.ts's "borrowed-store lifecycle changes invalidate a successful stop memo" (ea7e0435) is removed. After a completedstop(), it added a module, stopped the framework, started it and stopped it again. The new file's first test checks that a completedstop()stays settled.The other tests that started a stopped instance again now check their property within one stop, or on a new
AgentFrameworkon the stopped one's store. Those that tested what a restart itself did, by running one or by advancing the lifecycle generation as one did, are removed, intest/start-during-stop.test.ts,test/stop-without-stream.test.ts,test/present-while-acting.test.ts,test/tool-result-guard.test.tsandtest/turn-continuation.test.ts.a02f169c69removesresumeSleep()'s three tests intest/event-gate-sleep.test.ts, and the halves of two others that armed a timer after it.On Linux with Node 24.22.1, npm 11.19.1 and TypeScript 7.0.2, each tree installed with
npm install, which resolved chronicle 0.4.0, context-manager 0.23.1, mcpl-core 0.4.0, membrane 0.8.0,@modelcontextprotocol/clientand@modelcontextprotocol/server2.4.0, discord.js 14.27.0, ws 8.22.0 and chokidar 4.0.3:a02f169c69, in a copy of a clean checkout, the build exits 0, and the 15 test files this change touches pass 840 of 840.e85c57db9a'ssrcanda02f169c69's test files, the build exits 0, and 9 of the 840 tests fail. They're the new file's five tests of the refusal and its test of the turnrunUntilIdle()began, and one test each intest/reinit-after-branch-move.test.ts,test/compression-after-release.test.tsandtest/shutdown-errors.test.tswhose expectations this change updates.On Linux with Node 24.22.1, npm 11.19.1 and TypeScript 7.0.2, after
mainat 0.30.0 was merged in,npm installresolved chronicle 0.5.0, context-manager 0.24.0, mcpl-core 0.4.0, membrane 0.8.0,@modelcontextprotocol/clientand@modelcontextprotocol/server2.4.0, discord.js 14.27.0, ws 8.22.0 and chokidar 4.0.3. With those:c3cb848994, in a copy of a clean checkout, the build exits 0, and the 15 test files this change touches pass 846 of 846.mainadded 6 of those tests.e20be12455, in a copy of a clean checkout, the build exits 0, and those 15 files pass 846 of 846. So do 16 more test files, 475 of 475: those thatmainadded or changed sincee85c57db9aand in which a call of.stop()comes before a call of.start(), found by a text match.c3cb848994: withe20be12455'ssrcandc3cb848994'stest/stop-ends-its-instance.test.ts, the build exits 0, and 2 of that file's 8 tests fail, the two that expect the failed stop's refusal.What a new instance keeps, in runs of probes outside this pull request, each in a copy of a clean checkout, at
a02f169c69and again atc3cb848994, with the same outcomes:sleep:expired. With a nudge for the agent before the new framework's start, it made one model call, the nudge's.Not verified
e20be12455the 16 more above.start()'s list we ran nothing, and have only what lines of the code ata02f169c69say:src/gate/event-gate.ts:2135declaresselfWakeDueas a field of the event gate,src/framework.ts:3316andsrc/framework.ts:3713declarependingRequestsandredoStacksas fields of the framework, andsrc/types/framework.ts:259says a re-armed turn's wait and count "are kept in memory".What this leaves
addModule()made before astop()can still be running its module'sstart()when thatstop()runs.stop()doesn't wait for thatstart(). It stops the module, removes it from the registry and resolves, and once thestart()finishes, the framework emitsmodule:addedfor it andaddModule()resolves. Our internal review of this pull request found it, and this pull request doesn't change it.changelog.d/315-stop-ends-its-instance.breaking.md.Gideon-2613 made the change and ran its probes. Cordelia-2434 proposed ending the instance, and Ada-1017 made the design calls. Ezra-2423 weighed what each part is worth, and read the fixes that followed until the last test was added. Stanley-2495 read that test, and Humphrey-2407 the comment fix after it. Stanley-2495 also read the merge of
mainat 0.30.0, and Ezra-2423 the clause onstorePath. Oscar-1808 set its scope. Laurel-2386 wrote this description.