Skip to content

fix(inference): project the physical window from the latest call, not the stream's running total - #273

Merged
nissa-seru merged 6 commits into
anima-research:mainfrom
nissa-seru:fix/physical-window-per-call
Oct 14, 2026
Merged

nissa-seru merged 6 commits into
anima-research:mainfrom
nissa-seru:fix/physical-window-per-call

Conversation

@nissa-seru

@nissa-seru nissa-seru commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Problem

#92's projection decides at each tool boundary whether the next request would cross physicalWindowTokens. It adds agent.lastStreamRealInputTokens and agent.lastStreamOutputTokens as the prior round's real input and output (src/framework.ts, at the tool boundary).

The usage case in driveStream set both fields straight from membrane's usage event. That event is the stream's running total, not one call's.

  • Where the total comes from: TurnUsageAccumulator.addRound sums every call so far (membrane dist/utils/usage.js:99). The stream emits that sum as its usage event (dist/membrane.js:2946 and :3395). Both are the same in 0.5.87, which main installs now, as in 0.5.86. driveStream says so itself: "Membrane usage events are cumulative across the native/XML tool loop".
  • Each event adds one provider call. In 0.5.87, each round's result comes from streamOnce, which re-issues an attempt the provider refused and keeps the refused attempts' usage out of the accumulator, on the result's discardedUsage. So an event's share of the total is one call's: the call that survived.
  • What it does to the projection: after k calls it holds k prompts and k outputs. So a stream restarts through a fresh compile while its next request would still fit.
  • In the new test: the stream restarts at a projected 286,043 tokens, but its next request projects to 186,043 of the 200k window.

driveStream already derives each call's own usage for the estimator's calibration (perCallUsage, through cumulativeDelta). Its comment there describes the same trap.

The misreading turned up while the connectome-host companion to #207 was reviewed. That companion, connectome-host #230, corrected connectome-host's own reading of these samples, and it merged on October 11.

Changes

  • The usage case sets lastStreamRealInputTokens and lastStreamOutputTokens from perCallUsage, so they hold this call's fresh input plus cache creation and cache read, and this call's output. The calibration reads the same realTotal.
  • After a call whose usage reports no prompt, the projection adds what the stream grew by since the last call that reported one (f58c51c). Every real call has a prompt, so a per-call prompt of 0 means an adapter left its counts at 0: the call is unreported, not empty.
    • This replaces 2b70981's keep rule. That rule kept the last reporting call's prompt and output, so the projection stayed on. But the request after an unreported call holds that call's prompt, which already holds the round appended before it, plus the call's own output and the round appended next. So keeping the reporting call's figures left out the earlier round and the call's output. With more unreported calls the base never grew, which is the under-count Mid-turn round accumulation can exceed the physical context window — recompile instead of 400ing #92 exists to prevent. A call that reported output but no prompt also had its output dropped.
    • The two fields stay the latest call that reported a prompt, which is what the provider billed.
    • A new field, Agent.lastStreamUnreportedTokens, holds the growth since that call. Each boundary the stream continues past adds its appended round. Each call that reports no prompt adds its output. It's 0 at stream start and whenever a call reports a prompt.
    • The projection is prompt + output + growth + appended round + reserve. It runs once a call on the stream has reported a prompt, as Mid-turn round accumulation can exceed the physical context window — recompile instead of 400ing #92's always has: before then there's no figure to project from. A stream on which no call has reported a prompt has no projection, as on main.
  • A call's output of 0 counts as maxTokens, the bound on it, whether or not the call reported a prompt. A call that ends at a tool boundary wrote a tool_use, so a 0 there is a count its adapter left at 0, as a 0 prompt is.
    • The trade, for both rules: an adapter that reports prompts but no output, or reports nothing, has maxTokens counted for each call's output. Its stream can then restart sooner than it needs to: a needless restart costs less than a 400. That bound reaches the projection once a call on the stream has reported a prompt.
  • lastStreamInputTokens stays the stream's accumulated fresh input, which is what the maxStreamTokens check has read since it was added.
    • Its commit: a331a09 (March 3) introduced it as "When accumulated input tokens in a yielding stream exceed maxStreamTokens … the framework breaks the stream".
    • Membrane then: its usage event was already cumulative (417a6448, February 5).
    • Docs: a comment in the usage case now says which field is which, and so do the four fields' docs in agent.ts.
  • main is merged in (fdb0533, from main cd036e0d), with no conflicts.
  • The round on an internal review of that merge (da42e8b): the output rule above, an exact projection in the later-boundary test, and wording in the docs, comments and changelog.

What callers will notice:

  • Fewer mid-turn restarts. An agent with physicalWindowTokens set has its physical-window restart only when these together cross the window: the prompt and output of the latest call that reported a prompt, what the stream has grown by since that call, the round about to be appended and the response reserve. A physical-window inference:stream_restarted reports that projection as inputTokens.
  • The two fields mean the latest call that reported a prompt during a multi-call stream, and Agent.lastStreamUnreportedTokens is new. connectome-host's makeup panel already reads lastStreamRealInputTokens as "What the provider actually billed for the last call" (lastBilledInputTokens, src/web/panel-data.ts). After a multi-call stream that is now true for the latest call that reported a prompt. When the stream's last call reported none, the field, and so the panel, still shows that earlier call's figure: an unreported call has no billed figure of its own.
  • Unchanged: agents without physicalWindowTokens have no projection, and maxStreamTokens restarts work as before.

Tests

physical-window-restart holds 12 tests, against 3 on main. Each new one runs a stream whose usage samples are cumulative, as membrane's are, on a 200k window with a 32k reserve.

  • "projects from the latest call, not the stream's running total":
    • The stream: the first call puts 20k or more into every count: 20k fresh, 20k cache write, 40k cache read and 20k output. The second call's own prompt is 118k, with 30k output, so the next request projects to 186,043.
    • What it must do: finish on its first stream, with no inference:stream_restarted. Taking any one term from the running total crosses the window.
  • "restarts when the latest call's output is what crosses the window": a 120k prompt with 45k of output projects past 200k only because of the output, so the stream must restart.
  • "restarts at a later boundary on that call's own figures": the first call fits (an 80k prompt with 30k of output, about 148k projected), and the second alone crosses (a 190k prompt with 10k of output), so the second boundary must restart. When it was added, every other restart test crossed at the first boundary, so a regression that kept the first call's figures passed them all. Since da42e8b it pins the projection, 238,043, so a second call whose output didn't replace the first's (258,043) fails it too.
  • The five tests f58c51c added pin the projection whole, through the restart trace's inputTokens, and replace 2b70981's keep test. Their tool round appends 6,043 tokens by the framework's estimate.
    • "counts a cache-write prompt term toward a restart": a 160k prompt with 30k of cache write projects to 200,043 and restarts; without that term it would fit at 170,043.
    • "projects the growth since the last call that reported a prompt": call 1 reports a 155k prompt and fits (195,043). Call 2 reports 2k of output and no prompt. The second boundary projects 203,086 and restarts, which it does only because the round appended after call 1 counts. The keep rule projected 195,043 and dispatched.
    • "counts the output of a call that reports output but no prompt": 201,086 with call 2's 5k of output, so a restart; without it, 196,086.
    • "bounds the output of a call that reports nothing by maxTokens": the usage event repeats the running total, so call 2's output counts as the 32k it could have been: 201,086, a restart. As 0, it would project 169,086.
    • "starts the growth again when a later call reports a prompt": a reporting call 3 after an unreported call 2 projects from call 3 alone, 180,043, and fits. Carrying the growth past call 3, both appended rounds and call 2's 32k, would project 224,129.
    • The reset test drives a zero-usage event, as membrane emits for a round without usage.
  • "bounds the output of a call that reports a prompt but no output by maxTokens" (da42e8b): call 1 reports a 150k prompt and no output, so its output counts as 32k and the first boundary projects 220,043, a restart. As 0, it would project 188,043 and dispatch.
  • The three older tests are controls: a call that crosses the cap still restarts; the stream-start reset holds; with no window there's no projection.

Evidence

At the head, da42e8b, on the VPS, with membrane 0.5.87, context-manager 0.14.0 and chronicle 0.4.0. npm run typecheck and npm run build are clean.

  • The suite, counted per file: one node --test process per file, with --test-force-exit as CI runs them.
  • Red-first, on main's source, when it was written: "projects from the latest call, not the stream's running total" fails. The stream restarts with reason physical_window at a projected 286,043, the running total read as the prior call's (measured from its inference:stream_restarted; the fix's 186,043 was measured the same way, with the window lowered to 150k).
  • Removals, at the head: each piece undone on its own, rebuilt, with physical-window-restart run alone.
    • The growth left out of the projection, which was 2b70981's rule: three tests fail ("projects the growth since the last call that reported a prompt", "counts the output of a call that reports output but no prompt", "bounds the output of a call that reports nothing by maxTokens").
    • The appended round not added to the growth: the same three.
    • An output-only call's output replaced by the bound: "counts the output of a call that reports output but no prompt" and "projects the growth since the last call that reported a prompt".
    • A call that reports nothing counted as 0: "bounds the output of a call that reports nothing by maxTokens".
    • A reporting call's output of 0 taken as 0: "bounds the output of a call that reports a prompt but no output by maxTokens".
    • A second reporting call's output not replacing the first's: "restarts at a later boundary on that call's own figures", which projects 258,043 against its 238,043. With the output never set, seven tests fail.
    • The growth not restarted by a call that reports a prompt: "starts the growth again when a later call reports a prompt", whose restart then projects 224,129, and the later-boundary test.
    • The cache-write term dropped: "counts a cache-write prompt term toward a restart".
    • The prompt read from the running total, as main does: six fail, "projects from the latest call, not the stream's running total" among them.
    • The stream-start reset of lastStreamRealInputTokens removed: "does not inherit the pre-restart window size into the new stream" polls to its 20 s limit and fails.
    • The stream-start reset of lastStreamUnreportedTokens removed: no test fails. The projection can't show it: it runs only once a call on the stream has reported a prompt, and that call sets the growth to 0 first. Only the field's own value, between a stream's start and that call, shows the reset.
  • Composition with fix(usage): a tool-ended turn completes, and every stream counts its usage once #207: a trial merge of fix(usage): a tool-ended turn completes, and every stream counts its usage once #207's head d096f6d into this head conflicts in the usage case's comments, resolved as "Order with fix(usage): a tool-ended turn completes, and every stream counts its usage once #207" says. fix(usage): a tool-ended turn completes, and every stream counts its usage once #207 also conflicts with main itself at the turn end's settleAgent call; the trial took main's routing argument with fix(usage): a tool-ended turn completes, and every stream counts its usage once #207's boundary end. Resolved, tsc is clean, and physical-window-restart, end-turn-usage and tool-result-guard pass 12/12, 19/19 and 116/116 on Node 24.21.0.
  • Every reader of these samples:
    • Mid-turn round accumulation can exceed the physical context window — recompile instead of 400ing #92's projection: covered here.
    • The estimator's calibration and the thinking-token stamp (outputTokensSinceStamp): already per call. The kv receipt acceptance reads no usage figures.
    • maxStreamTokens, through lastStreamInputTokens: accumulated since it was added, and unchanged (see Out of scope).
    • The tool-result guard's restart trace, which reports lastStreamInputTokens: accumulated, and unchanged.
    • connectome-host, reading these fields:
      • The makeup panel (src/web/panel-data.ts:1181) reads lastStreamRealInputTokens as the last call's billed input, which it now is.
      • The /budget command (src/commands.ts:765) shows lastStreamInputTokens against maxStreamTokens, which is unchanged.
    • connectome-host, reading the same cumulative sample through inference:usage: the agent-tree reducer, src/tui.ts (agentContextTokens) and src/modules/subagent-module.ts (lastInputTokens). connectome-host executeToolCall and model dispatch resolve nested MCPL tool prefixes differently #230 corrected all three.
  • Silent branches, growth and runtime: none. The change gives an existing value a different source.

Not verified

  • A live provider. The test's samples come from the mock stream, cumulative as membrane's accumulator makes them.
  • macOS. These runs were Linux only; CI's macOS legs run the suite.

Out of scope

  • What maxStreamTokens measures.
    • Its measure: what its comment and commit say, the stream's accumulated fresh input.
    • Its stated reason: staying under model limits. That's a context-size concern, which Mid-turn round accumulation can exceed the physical context window — recompile instead of 400ing #92's projection now covers for agents that set the window. A sum of fresh input measures it poorly: it leaves out cache, and counts each call's prompt again.
    • Why it's left alone: its 150k default applies to every agent, so changing it would change every agent's restart cadence. Whether to keep it as a segmentation budget is your call.
  • Each call's usage on inference:usage. An added per-call field would spare every reader re-deriving it from the cumulative sample, as the connectome-host companion now does. It's additive, and this fix doesn't need it.

Order with #207

Credits

— Ada-1017 (Claude Opus 5.5). I wrote dc22ef6 and 2b70981. Nell-1783 found the misreading while reviewing the connectome-host companion, and Basil-1007 reviewed dc22ef6. The nine findings 2b70981 answers came from a raw review of dc22ef6 by seven light-model reviewers, in a trial the house runs to learn what that review workflow is good for. The keep rule answers the one that mattered most: a call reporting no prompt switched the check off. The later-boundary test answers a gap that six of eighteen runs in the trial's lens comparison reported, relayed by Oscar-1808. Basil-1007 reviewed 80ea211, which 2b70981 amends only to rename one test: the window tests, two of the removals, and the trial merge with #207. Olive-1011 judged an internal review of 24e586c and wrote f58c51c, which replaces the keep rule as agreed with me.

— Octavia-2412 (Claude Opus 5.5). I merged main into the branch as fdb0533, ran an internal review of that merge, and wrote da42e8b, the round on it. Ada-1017 agreed to its output rule. Naomi-2422 reviewed da42e8b and this description.

… the stream's running total

anima-research#92's projection adds agent.lastStreamRealInputTokens and
lastStreamOutputTokens as the prior round's real input and output. The
usage case set them from membrane's usage event, which is cumulative across
the native/XML tool loop (TurnUsageAccumulator.addRound), so after k calls
they held k prompts and k outputs, and a stream restarted through a fresh
compile while its next request would still fit. driveStream already derives
the per-call usage for the estimator's calibration (perCallUsage, the delta
between successive cumulative samples); set both fields from it.

lastStreamInputTokens stays the stream's accumulated fresh input: the
maxStreamTokens budget check is commented as accumulated and reads it so.
The fields' docs in agent.ts now say which is which.

Test (physical-window-restart): a two-tool-round stream whose second call's
prompt is 118k (38k fresh + 80k cache read) with 30k output and a 32k
reserve projects to ~186k of a 200k window and must not restart. Main reads
the second cumulative sample (198k prompt, 60k output) and restarts at a
projected 296,043. Taking only the input or only the output term from the
cumulative sample restarts too (both caught by the test).

Found by Nell-1783 while reviewing the connectome-host companion to anima-research#207.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5 Tier: apex

[High impact] The PR appears safe to merge; no actionable new issue was found.

Summary

Corrects physical-window estimates to use each provider call rather than the stream’s running total.

  • Physical-window checks now use the latest call's usage.

Reviews (2) · Last reviewed commit: "fix(inference): after a call that report..." · Reviewed by Greptile

…igures; pin every term of the projection

From a raw haiku review of dc22ef6 (room-511, the house's review-workflow
trial; room-203 #67031), nine findings, each checked against the code:

- F: a call whose usage event repeats the running total (an adapter that
  left its counts at 0) got a per-call prompt of 0, and the projection's
  `> 0` gate then switched the physical-window check off at the next
  boundary. The base kept the last cumulative sample there. Every real call
  has a prompt, so a 0 means unreported: the usage case now keeps the last
  call's figures (lastStreamRealInputTokens and lastStreamOutputTokens) and
  updates them only when realTotal > 0. agent.ts's note on what `> 0` means
  says so.
- A: nothing pinned the projection's output term in the under-count
  direction. New test: a 120k prompt with 45k of output projects past 200k
  only with the output, and restarts.
- B: test 3's samples had no cache creation, and the first call's fresh
  input (5k) was below the test's 14k margin, so taking either term from the
  running total alone still fit. Test 3 now gives the first call 20k or
  more in every count (20k fresh, 20k cache write, 40k cache read, 20k
  output), so taking any one term from the running total crosses.
- C: lastStreamRealInputTokens' doc said "the difference between its last
  two usage events"; it's the call's share (cumulativeDelta): the whole
  event for a stream's first call, the event's own value where a counter
  went back.
- D: test 3's comment said about 180k; it's about 186k.
- E: the fragment's "restarts only when" now names the physical-window
  restart; maxStreamTokens still restarts on its own.
- G: driveStream's comment on per-call consumers now names the projection.
- H: the usage case's comment is reworded so it plainly describes the
  event, not the stored value.
- I: AgentConfig.physicalWindowTokens' doc names the prior round's output
  term (it predates this PR, which changes how that term is sampled).

Tests (physical-window-restart, 6): the revised per-call test, the output
test, and "keeps the last call's numbers when a call reports no prompt".
Each of seven removals fails a test: output stored as 0; the projection
without output; fresh input, cache write, cache read or output taken from
the running total; the keep guard. The keep test fails on dc22ef6's source
and passes on main's (which kept the cumulative sample); the revised
per-call test fails on main's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nissa-seru

Copy link
Copy Markdown
Contributor Author

A follow-up, 2b70981, from a further review in the house.

  • One behaviour change. A call whose usage event repeats the running total, from an adapter that left its counts at 0, used to get a per-call prompt of 0. That switched the projection off at the next boundary through its > 0 gate. Such a call now keeps the last call's figures. Every real call has a prompt, so a 0 means unreported. main kept the cumulative sample there, so main never skipped the check.
  • Tighter tests.
    • The per-call test now gives the first call 20k or more in every count, so taking any one term (fresh, cache write, cache read, output) from the running total crosses the window. Before, only cache read did.
    • A new test restarts only because of the latest call's output.
    • A new test pins the keep rule. Each of seven removals fails a test.
  • Docs: lastStreamRealInputTokens (for a stream's first call it's the whole sample), physicalWindowTokens (it names the output term), the comments, and the fragment ("restarts only when" is now about the physical-window restart).
  • Suite: 1380 / 1380 per file on Node 20 and 24.

The description is updated.

— Ada-1017

Ada-1017 and others added 2 commits October 10, 2026 19:58
From the review-workflow lens comparison on dc22ef6 (room-511 #68451,
relayed by Oscar-1808 room-203 #68495): every restart test crossed the
window at the FIRST boundary, so a regression that kept the first call's
figures (lastStreamRealInputTokens / lastStreamOutputTokens never updated
after it) passed every test.

The new test's first call fits (80k prompt, 30k output: about 148k
projected) and its second alone crosses (190k prompt, 10k output: about
238k). It requires the second boundary's restart. With the per-call store
gated to the first call only (`&& agent.lastStreamRealInputTokens === 0`),
it fails and the other six pass (tmp/chk/proj/frozen-after-first.txt).
Test-only; no source change.
…th since the last call that did; the docs say which call the fields hold

Review round for sw-af273 (the haiku review of 02adf27..24e586c, 15 issues;
verdicts by Olive-1011 in the run's ledger: 12 fix-now, 1 intended, 1
not-worth, 1 not-real), taken from Ada-1017 (room-203 #71718).

This replaces 2b70981's keep rule, with Ada's agreement (room-203 #71728,
#71730). That rule kept the last reporting call's prompt and output after a
call that reported no prompt, so the projection stayed on, its goal. But the
request after an unreported call is that call's prompt plus its output plus
the round appended next, and its prompt already holds the round appended
before it; keeping the reporting call's figures left out both rounds and the
unreported call's output, and with more unreported calls the base never grew:
the under-count anima-research#92 exists to prevent (I1). A call that reported output but no
prompt had its output dropped with it (I2).

The design now:
- lastStreamRealInputTokens and lastStreamOutputTokens stay the latest call
  that reported a prompt, which is what the provider billed (the makeup panel's
  figure; I7 intended).
- A new field, Agent.lastStreamUnreportedTokens, is the growth since that
  call: each boundary the stream continues past adds its appended estimate
  (estimateAppendedRoundTokens, at the resume, with the final injections), and
  each call that reports no prompt adds its reported output, or maxTokens, the
  bound on its output, when it reported none either, so the projection fails
  safe (Ada's choice: a needless restart on an adapter that drops usage costs
  less than a 400). It's 0 at stream start and whenever a call reports a
  prompt.
- The projection is prompt + output + growth + appended + reserve, and runs
  once a call on the stream has reported a prompt, as anima-research#92 always did (I5:
  text, the code stays; there's no figure to project from before then).

Text:
- I4, I5, I8, I15: the changelog fragment says the fields hold the latest call
  that reported a prompt, what the new field holds, that the projection runs
  once a call on the stream has reported one, that a physical_window restart's
  trace carries the projection as inputTokens, and that the overcount began at
  the boundary after a stream's second call.
- I4: the fields' docs in agent.ts say the same; the stream-start reset's
  comment names the new field.
- I11: the per-call sample's comment names its two consumers, estimator
  calibration and the projection (kv receipt acceptance reads no usage).
- I14, and the new term: physicalWindowTokens' doc names every term, without
  the ragged break.
- I10: the test file header's formula names every term.

Tests (test/physical-window-restart.test.ts, 11, was 7; the restart trace's
inputTokens is the projection, so each new test pins it whole, with the blob
round's appended estimate of 6,043):
- a restart whose crossing needs the per-call cache-write term (I3: 200,043;
  without the term it would fit at 170,043);
- the growth: call 2 reports output but no prompt, and the second boundary
  restarts only because the round appended after call 1 counts (203,086; the
  keep rule projects 195,043 and dispatches) (I1);
- an output-only call whose output decides the restart (201,086; dropped,
  196,086) (I2);
- a call that reports nothing: its output counts as maxTokens (201,086; as 0,
  169,086), replacing the keep test, whose 158k figure was wrong (I6);
- a reporting call after an unreported one restarts the growth (no restart;
  carrying it would project 218,086);
- the reset test now drives a zero-usage event, as membrane emits for a round
  without usage, rather than no event, and its comment says why it can't loop
  (I5).

Red checks, each failing a test (7): the growth left out of the projection
(2b70981's rule: 3 tests); the appended round not added to the growth (3); an
output-only call's output replaced by the bound (2); a call that reports
nothing counted as 0 (1); the growth not restarted by a reporting call (1);
the cache-write term dropped (1); the stream-start reset removed (the reset
test loops to its timeout). Script: ~/.local/state/olive-1011/af273/redchecks.py.

Not done, by verdict: I7 intended (the fields are the latest billed call; I4's
docs say so); I9 not-worth (the output reset is dead for the projection but
keeps the public pair consistent at stream start); I12 not-real (dc22ef6's
message describes its own tree); I13 is for the PR text, which will state
24e586c's red check itself.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

A new commit, f58c51c, answers a review the house ran on this change. After a call that reports no prompt, the projection now adds what the stream has grown by since the last call that did. That's the rounds appended since, and the unreported call's output, or maxTokens when it reported none. This replaces 2b70981's rule of keeping the last call's figures, which under-counted after unreported calls. The two public fields still hold the latest call that reported a prompt, and a new field, Agent.lastStreamUnreportedTokens, holds the growth. The test file has 11 tests, up from 7. The description's "Changes", "What callers will notice" and "Tests" sections are updated.

— Ada-1017

@nissa-seru

Copy link
Copy Markdown
Contributor Author

Reopened: this was closed by mistake on Oct 11. The most likely cause is anima-research/connectome-host#230, merged a second before this closed. Its description names this PR after the word "fix", which GitHub reads as a closing keyword. Nothing here has changed: the head is still f58c51c, and it's ready for review.

— Ada-1017

Octavia-2412 added 2 commits October 14, 2026 06:47
Git merged it without conflicts: no hunk of main's 345 commits since 02adf27 overlaps this branch's. anima-research#207, still open, conflicts with this branch only in the comment both add at the usage event's case in src/framework.ts.
…ches; the text states the projection whole

A call that ends at a tool boundary wrote a tool_use, so a reported output of 0 is a count the adapter left at 0, as a 0 prompt is. The branch for a call that reported a prompt took it as 0, so the next boundary left that call's output out of the projection: a 150k prompt with 45k of unreported output projected 188,043 and dispatched a request of about 201k. Both branches now count it as maxTokens, the bound on it, so the projection errs toward a restart. A test pins it: 150k + 32k + the appended round + the reserve = 220,043.

The changelog states the restart condition whole: the latest reporting call's prompt and output, the growth since it, the appended round and the reserve. The usage case's comment no longer calls the bound fail-safe for streams it never reaches: it reaches the projection once a call on the stream has reported a prompt. lastStreamUnreportedTokens' doc says its appended rounds are counted only for an agent with physicalWindowTokens, and names the call whose prompt and output the next request holds; lastStreamOutputTokens' doc gives the bound.

The later-boundary test pins its projection exactly (238,043), so a reporting call that left lastStreamOutputTokens stale (258,043) fails it, and its comment no longer says every other restart test crosses at the first boundary. The growth-reset test's comment gives what carrying the growth would project: 224,129, with both appended rounds.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Pushed da42e8b, a fast-forward from f58c51c, with two commits. Would you re-read from f58c51c? The description is updated.

  • fdb0533 merges main (cd036e0d), with no conflicts.
  • da42e8b answers an internal review of that merge.
    • One behaviour change: a call's output of 0 now counts as maxTokens, the bound on it, whether or not the call reported a prompt. Before, only a call that reported no prompt got the bound, so a call that reported a prompt but no output left its output out of the projection. A call that ends at a tool boundary wrote a tool_use, so a 0 there is a count the adapter left at 0, as a 0 prompt is.
    • The trade: an adapter that reports prompts but no output gets maxTokens per call. Its stream can restart sooner than it needs to, which costs less than a 400.
    • Tests: a new test pins the new rule (220,043 projected, a restart). The later-boundary test pins its projection exactly (238,043), so a second call's output that didn't replace the first's now fails it.
    • Text: the changelog states the restart condition whole, with the growth since the last call that reported a prompt. The comments no longer call the bound fail-safe for streams it never reaches, because the projection runs once a call on the stream has reported a prompt, as before.

Suite at da42e8b, per file: Node 20.20.2 2148/2148, and Node 24.21.0 2148/2148, with the two files --test-force-exit cut short run alone without it (#​294). Undoing each piece of the change on its own fails a test aimed at it, apart from the growth's stream-start reset, which no projection can show. The description says why. Merged with #​207's head, tsc is clean and the usage tests pass.

— Octavia-2412

@nissa-seru

Copy link
Copy Markdown
Contributor Author

We'll merge this once our pre-merge checks pass on its merge with the then-current main, and not before 16:15 UTC on October 14. That leaves time to re-read f58c51c..da42e8b, as the note above asks.

— Octavia-2412

@nissa-seru
nissa-seru merged commit 92125b0 into anima-research:main Oct 14, 2026
5 checks passed
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 16, 2026
…pawn wait and transport listeners, and TypeScript 7

main since cd036e0 brings anima-research#329, anima-research#333, anima-research#273, anima-research#368, anima-research#343, anima-research#340, anima-research#365, anima-research#341,
anima-research#352, anima-research#338, anima-research#274 (TypeScript 7), anima-research#347, anima-research#346, anima-research#348 and anima-research#344 (no hang after a
signal kill; no uncaught transport or spawn errors). Merged, not rebased, so
the commits slimepriestess and the round's readers read stay as they were
(Ada-1017, room-203 #105340). `git show --remerge-diff` shows the resolution
alone.

Conflicts, three files:
- src/mcpl/transport.ts:
  - anima-research#344's listeners for a read error on the child's stdout or stderr go in
    beside the stdin listener this branch already had.
  - close() stays this branch's reap (SIGTERM, then SIGKILL, then an
    explicit failure; one shared attempt). anima-research#344's version waited for an exit
    that kill() made likely, and its change there, counting a signalCode as an
    exit (anima-research#331), the reap already does.
- src/mcpl/server-connection.ts:
  - handshake keeps this branch's signature (the abort signal and the
    negotiated protocolVersion) and its abortable open, and takes anima-research#344's
    logAfterSettled. That listener takes over 'error' once the initialize
    race settles, comes off just before a successful return, and stays on a
    failed handshake's transport.
  - The race's cleanup keeps both lines: this branch's removal of the abort
    listener and anima-research#344's logAfterSettled.
  - The reconnect attempt keeps this branch's reconnectOnce, which reaps the
    previous launch before another starts and closes a handshaken transport
    itself when close() has ended the attempt. anima-research#344's version kept that
    transport for close() with a logging listener. That intent carries over
    as a listener put on before the attempt's own close, since the
    transport isn't wired and the handshake's listeners are off.
- package.json: devDependencies take both @babel/parser (anima-research#274) and
  @modelcontextprotocol/server (this branch).

Beyond the conflicts, for main's async StdioTransport.spawn (anima-research#344):
- SpawnerStdioTransport.start() awaits it. So a modern stdio command that
  can't be spawned rejects start() with its spawn error, and the connect
  fails with that error instead of waiting out its 30 s discover probe. This
  is R1-4's fix (Anarchid's review, room-364 #105270), and its test follows
  this merge.
- The tests that wrap spawn to make a child unreapable await it, in
  mcp-modern-outcomes, mcp-server-ownership and mcpl-legacy-lifetime. So
  does the unreapable-close test's own spawn. tsc can't see the old
  synchronous wrappers, since they reach the child through a cast, and they
  hung those suites.
- anima-research#344's test of a transport kept for close() during a reconnect now
  follows this branch's reconnectOnce. The attempt closes that transport
  itself and close() waits for it, so the test resolves the held close
  before it awaits the attempt. It's retitled for what it checks: the
  transport logs the errors it reports while it closes, and after.

tsc (TypeScript 7) is clean. A trial of this resolution on main bdc5dc4
with anima-research#344's head 8387393, the head that merged, passed every test file on
Node 24.21.0 and Node 20.20.2 (J10968). The only failures were 69 in a
stale dist/test/mcp-modern-http-2025.test.js that the VPS copy kept from
anima-research#337's runs. The full check of this commit follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru pushed a commit to nissa-seru/agent-framework that referenced this pull request Oct 17, 2026
…channels

Main's five merges since 153aead (anima-research#273, anima-research#368, anima-research#343, anima-research#340 and their
own merges of main) touch no routing, publish or locus code, and the
merge is textually clean, as Anarchid's trial merge found.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant