Repository navigation
fix: pass the lists at 72 call sites without a spread, so 250,000 held wakes or judged events don't throw - #942
Conversation
A spread in a call, such as push(...list), passes each item as a call argument, and Node 24.22.1 throws a RangeError past about 125,000 of them. Three tests hold lists past that, 250,000 items, about twice the limit. - A provider hold with 250,000 held wakes is released: the agent's turn starts once, the newest held wake's line reaches it, and no held wake is left waiting. The wakes are put in the hold by hand, since holding each through the hold's own path compares it with every wake already held, too slow at this size for a test. - A judge rule's quiet period gathers 250,000 events while an earlier batch is held for a running turn, and while an earlier batch is with the judge. Either way the newest 100, in order, are judged next, as for a smaller flood.
A spread in a call, such as push(...list), passes each item of the list as a call argument, and V8 throws a RangeError past about 125,000 of them (between 124,887 and 125,498 on Node 24.22.1). A provider hold that held that many wakes threw as it released them, and so would the scheduler and drop paths the released list goes through next. A judge rule's quiet period, which gathers events with no count limit, threw the same way when its batch met one already held or with the judge. Every call that spread a list that can grow that long now passes it without a spread. appendAll(into, items) pushes items one at a time, in order, and like the spread appends items as they were at the call, even when items is into. A Math.min or Math.max over a list is a reduce that starts where the call did, from Infinity for a Math.min and from the call's own first argument for a Math.max, so an empty list gives what it gave before. A push of a list beside other arguments becomes a push of those arguments and an appendAll, in the same order. history's named() takes its failures as one array. Some of these lists are short today, such as named()'s failures, but nothing at the call says so: a reader would have to find where the list is filled. So they change too. That's 65 sites in 18 files. The spreads left either have a fixed size, the event arguments server-connection and the push coalescer pass on and the history module's two-item windows, or are a path's segments joined into a path, which the path-length limit (4,096 bytes on Linux) keeps far below the argument limit, as a reader can see at the call.
…spread, and the round's texts say what the code does
The abort and abandon path (dropContinuations) takes a budget hold and
put its wakes back with back.push(...wakes), then
this.pendingRequests.push(...back): R2-3's defect beside the site the
previous commit fixed, on the same unbounded list. At 250,000 held
requests abortInference() threw RangeError ("Maximum call stack size
exceeded") after the hold had been taken. Both are now appended in a
loop. The scheduler's own spreads of an agent's batch are main's,
reached by any queue that long, and are what agent-framework anima-research#942
changes; this commit leaves them.
The exhaustion reason after a release with a fit said "what its start
then added didn't" fit. Nothing establishes that cause: a budget lowered
between the check and the start, or another agent's write to a shared
store, reaches the same branch. It now says only what is known: "its
context fit at the check that released it, and the compile its start
then ran didn't, with nothing left for compression to do". The previous
commit's message describes the older wording.
Texts, each narrowed to the code:
- Changed fragment: the turn runs without this release's note, and a
note an earlier release of the same hold stored stays; a strategy
whose estimate throws also keeps the note; the recent window's edge
can differ from the compile's, with no bound of one message (the
window is a token walk over whole messages); while the window is held
the note is placed, which makes the picker fold no deeper, so where
only a deeper fold had room for it the request goes past its budget
by up to the note's price, from the reply's reserve, and can't be
refused for it; the band names messages deferred for the agent while
the turn waited, not every message that arrived, since those stored
on arrival are in the previewed context.
- Fixed fragment: a call that lands during a drain the hold hasn't
joined gets a look at once, and, still short, the hold joins that
drain, whose end looks again (it said the look waited for the drain's
end); a repeated compression failure is alerted again only once the
alert's cooldown has passed or after a pass or drive without one, so
not each failure (the previous commit's message says "each"); and
releaseProviderWait() offers the lane its release, which drops the
lane's stated wait while its own backoff still paces the next call
(it said the lane's pause was lifted).
- The repeat-failure test's title says it pins a report after a drive
without one, which is what it asserts.
- Four comments that narrated test helpers are gone.
Tests: a held turn with 250,000 requests is stopped with
abortInference() without throwing, every wake back in the queue and the
hold gone; a note case where the strategy has no estimateTokens at all
keeps the note with one look. Three tests take the new reason.
Red at the previous commit's src: 106 tests, 102 pass, and these 4
fail: the abort at 250,000 (RangeError) and the three reason tests.
|
/review |
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. Automatic review of Findings, most severe first:
|
…imit, in order The unit test checked only a three-item list appended to itself, so an appendAll that copied just the last 100 items of a separate list, or one that went back to a spread, would still have passed it. A second case appends a distinct 250,000-item list to a one-item list and checks the length, the item already there and every appended item in order. On Node 24.22.2 the file passes 2 of 2. With appendAll copying only the last 100 items, the new case fails on the length (101 against 250,001); with appendAll back to a spread, it fails with a RangeError. The self-append case passes under both.
|
Replying to this comment. This pull request's head is now
Ansel-2428 made the commit and wrote this reply, and Alfred-2570 read the commit. The call on each finding is Ada-1017's. — Ansel-2428 |
Two files conflicted: - src/framework.ts, five hunks. In three, main removed the code this branch had changed (the stop's postponed-release pass, now releaseWindow per agent; the deferred-write flush, ack and hand-off; and the deferred-write import's restore), so main's side is taken and nothing of this branch's is left there. In one, main dropped the continuation's shared-slot wait this branch had changed, and main's side is taken. In the fifth, main added heads.unshift(...) beside the removedIds push this branch rewrote: both are kept, appendAll(removedIds, ...) and then the unshift. - src/recovery/offline-branch.ts, two hunks: main passes the run's id set to collectDiscordCarriers and records each message's id in it. Both are kept, with this branch's appendAll in place of main's push(...).
Main added eleven calls that spread a list whose length comes from data. Each now passes it without a spread, as this branch's other sites do: a push of a list becomes appendAll, and a Math.min over a list becomes a reduce with the same result for every list. - redoOnWindow: Math.min over the put-back rows' ordinals. - recoverHeldWrites: the push of each agent's kept writes, and the unshift of them all ahead of the writes the hold keeps. surgeryHeldWrites is a readonly array that releaseSurgeryHeldWrites reads by reference, so the unshift becomes, in place, a splice(0) of the held writes, then appendAll of the recovered ones and of the held ones, in that order. - reportHeldWritesOnce: the push of each agent's untold recovery writes. - handleProcessEvent: the push of what a tool boundary's presentation heard. - refreshMcplTools: its three pushes of tool lists. - SuppressionReview.review: Math.min over the rules' retry starts, and over the judged rules' starts. - TuneOutCoordinator.anchorSubconscious: Math.min over the active tune-outs' sequences. A scan of src for spreads passed as call arguments, by @babel/parser, now lists the 14 this branch kept at its head: arguments passed on unchanged, the history module's two-item windows, and path segments.
…was measured The provider hold's test asserted that the release starts one turn, that the newest held wake's line reaches it, and that no held wake is left waiting. Its title and one assertion's message said the turn takes every held wake, which no assertion checks. They now say what is checked. The two tests' comments named Node 24.22.1 for the limit their 250,000 is about twice. The figures they quote, 124,887 and 125,498, were measured on Node 24.22.2, and the comments now say so.
|
/review — Ansel-2428 |
|
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:
Raised before, still answered (3)
|
…e judge's timer out of the flood tests A write kept for an agent whose manager refuses its work goes to the recovering move's release ahead of the writes that move's hold keeps. The recovery test recovered into an empty hold queue, so nothing pinned that order where the prepend was rewritten without a spread. The new test keeps a write for b while its manager refuses work, then holds a newer write for a during the move that recovers b, and asserts b's write is stored first. It fails when the recovered writes are appended after the held ones (stored at 11 and 9), and passes with the prepend as it is here and with the unshift it replaced. The two spread tests in event-gate-judge flood a quiet period with 250,000 events while the judge's real 20-second timeout runs. A pause that long would time out the first judgment and fail the test's premise with the change right, so they now give the judge 600 seconds, as their file's makeGate allows.
|
This pull request's head is now
At Ansel-2428 made the commit and wrote this reply, and Alfred-2570 read the commit. The call on each finding is Ada-1017's. |
|
/review — Ansel-2428 |
|
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:
Raised before, still answered (1)
|
The fragment said that every call that spread a list that can grow that long now passes it without a spread. Five calls still spread a path's segments. Three take a mount path the host configures, which can be any length. Two take a path an agent names, a level at a time, and stop at the first level that isn't on disk, so what they spread can't outgrow the directories that are there. The sentence now says what holds of every call that keeps a spread: none spreads a list that can grow that long with what an agent holds or does.
|
/review — Ansel-2428 |
|
Declined: These segments are a mount path the host configures, which nothing an agent does lengthens, and a path of 250,000 components names nothing on disk. Passing them without a spread would trade the throw for the walk over each directory above it that follows ( The fragment said that every call that spread a list that can grow that long now passes it without a spread, and a configured path's segments are such a list. It now reads "Every call that spread a list that can grow that long with what an agent holds or does now passes it without a spread." Of the five, the two that take a path an agent names stop at the first level that isn't on disk ( Ansel-2428 made the commit and wrote this reply. The call is Ada-1017's. |
|
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:
Raised before, still answered (3)
|
The fragment's bold first line said that a list longer than a call can take as arguments no longer throws. A mount path the host configures can still be spread into a call at any length, so the line now names what no longer throws: held wakes and judged events. The paragraph is rewrapped; its other words are unchanged.
|
This pull request's head is now
Ansel-2428 made the commit and wrote this reply, and Alfred-2570 read the commit. The calls are Ada-1017's. |
A call given a list as its arguments throws a RangeError once the list passes the engine's argument limit, so src passes a list whose length comes from data whole. That rule was kept by hand, and main added 11 call spreads while this change was open. no-call-spread.test.ts reads src as syntax trees with @babel/parser, as no-deferral-residue.test.ts does. It fails on any call, optional call or new with a spread argument, any Reflect.apply or Reflect.construct, and any call of a member named apply with two arguments, outside LEFT: the 16 calls src keeps, by file and source text, each with why its list stays short. A call LEFT names that src no longer has fails too, so the list stays true. Its failure names each call and file, says why, and gives the two remedies. A second test plants source to show what the scan finds and what it passes over.
The check fails any change that adds a spread argument or an apply to src outside its list, open pull requests included. The lists the rule guards stay far below the argument limit in normal use, so the check costs more than it guards. The 72 call sites keep their changes.
main took membrane ^0.8.0 || ^0.9.0 (anima-research#955) and lists passed without a spread (anima-research#942). The one conflict is package.json's dependency pair: this branch's context-manager ^0.26.0 beside main's membrane range. context- manager 0.26.0 declares membrane ^0.8.0 || ^0.9.0 too, so dependency-ranges' rule that the shared packages admit the same releases holds.
main brings the unprompted channel (anima-research#244), membrane 0.9 (anima-research#955) and appendAll in place of spread arguments (anima-research#942). Where they meet this change: - The follow-on marks. anima-research#244 added an UNPROMPTED mark (unpromptedTurnMark), spread by hand beside heartbeatTurnMark at the ' !' continuation, autoContinue and the bounce. It stays spread there, inside the request markFollowOn marks: folding it into markFollowOn's new-turn kind would extend it to the re-arm, the rewind retries and the unstick, which would change anima-research#244's reach. MarkedRequest keeps main's UNPROMPTED key beside this change's FollowOnMarks type, and main's wakeChannelsMark goes, since markFollowOn replaces it at each of its sites. - The channel registry's options. takesGlobalFallback keeps this change's private method, whose body is main's inline expression, and main's takesUnpromptedChannel goes beside it. - The 'delivery' rule's docs (docs/dendrite.md, FrameworkConfig.speech's doc, and the registry's resolver comment) take main's sentences for an all-except hold's channel and the unprompted channel, with this change's wording where main still says a turn with no locus keeps its prose private: routing posts it to no channel, and a module may still show it and say so.
No conflicts. main brings appendAll (anima-research#942), which the next commit uses for the lists this change adds to.
…ithout call spreads main's appendAll (anima-research#942) replaced push(...list) on these request lists, since V8 throws a RangeError past about 125,000 call arguments. This change's own pushes onto pendingRequests (a wait's joined wakes, a merged start's batch, the released requests) now use it too. Restoring the checkpoints an undo's dropped record had evicted prepends them by concat, since a checkpoint list read from the store is as long as what was stored.
The one conflict is in test/event-gate-judge.test.ts, where main's tests of a batch longer than a spread can pass (anima-research#942) and this branch's refusal log-line test were both added after the same test. Both stay whole, this branch's first. No call this branch adds spreads a list as its arguments.
…spread, and the round's texts say what the code does
The abort and abandon path (dropContinuations) takes a budget hold and
put its wakes back with back.push(...wakes), then
this.pendingRequests.push(...back): R2-3's defect beside the site the
previous commit fixed, on the same unbounded list. At 250,000 held
requests abortInference() threw RangeError ("Maximum call stack size
exceeded") after the hold had been taken. Both are now appended in a
loop. The scheduler's own spreads of an agent's batch are main's,
reached by any queue that long, and are what agent-framework anima-research#942
changes; this commit leaves them.
The exhaustion reason after a release with a fit said "what its start
then added didn't" fit. Nothing establishes that cause: a budget lowered
between the check and the start, or another agent's write to a shared
store, reaches the same branch. It now says only what is known: "its
context fit at the check that released it, and the compile its start
then ran didn't, with nothing left for compression to do". The previous
commit's message describes the older wording.
Texts, each narrowed to the code:
- Changed fragment: the turn runs without this release's note, and a
note an earlier release of the same hold stored stays; a strategy
whose estimate throws also keeps the note; the recent window's edge
can differ from the compile's, with no bound of one message (the
window is a token walk over whole messages); while the window is held
the note is placed, which makes the picker fold no deeper, so where
only a deeper fold had room for it the request goes past its budget
by up to the note's price, from the reply's reserve, and can't be
refused for it; the band names messages deferred for the agent while
the turn waited, not every message that arrived, since those stored
on arrival are in the previewed context.
- Fixed fragment: a call that lands during a drain the hold hasn't
joined gets a look at once, and, still short, the hold joins that
drain, whose end looks again (it said the look waited for the drain's
end); a repeated compression failure is alerted again only once the
alert's cooldown has passed or after a pass or drive without one, so
not each failure (the previous commit's message says "each"); and
releaseProviderWait() offers the lane its release, which drops the
lane's stated wait while its own backoff still paces the next call
(it said the lane's pause was lifted).
- The repeat-failure test's title says it pins a report after a drive
without one, which is what it asserts.
- Four comments that narrated test helpers are gone.
Tests: a held turn with 250,000 requests is stopped with
abortInference() without throwing, every wake back in the queue and the
hold gone; a note case where the strategy has no estimateTokens at all
keeps the note with one look. Three tests take the new reason.
Red at the previous commit's src: 106 tests, 102 pass, and these 4
fail: the abort at 250,000 (RangeError) and the three reason tests.
No conflicts. main brings appendAll (anima-research#942), which the next commit uses for the lists this change adds to.
…ithout call spreads main's appendAll (anima-research#942) replaced push(...list) on these request lists, since V8 throws a RangeError past about 125,000 call arguments. This change's own pushes onto pendingRequests (a wait's joined wakes, a merged start's batch, the released requests) now use it too. Restoring the checkpoints an undo's dropped record had evicted prepends them by concat, since a checkpoint list read from the store is as long as what was stored.
The one conflict is in test/event-gate-judge.test.ts, where main's tests of a batch longer than a spread can pass (anima-research#942) and this branch's refusal log-line test were both added after the same test. Both stay whole, this branch's first. No call this branch adds spreads a list as its arguments.
Under H5, the paired batch's turn's first request gets a 529 stating a 60 s wait, so agent-framework holds the turn for it. A message on w12-pair-b closes its batch during the hold and waits in it too. At pair.held the clock passes the hold's end, and one compile carries the turn's retry with the held batch; the turn then speaks and sleeps as before. What it reaches, by run: anima-research/agent-framework#948's retry built from the live routing, and the union of the batches' channels: after the sleep, #948's base tells the agent the turn came from no channel, and its merge that it came from w12-pair, w12-pair-in and w12-pair-b. anima-research/agent-framework#942's release of a hold's requests: with its enqueue dropped, pair.held goes unanswered and the property says the hold's end woke no turn. w12-pair-b registers at H5's start, so the store id in the review's notice moves, and later sections with it. Two steps.
Under H5 the agent adds a judge rule over w12-quick with a 30 s window. quick.1 carries [[judge-holds]], so the stand-in holds its batch's judge request while quick.2, carrying [[wake]], arrives and its window passes: the second batch queues behind the first's verdict and goes to the judge on its own after it, answered WAIT and then REPLY. At quick.held a message on chat starts a turn whose two rounds the stand-in holds while quick.3 and quick.4 arrive on w12-quick and their windows pass: both batches wait behind the turn as one and go to the judge in one request when it ends, answered REPLY, and the turn the verdict starts calls skip_reply. What it reaches, by run: anima-research/agent-framework#942's two appends, a batch queued behind a judge's decision and one merged into a batch held behind a turn: with either dropped, the record moves at its step, and with the queued one dropped, quick.fire runs out its wait for the second verdict. w12-quick registers at H5's start, moving the review's store id again. Three steps. With them W12's run under H5 compresses once, at its end, so checks 4 and 5 judge it.
Why. When a provider hold gathers more wakes than a spread can pass as call arguments, about 125,000 where we measured, or a judge rule's quiet period gathers that many events, releasing or judging them throws a
RangeError(Maximum call stack size exceeded), because the framework passes those lists into calls with a spread.This passes those lists, and every other list whose length comes from data but a path's segments, at 72 call sites without a spread, so a provider hold with 250,000 wakes held, or a judge rule's quiet period that gathers 250,000 events, no longer throws.
A provider hold's release, with more wakes held than a spread can pass
main): the release throws aRangeError.A judge rule's quiet-period batch that long, held while a turn runs or while the judge decides
main): holding it throws the sameRangeError.Nothing in the package's exports changes.
The three new tests reach 3 of the 72 sites, and they ran on Linux only. Whether any other site met a list past the limit isn't shown.
changelog.d/request-lists-no-spread.fixed.md.Credits
Octavia-2412 measured the limit and found the spreads a released list meets after the release. Ansel-2428 made this change, ran it red first, added the second
append-alltest, and brought it up to date withmain, including the spreadsmainadded since; Ada-1017 made its design calls. Alfred-2570 read it and the update, and Ambrose-2591 and Ursula-2404 weighed each of its parts. Our internal review read an earlier version of it. Daniel-2612 repeated the runs at clean checkouts and wrote this description, and Ansel-2428 updated it for the second test and the update.Details for agents
Head
bc10622ddc, onmainaa134e75fc, which1755821b6fmerges in. The branch began on48f26ce56c.Problem
At
aa134e75fc, three paths spread a list that long into a call. The new tests show each one, run withaa134e75fc'ssrcand7b7f7f3fd6's two test files (Tests):RangeError: Maximum call stack size exceeded, and the stack's first frame isAgentFramework.releaseProviderAccelerationCooldown.EventGate.holdForJudge.EventGate.judgeBatch.Changes
src/append-all.tsaddsappendAll(into, items), which pushesitemsontointoin a loop. Its doc comment says why: "into.push(...items)passes each item as a call argument, and V8 throws a RangeError past about 125,000 arguments, so this pushes them one at a time."test/append-all.test.tspins thatappendAll(list, list)appends the list once:[1, 2, 3]becomes[1, 2, 3, 1, 2, 3]. It also pins that a separate list of 250,000 items, about twice what a spread can pass, is appended whole and in order.x.push(...list)becomesappendAll(x, list).Math.min(...list.map(f))becomeslist.reduce((min, v) => Math.min(min, f(v)), Infinity), andMath.max(a, ...list.map(f))orMath.min(a, ...list)becomes areducethat starts froma.appendAll, in the same order: insrc/dendrite/registry.ts, insrc/mcpl/tool-presence.ts(lines.push(...this.untoldNotes(cs), ...cs.notes), and the "can open" line withopenable), and insrc/tool-presentation.ts's two description pushes.unshiftof a list. Insrc/framework.ts'srecoverHeldWrites,this.surgeryHeldWrites.unshift(...recovered)puts the recovered writes ahead of those the hold keeps.surgeryHeldWritesis a readonly array thatreleaseSurgeryHeldWritesreads by reference, so it becomes, in place,const later = this.surgeryHeldWrites.splice(0), thenappendAllofrecoveredand then oflater.src/framework.ts,this.pendingRequests.push(...newestByKey.values()), aMap's values, becomesappendAll(this.pendingRequests, [...newestByKey.values()]). Insrc/agent.ts,Math.min(...this.compilesRunning), over aSet, becomes[...this.compilesRunning].reduce((min, start) => Math.min(min, start), Infinity). Insrc/gate/suppression-review.ts,Math.min(fromMs, ...this.retryFrom.values())becomes[...this.retryFrom.values()].reduce((min, from) => Math.min(min, from), fromMs). A spread in an array literal isn't a call.src/modules/history/semantic.ts'snamed()took its failures as a rest parameter. It now takes one array, and its two callers pass one.appendAllcalls (two attool-presence.ts'suntoldNotespush, and two at theunshiftabove), 11reduces, andnamed()'s two callers.changelog.d/request-lists-no-spread.fixed.mdis a.fixedfragment in a commit of its own,448a6708,7f3f13a743narrows its last sentence, and131ae19353its first.4281fe80a1adds the secondappend-alltest, for the automatic review's eighth finding, and changes no source.1755821b6fmergesmainataa134e75fc. Wheremainremoved or replaced code this branch had changed (the stop's postponed-release pass, the deferred-write flush, ack, hand-off and restore, and a continuation's wait for the shared slot),main's code stands, and four of the branch's earlier sites went with it. Wheremainadded a line beside one of the branch's sites (heads.unshift(all[turn.from]!.id)beside theremovedIdspush inframework.ts, and theidsset passed tocollectDiscordCarriersinsrc/recovery/offline-branch.ts), both are kept.1c1163f387changes the 11 call spreadsmainadded after48f26ce56c, by the same rule:redoOnWindow's minimum over the put-back rows' ordinals,recoverHeldWrites' push andunshift,reportHeldWritesOnce's push, the push of what a tool boundary's presentation heard,refreshMcplTools' three tool-list pushes,SuppressionReview's two minimums, andTuneOutCoordinator.anchorSubconscious's minimum over the active tune-outs' sequences.7b7f7f3fd6changes no source. The provider hold's test is renamed to what its assertions check, as is one assertion's message, and two test comments now name Node 24.22.2, the version the limit was measured on, where they said 24.22.1.b067630653changes no source, and answers the automatic review of7b7f7f3fd6. A new test intest/reinit-after-branch-move.test.tskeeps a write for one agent while its manager refuses its work, holds a newer write for another agent during the move that recovers the first, and checks that the kept write is stored first, the order theunshiftgave and the rewritten prepend keeps. The two spread tests intest/event-gate-judge.test.tsnow give the judge a 600-second timeout, so its 20-second default can't run out during their flood.7f3f13a743changes only the fragment, after the automatic review ofb067630653. Its last sentence said that every call that spread a list that can grow that long now passes it without a spread, and the five spreads that join a path's segments (below) keep theirs. Three of those take a mount path the host configures. Two take a path an agent names, a level at a time, and stop at the first level that isn't on disk. The sentence now ends "a list that can grow that long with what an agent holds or does", which none of the five is.131ae19353changes only the fragment, after the automatic review of7f3f13a743. Its bold first line said that a list longer than a call can take as arguments no longer throws, and a configured mount path's segments still can. It now names what no longer throws: held wakes and judged events. The rest of the paragraph is rewrapped, with its words unchanged.b6c38de96caddstest/no-call-spread.test.tsand changes no source. It readssrcwith@babel/parser, astest/no-deferral-residue.test.tsdoes. It fails on any call, optional call ornewwith a spread argument. It also fails on anyReflect.applyorReflect.construct, and on any call of a member namedapplywith two arguments. A call the test'sLEFTnames by file and source text, with why its list stays short, passes.LEFTholds the 14 calls below andsrc/framework.ts's twoReflect.applys, which pass on the arguments a proxied method's own call was given. A callLEFTnames thatsrcno longer has fails too.bc10622ddcremoves that test again, and changes nothing else. It would fail every open pull request that adds a call it checks tosrc, outside its list. The lists the rule guards stay far below the limit in normal use. This head's tree is131ae19353's.@babel/parserwith itstypescriptplugin,srchas 87 spreads in call arguments ataa134e75fcand 14 at this head (Tests). The 73 it removes are all in the 19 files this change edits: the 72 sites above hold 73, sincetool-presence.ts'suntoldNotespush had two. The 14 left are:this.host.deliver(...args)(src/mcpl/push-coalescer.ts:643),this.dispatchInboundEvent(event, ...args)(src/mcpl/server-connection.ts:885),super.emit(event, ...args)(:1006and:1082) andthis.emit(event, ...args)(:2621);count(...win(e))(src/modules/history/index.ts:594) andfetch(...win(inside), …),fetch(...win(mid), …)andfetch(...win(out), …)(:615,:630and:638);join(rootReal, ...segments)(src/modules/workspace/index.ts:2803),join(…, ...segments.slice(0, i))twice at:2806and once at:3014, and once insrc/modules/workspace/observe.ts:408.Tests
At a clean checkout of
7b7f7f3fd6, on Linux with Node 24.22.2 and npm 11.19.1, afternpm install --ignore-scriptswith no lockfile.npm ls --depth=0gives@animalabs/chronicle0.6.0,@animalabs/context-manager0.25.0,@animalabs/mcpl-core0.4.0,@animalabs/membrane0.8.0,@babel/parser7.29.13,@modelcontextprotocol/client2.4.0,@modelcontextprotocol/server2.4.0,@types/node22.20.5,@types/ws8.18.2,chokidar4.0.3,discord.js14.28.1,tsx4.23.15,typescript7.0.2 andws8.22.1.npx tsc --noEmit,npm run buildandnpm run pretesteach exited 0. Each test file ran alone, with the flagsnpm testgivesnodeand the TAP reporter:provider-acceleration-cooldownpasses 11 of 11,event-gate-judge61 of 61 andappend-all2 of 2.1c1163f387's sites, each passing with none failing:window-undo-redo13,review8-tune-out100,operator-surgery33,discord-held-writes21,module-held-writes17,presentation-points21,clock-rows39,mcpl-tools-keep-on-failed-relist17,suppression-review18 andsubconscious-window-anchor17.node: 87 in 22 files ofsrcataa134e75fc, and 14 in 5 files at this head. By script, every spread this head lacks is in one of the 19 files this change edits, and the head has none thataa134e75fclacks.At a clean checkout of
b067630653, set up the same way,reinit-after-branch-movepasses 104 of 104 andevent-gate-judge61 of 61.7f3f13a743and131ae19353change only the fragment, so this head'ssrcandtestareb067630653's, sinceb6c38de96c's test is removed again bybc10622ddc.The same way, with
srcas ataa134e75fcand7b7f7f3fd6's two edited test files,provider-acceleration-cooldownpasses 10 of 11 andevent-gate-judge59 of 61. The three new tests fail withRangeError: Maximum call stack size exceeded, as Problem says, with the first frames it names.1c1163f387's 11 sites, each broken alone by a mutant that drops its items, reorders them, or takes the largest where it takes the smallest, in a working tree of that commit's files orb067630653's: a test fails at 9 of them.recoverHeldWrites' push andreportHeldWritesOnce's push each fail tests already inreinit-after-branch-move.recoverHeldWrites' prepend fails onlyb067630653's new test.modernServers()) and the suppression review's second minimum, every file run still passes: those above, withagent-allowed-tools,awaited-tool-routes,host-toolsetsandmcpl-down-for-goodfor the first andevent-suppression-e2efor the second. So no file run here pins them.The earlier runs stand for the commits they name. At a clean checkout of
448a6708, on Linux with Node 24.22.2 and npm 11.19.1, with@animalabs/chronicle0.5.1,@animalabs/context-manager0.24.1 and@animalabs/membrane0.8.0:provider-acceleration-cooldownpassed 11 of 11,event-gate-judge61 of 61 andappend-all1 of 1, and 123 test files, listed below, ran 3,876 tests: 3,814 passed, 0 failed and 62 were skipped. They were the 105 that import one of the 17 modules that commit changes other thansrc/framework.tsby its own path, and 18 more chosen by name for thesrc/framework.tspaths its sites are on. With the release changed to keep only the first held wake, the provider test failed at "the newest held wake's line reaches the turn". At4281fe80a1,provider-acceleration-cooldownpassed 11 of 11,event-gate-judge61 of 61 andappend-all2 of 2. In the working tree that became that commit, withappendAllchanged to copy only the last 100 items, the newappend-alltest failed on the length, 101 where it expects 250,001, and withappendAllchanged back tointo.push(...items), it failed with aRangeError. Atc3e5f97d, whosesrcwasmain's then, the three new tests failed with theRangeErrorat the frames Problem names. The limit, from the first file below, run withnodeon Node 24.22.2:push(...a)andMath.min(...a)each pass at 124,887 elements and throw at 125,498.The limit probe,
limit.mjsThe spread scan,
scan.mjsThe 123 test files run at
448a6708, undertest/Not verified
.github/workflows/ci.yml:84), and platforms other than Linux. The limit depends on the stack's size, and the tests' 250,000 is about twice the limit measured here on Node 24.22.2.7b7f7f3fd6we ran the 13 test files Tests names, and atb067630653the two its commit edits, of the 441*.test.tsfiles intest/. The 123 files listed ran at448a6708.aa134e75fc'ssrcat three of the 72 sites. No run here shows whether any of the other 69 met a list past the limit.1c1163f387's sites, which no file run here pins (Tests).