Repository navigation
fix(shards): a sharded body keeps each block in place and goes out as given, and window edges keep a tool call with its results - #310
Conversation
… given, wherever its shards render Where adaptiveResolution is on, ingress (chunkIngressMessage) joined a message's text blocks into one string, cut it into shards, and put every other block on the first shard after its text. The renders then disagreed with the message and with each other: - The head and recent windows' merge (mergeAdjacentBodyGroupRaw) sent a run's other blocks ahead of its joined text, so a reply [thinking, text, tool_use] went out as [thinking, tool_use, text], [short text, image, long text] as [image, text], and two text blocks as one. - The adaptive middle's shard path sent a raw run's text alone. A reply ending in a call lost its thinking and its call, and its result was in no message of the compile. An image went unsent. A tool result whose message was split went out as the placeholder for a result that isn't shown. - The middle's raw run carried no sourceMessageId, so the merge never joined it to the window's shards of the same body next to it. A body across the middle and a window went out as two or three entries, which membrane's native formatter sends as one message with the body's text cut where the regions meet, so each time the recent window's edge crossed one of its seams, the prompt was rewritten from that body on. The change: - chunkIngressMessage cuts the same joined text at the same seams, and gives each shard its slice of the message's blocks, in order. A text block keeps its own boundaries, cut where a seam falls inside it, and every other block goes on the shard holding its place in the text: the shard whose text it comes before, or the last at the message's end. The pieces of a cut text block are plain text, since the block's other fields (cache_control, a raw replay form) are the whole block's. A raw form that a cut block carried, or whose blocks land on more than one shard, is given up with its reasoning pairing (releaseRawFormsAcrossParts in raw-forms.ts), so a shard never replays what another holds. Every shard after the first declares continuesBlock: whether the seam before it fell inside a text block. IngressChunkResult's shards carry it, addMessage stores it, and StoredMessage and the internal record keep it (message-store's append extras). addMessage refuses a chunking whose first shard declares it, or whose later shards declare it on some and not others, before writing any shard. - joinBodyContent (body-runs.ts, exported) joins consecutive parts of a body, shards or renders that join a run of them: the parts' blocks in order, joining text only at a seam that split a block, so a body stored this way comes back as given. A body stored before, which declares no continuesBlock, joins as the windows always joined it: its other blocks, then its text as one block, since joined as stored its thinking and calls would come after its first slice of text. A single part is its own content. - Every raw render in the strategy joins through it (joinShards): the middle's raw run, the kv-unified cache check's rebuild of that run (kvUnifiedKeepEntries), and the windows' merge, which renderAsNewest uses. The middle's run sends each shard as any raw message is sent (cappedForSend), cut at maxMessageTokens as the windows cut it, and is priced as the parts it joins, each at the plan's price; the plan prices a shard as any message. It names its first shard as sourceMessageId, as the merge names a composite, so the merge joins it to the window's shards beside it: the merge reads continuity from each entry's last shard to the next one's first. - The image strip judges each image at the message that holds it. joinShards records, for each other block of a joined part, its shard (_blockHome, which pairing repair already used for a moved result, renamed from _movedFrom), and pairing repair's copies of blocks keep it (releasedKeepingHomes). Both strip paths read it (homeOf): the verdicts the estimate gave (asEstimated), and the counting path, which now decides every image newest home first, as the estimate counts them, before applying the decisions entry by entry, each image as an occurrence (its entry and its place among the entry's images), so an image object that sits twice gets a verdict at each place. - callStart: where a tool result's call is a sharded body, the recent window's start, the head and tail reservation's floor and the compression hold's boundary, each where the message it stops at holds the result, start at the call's first shard that holds a tool_use or a thinking block, read in the body before the result's own body. A reply that ends in its calls has them on its last shard, so the earlier shards of a call with no thinking can fold while the calls stay raw before their results. A call that thinks first counts from its first shard, so it keeps its thinking with it, as main's floor and hold counted a sharded call, from its body's start. A body stored before holds the call on its first shard, as the floor and the hold counted it. The recent window stepped back one message, which for such a body is its last shard, leaving the tool_use out of the raw tail. - answeredToolCalls places a call at its body's first shard, so tool-result pruning orders a sharded reply's calls as one message's, by where their results sit, where they now sit on different shards. - Comments that said a body's other blocks sit on its first shard, that the middle sends text alone or uncut, or that a composite gathers its text into one block, say what the code does now; concatBodyGroups' doc says it renders a body as text alone, with placeholders for its other blocks, and points to joinBodyContent; IngressChunkResult's and joinBodyContent's docs say a cut text block comes back as plain text; picker.ts's smaller-recent-window doc says where the call counts from. The design doc's §3.6 and §3.6.1 describe the layout, the join and older bodies. - Changelog fragments: 829-body-blocks-in-place.breaking.md (hosts that join a body's stored shards themselves: use joinBodyContent; the two such callers, agent-framework's asCompiled and connectome-host's Web UI coalesce, old beside new; the refusal; what an older release renders) and .fixed.md. Older stores: in the head and recent windows a body stored before renders as it did. In the middle it now sends its other blocks, as the windows always did, and across the middle and a window it is one entry. The recent window now starts at the first shard of such a body when it starts at a result the body's call answers. A stored window holding one of these three changes misses the prompt cache from that body on, on its first compile after the upgrade. With a positive maxMessageTokens below a shard's size, the middle now cuts its shards, as the windows always did. Tests: - body-blocks-in-place: five shapes (a reply that thinks, writes and calls a tool; two text blocks; a short text, an image and a long text; an image before its caption; a result and a long note), each in the recent window, raw in the middle, across the middle and the recent window (with the premise that both regions hold some of it), and in the head window, under the default folding and kv-unified: one message sends the body, exactly as given, and no other message carries any of its text, images or tool blocks. A body stored before (an ingress that writes the old layout) goes out in each place as the windows always sent it. The recent window's start at a result: the call's tool_use shard, the first shard of a call that thinks (thinking and redacted_thinking), the first shard of an older body, the first of two calls on different shards, the call before a result that sits after text on a later shard, and the store's start for a result on a later shard of the first message. With a cap that cuts shards, the body raw in the middle goes out as in the recent window. - ingestion: a reply's stored shards join back as given, the thinking leading and the call ending; two text blocks with a seam inside one, and with the seam on their boundary (continuesBlock false); a short text, an image and a long text; images before a caption and at the end; a tool result then a long text; an image and an empty text block on a seam; a cut text block's pieces plain, with the message item's rawItem and its reasoning released and the function call's kept; a raw form whose blocks land whole on one shard kept, and one whose blocks land whole on different shards given up; the older layout's join; continuesBlock false and true through the store and a reopen; mixed declarations, a declaration on the first shard, and false on every shard refused before any shard is written. - body-run-price: a body with an image at its end, raw in the middle, sends it where it sat and is counted as every part; an emitter whose estimateTokens counts more doesn't change the middle run's count. - live-image-accounting: the real chunker and merge keep a body's images on its last shard, where the strip and the estimate count them alike; the strip judges a joined body's image at its own shard though the entry starts at another, in both strip paths, and through a second join; the counting strip spends a live slot on a body's newest shard first; pairing repair's copies of a result keep their shard, the one an orphan's drop leaves and the one a relocation leaves behind; an image object that sits twice in one entry is decided at each place; the merge keeps another copy of a body apart after a run whose last shard's index is above it. The hand-built text-only middle composite's test is gone, since no path builds one. - kv-unified-render-state: the cache check's rebuild credits every marker for a middle composite that sends its image, for a long reply ending in a call, raw in the middle, with its result after it, and for a body's raw shards cut at maxMessageTokens in the middle. kv-unified-marker-boundaries: a body across the middle and the recent window is marked as one message, its marker recorded where it ends. - capped-message-price: a body's raw shards go out cut at the cap in every region, the middle included. - overbudget-diagnostics: the floor counts a sharded call from its shard that holds the tool_use, for both layouts (four rows), and a sharded result with its call's last shard (a row for each layout where the call is sharded). - compression-hold G2: the shard that holds the call's tool_use stays with its held result, and the call's first shard, text alone, compresses before it. - tool-pruning-price: the separated-call case runs with an ingress that writes the older layout; a sharded reply's calls on different shards are ordered as one message's. - calibration-band's preview fixture drops the emitter that counted the middle's run at half, since the run now counts as the plan priced it; the picker's refusal is still the only one the preview can fall back at. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Automated review
Recommendation: fix, then merge. 1 high severity finding should be fixed first.
Reviewed commit 3ec6915 with claude-opus-5-5, by reading the code. It did not run the code or look at the checks.
Does this change belong in the project?
It fits the project. This fixes how sharded bodies render under adaptiveResolution, which the design doc already covers. It brings shards closer to the doc's goal that joined shards reproduce the original message.
5 reasons with sources
The code stays in the modules that already own sharding. The breaking change for hosts is declared in a changelog fragment, as CONTRIBUTING asks.
- The change carries out the design's own rule that joined shards should give back the original message. §3.6 says shards are "byte-faithful" and that an unfolded run is "one composite API message whose body is the byte-faithful concatenation of those shards". On main, sharded bodies lose or reorder non-text blocks and split into several entries across regions. The PR makes each shard keep its place in the original message and joins them back as one entry. (docs/adaptive-resolution-design.md §3.6 / §3.6.1)
- The code lands in the modules that already own ingress, body runs and rendering.
joinBodyContentsits in body-runs.ts besidecontinuesBody,bodyStartandbodyBounds. It is exported on the same index.ts line as those helpers. Ingress chunking stays inchunkIngressMessage, and the strategy's raw renders now share this one join instead of each joining shards their own way. (src/body-runs.ts, src/adaptive/render.ts, src/index.ts) - The breaking change for downstream hosts is declared, the hosts are named, and it comes with the matching design-doc update. The diff adds
changelog.d/829-body-blocks-in-place.breaking.mdand a.fixed.mdfragment. The breaking fragment names agent-framework'sasCompiledand connectome-host'scoalesceAndFlatten, and says what each must change. CONTRIBUTING expects consumer impact and companion PRs to be declared, with merge-order guidance. The description was cut off, so whether companion PRs exist is for a maintainer to confirm. (CONTRIBUTING.md (Changelog; How changes land)) - The description backs its claims with a store-based probe, which is the kind of evidence CONTRIBUTING asks for. The PR reports results from a 48-case probe and a 30-turn sweep. It also lists which stored windows will miss the prompt cache once after the upgrade, matching the 'say what you did NOT verify' and real-store expectations. (CONTRIBUTING.md (What a PR should contain))
- The design-doc sentence on older bodies may overstate the behavior; the review finding about it is accurate. The new text says a body stored before this change renders its other blocks first "in every region". A review finding reports that single-shard runs don't do this, so the doc should be corrected to match. This is an accuracy issue in the doc, not a mismatch with what the project intends. (docs/adaptive-resolution-design.md §3.6 (added line 212))
Findings
4 findings of medium or high severity, see the inline comments:
- High severity. Recent window start misses a tool_result that ingress now places on a later shard of its message.
- Medium severity. Refusal floor checks only the newest body's first shard for a tool_result.
- Medium severity. holdBoundary steps back to the call only when the first held shard holds the tool_result.
- Medium severity. A reasoning pairing whose members land whole on different shards is not released.
2 more findings, less severe
- Low severity, documentation,
docs/adaptive-resolution-design.md:212. Design doc says legacy bodies render other blocks first "in every region", which single-shard runs don't do.joinBodyContentreturns a single part's content unchanged (if (parts.length === 1) return [...parts[0]!.content]). A legacy shard 0 rendered alone, for example when the body's later shards fold, still goes out as stored: its text, then its image or tool_use. The doc says such a body renders "its other blocks first, then its text as one block, in every region". That is true only for joins of two or more parts. - Low severity, maintainability,
test/body-blocks-in-place.test.ts:67-86. LegacyIngress test helper is copied into three test files. The sameLegacyIngressoverride, which rebuilds the old shard layout fromsuper.chunkIngressMessage, appears in test/body-blocks-in-place.test.ts, test/overbudget-diagnostics.test.ts and test/tool-pruning-price.test.ts (asLegacyIngressStrategy). These copies emulate the stored format that compatibility depends on. If the legacy layout needs to change, all three must be updated together. It should live in one shared test helper.
| if (boundary > 0 && boundary < messages.length && this.hasToolResult(messages[boundary])) { | ||
| boundary--; | ||
| boundary = this.callStart(messages, boundary); | ||
| } |
There was a problem hiding this comment.
High severity. Recent window start misses a tool_result that ingress now places on a later shard of its message.
Before this change, ingress put every non-text block on shard 0, so a sharded message's tool_result was always on its first shard. chunkIngressMessage now keeps each block where it sat, so a user message like [long text, tool_result] stores its result on its last shard. The PR's own test ("the recent window at a result that sits after text, on a later shard") shows this layout. getUnheldRecentWindowStart still checks only hasToolResult(messages[boundary]). When the window edge falls on shard 0, or on any shard of the result's body before the one that holds the result, no step back happens. The window then holds the result but not the call body before it, so enforceToolPairing blanks the result as an orphan without refusing the compile. The check should look at whether any shard from boundary to the end of its body holds a tool_result (or at bodyBounds). Only the case where the edge lands exactly on the result's shard is tested.
There was a problem hiding this comment.
Addressed in fcdf6b44. It was a regression for a message written as [long text, tool_result], whose result main kept on the first shard. The recent window's start, the compression hold's boundary and the floor's newest turn now ask whether any shard of the boundary's message holds a tool_result (bodyHoldsToolResult), and if one does, start at the call. This also fixes #308, the same check's gap on main for a result message whose edge falls on a later shard. Tests cover an edge on each shard of such a message, a compression hold on it, and the floor. The tests of the window, the hold, and the floor where only the last shard would fit fail against 3ec69158.
Written by Beth-2389.
— Lottie-2661
| const newestBody = bodyStart((k) => messages[k], messages.length - 1); | ||
| const newestTurn = newestBody > 0 && this.hasToolResult(messages[newestBody]) | ||
| ? bodyStart((k) => messages[k], newestBody - 1) | ||
| ? this.callStart(messages, newestBody) | ||
| : messages.length - 1; |
There was a problem hiding this comment.
Medium severity. Refusal floor checks only the newest body's first shard for a tool_result.
newestTurn is set to callStart(...) only when hasToolResult(messages[newestBody]) is true, and that tests only the first shard of the newest body. With blocks now kept in place, the newest message [long text, tool_result] holds its result on its last shard. In that case the floor counts only messages.length - 1 and leaves out the call, so clearedBy can name smaller-recent-window for a window that would orphan the result. The comment above it says "a result leads its message, so it's on the body's first shard", but the PR's own tests store results on later shards. Every shard of the newest body should be checked.
There was a problem hiding this comment.
Addressed in fcdf6b44, with the recent window's fix: the floor's newest turn asks bodyHoldsToolResult of every shard of the newest body. Its test, a result after a long text where only the last shard would fit, fails against 3ec69158.
Written by Beth-2389.
— Lottie-2661
| // earlier ingestion of the same text, which shares the group id). | ||
| b = bodyStart((k) => messages[k], b); | ||
| } | ||
| if (b > 0 && this.hasToolResult(messages[b])) b = this.callStart(messages, b); |
There was a problem hiding this comment.
Medium severity. holdBoundary steps back to the call only when the first held shard holds the tool_result.
holdBoundary takes the first held message, which for a held sharded body is its shard 0. It calls callStart only if hasToolResult(messages[b]). When ingress put the held result on a later shard (result after a long text, a layout this PR introduces), shard 0 holds no result, so no step back happens. The call's shards before the body can then close into chunks and be compressed while their held result is still provisional. That is the case the compression hold exists to prevent. The check should cover the whole held body.
There was a problem hiding this comment.
Addressed in fcdf6b44, with the recent window's fix: holdBoundary asks bodyHoldsToolResult of every shard of the held message. Its test, a compression hold on a [long text, tool_result] message, fails against 3ec69158.
Written by Beth-2389.
— Lottie-2661
| const partOf = new Map<ContentBlock, number>(); | ||
| parts.forEach((part, k) => { for (const block of part) partOf.set(block, k); }); | ||
| const released = new Set<string>(); | ||
| const landed = new Map<string, Set<number>>(); | ||
| for (const block of before) { | ||
| const k = partOf.get(block); | ||
| for (const key of rawFormKeys(block)) { | ||
| if (k === undefined) { | ||
| released.add(key); | ||
| continue; | ||
| } | ||
| let where = landed.get(key); | ||
| if (!where) landed.set(key, (where = new Set())); | ||
| where.add(k); | ||
| } | ||
| } | ||
| for (const [key, where] of landed) if (where.size > 1) released.add(key); | ||
| if (released.size === 0) return parts; |
There was a problem hiding this comment.
Medium severity. A reasoning pairing whose members land whole on different shards is not released.
releaseRawFormsAcrossParts releases a key only if a block carrying it was cut, or if one key's carriers land on more than one part. pairingsOf skips blocks that have no item key. So [thinking (rawItem rs_1), long text with no raw form, tool_use (rawItem fc_1)] forms the pairing [rs_1, fc_1]. The thinking lands whole on shard 0 and the call on the last shard, each key lands on exactly one part, and nothing is released. Shard 0 then replays a reasoning item without the item it led to, which releaseSharedRawFormsOnMove treats as invalid ("would keep the id that ties it to a partner it no longer sits with"). Any pairing whose keys land on different parts should be released as well.
There was a problem hiding this comment.
Addressed in fcdf6b44. releaseRawFormsAcrossParts now gives up a pairing whose members land on more than one shard, as it does a raw form whose carriers do, with a test of a reasoning item on the first shard and its function call on the last, which fails against 3ec69158.
Written by Beth-2389.
— Lottie-2661
…olds it; a reasoning pairing split across shards gives up its raw forms; a first shard's continuesBlock is never refused A tool result's message is now checked whole, on all its shards: - Ingress keeps each block where it sat, so a message written as [long text, tool_result] holds its result on its last shard. The recent window's start (getUnheldRecentWindowStart), the compression hold's boundary (holdBoundary) and the head and tail reservation's floor (newestTurn) asked only the shard at the boundary, or the body's first shard, whether it held a tool_result. So an edge on an earlier shard of that message started the window after the call, leaving the result's call out of the raw tail, and the floor and the hold counted from the wrong place. main, which put every other block on the first shard, stepped back there. - bodyHoldsToolResult asks it of every shard of the boundary's message, and all three sites use it. This also fixes anima-research#308, where a window's edge on a later shard of a split result message, whose result is on its first shard, left the result and its call out of the raw tail. A stored window whose edge falls on a later shard of a split tool-result message now starts at the result's call, so it misses the prompt cache from there, once, on its first compile after the upgrade. releaseRawFormsAcrossParts releases a reasoning pairing whose members land on different shards: a reasoning item on the first shard and the function call it led to on the last, with plain text between, would otherwise each keep its item, and a shard replayed alone would carry one member of the pairing without the other. addMessage no longer refuses a chunking that declares continuesBlock on its first shard. No join uses a first shard's declaration: joinBodyContent reads the parts after the first, and a body's first shard is always the first part of a join. It is stored as given. A chunking that declares it on some shards after the first and not others is still refused. The design doc's sentence on bodies stored before says their shards render as the windows joined them wherever two or more render together, and that a shard rendered alone goes out as stored. Tests: - body-blocks-in-place: for a [long text, tool_result] message after its call, the recent window starts at the call with its edge on each of the message's shards, and a compression hold on the message starts at the call. The shape whose result message's shards fell across the middle and the recent window now has the whole message and its call raw in the recent window, so its row asserts that. - overbudget-diagnostics: the floor counts a result after a long text, on a later shard, with its whole body and its call (two rows), and a result message that is the store's first message with its whole body (the floor's newest turn no longer skips callStart for the store's first body). - ingestion: a reasoning pairing whose members land whole on different shards gives up both items, and one whose members land whole on one shard keeps both; a chunking that declares continuesBlock false or true on its first shard is accepted and joins back as given (the first-shard refusal's test is gone). - The old shard layout's rewrite, which three test files copied, is one helper, test/_harness/legacy-ingress.ts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Replying to this review.
Two changes in the same commit don't come from your review. A chunking that declares The full suite passes at Written by Beth-2389. — Lottie-2661 |
|
@antra-tess @Anarchid We're asking you both to review this PR at What it changes for hosts and their users:
As of 2026-10-22 22:01 UTC it merges with Written by Beth-2389. — Lottie-2661 |
One conflict, in src/strategies/autobiographical.ts, at the adaptive plan's price for a middle message. - main (edef413, in anima-research#330) moved that price into middlePrice, which the plan's chunks and the over-budget refusal's rawPinsDiagnostics now share, and priced a shard uncut there, since main's shard path sends a run of raw shards' text joined and uncut. - This branch prices a shard as any message, the post-strip estimate plus cappedForSend's extra, since its shard path cuts each raw shard as any raw message is cut and prices its entry as the shards it joins, each at that price. The resolution keeps main's shared middlePrice with this branch's rule, so the refusal prices raw pins as the plan and the shard path do. main's comment after the plan said the refusals after it count "a shard's text alone"; with the shard path pricing each shard as the plan does, it now names an emitted prefix only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a shard's text prunedForSend's doc said the adaptive middle's shard path sends a shard's text, kvUnifiedKeepEntries' doc said a body's raw shards in the middle join their text into one entry, and estimateTokens' doc priced a raw shard's text. Since blocks are kept in place, the shard path joins a run of raw shards, each cut by cappedForSend, with joinShards, kv-unified's rebuild joins them the same way, and a raw shard is priced with every block it holds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h sends them main's anima-research#330 test priced a marked message's parts uncut, since main's shard path sent a run of raw parts' text joined and uncut. Here the shard path cuts each part at maxMessageTokens as it cuts any raw message, and the plan and the refusal's rawPins price a part as it is sent (middlePrice). The test now asserts that the named mark covers every part and is priced at 400 to 450 tokens for each, so a part priced uncut (about 2,000) or a part left out falls outside, and walks the budget down far enough for the cut parts to be over it. At this head the mark's nine parts price at 3,852. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@antra-tess @Anarchid
The full suite passes at Written by Beth-2389 (Claude Opus 5.5). — Lottie-2661 |
One conflict, in src/strategies/autobiographical.ts's imports: main (anima-research#331, the thinking binding) imports canonicalJson and withoutThinking, and this branch imports continuesBody, joinBodyContent and releaseRawFormsAcrossParts. The resolution takes both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ps its thinking when noted Beside the test of a sharded reply that isn't noted: a reply that leads with its thinking, stored as shards, keeps that thinking at the next binding compile once the host notes it. With blocks kept in place its first shard holds the thinking at its head, which the test checks as setup, and renderAsNewest and the compile join its shards alike, so the noted digest matches the compiled reply. Under the older layout the reply kept its thinking too, since both renders put the other blocks ahead of the text; the test guards the joins in the new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@antra-tess @Anarchid
The full suite passes at Written by Beth-2389 (Claude Opus 5.5). — Lottie-2661 |
main brings anima-research#268 (request image ceilings), anima-research#298 (system changes in place), the 0.22.0 release, anima-research#335 (the context observer) and anima-research#339 (a flat profile that can raise). Four conflicts, each taking both sides: - src/strategies/autobiographical.ts, the AnsweredCall doc: main adds anima-research#268's ImageVerdict, CeilingKinds and StripReplay types above it; this branch's doc names a sharded message's first shard. Both kept. - src/strategies/autobiographical.ts, the smaller-recent-window floor: main (anima-research#298) takes the newest conversation message, past the system changes after it, and steps a result's body back to the call before it by the result's first shard; this branch looks for the result on any of the body's shards (bodyHoldsToolResult) and steps back to the call's first shard that holds a tool_use or thinking (callStart). The resolution starts from anima-research#298's newest conversation message and steps back as this branch does. callStart returns 0 for a body at the store's start, where main's guard (newestBody > 0) kept the newest message; a body at 0 has no call before it, so the floor counts from 0, the whole body, as this branch counted it. - src/strategies/autobiographical.ts, applyImageStripping's verdicts: main (anima-research#268) keeps each verdict whole, to credit the ceiling line, and reads a moved result's home from _movedFrom; this branch renamed that map _blockHome, which also places a joined body's other shards, and reads it through homeOf. The resolution takes anima-research#268's loop with homeOf. No other _movedFrom remains. - src/adaptive/picker.ts, the smaller-recent-window doc: anima-research#298's system changes and the newest conversation message, with this branch's result on any shard and the call from its first shard that holds a tool_use or thinking, as the resolved floor counts it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…age answering it, wherever its shards hold the tool_use and the tool_result Ingress keeps each block where it sat, so a message written as [long text, tool_result] holds its result on its last shard. The chunkers' tool-pair guards asked only the message right after the call: - both chunkers' size close (and the autobiographical boundary hint) waited one message past a message holding a tool_use; - the Knowledge chunker's summary-boundary close asked whether that one message held a tool_result; - the head window's end stepped back only past a message holding a tool_use. So a chunk could close after the result message's first shard, with the result in a later chunk. A compression request drops a tool_result whose call isn't in it (stripUnpairedToolBlocks), so that result reached no summary. main, which put every other block on the first shard, kept the result there. A call stored before blocks were kept in place has the same gap the other way round, on main too: its tool_use is on its first shard and its text on the shards after it, so a chunk could close after the text, before the result. callPartedAfter says where a boundary after a position would part a call from the message answering it: a message holding a tool_result on any shard answers the message before it when that one holds a tool_use on any shard, and the two run from the call's start (callStart, as the recent window's start, the floor and the hold count it) through the answering message's last shard. closingPartsCall adds main's rule (a message holding a tool_use) and, at the listing's end, the cases the store can't show yet: the chunkers run as each shard lands, and the autobiographical one keeps each chunk it closes, so a close inside a message still being written (shardCount) or inside a call whose result hasn't come waits for the rest. The size closes and the boundary hint ask closingPartsCall; the Knowledge summary-boundary close asks main's question of each message whole (bodyHoldsToolUse, bodyHoldsToolResult), and the size close then keeps the rest of the answering message in the chunk; the head window's end steps back to the call's start while its edge would part them. For a store without shards each rule gives what it gave before. refuseToolPairSplit's two comments gave the old layout as the reason it reads a body whole; they now give the in-place one. Tests: - body-blocks-in-place: for a result after a long text and for a call stored before blocks were kept in place, written live, the chunk holding the tool_use holds its tool_result, under the autobiographical chunker, a subclass whose boundary hint asks for a close everywhere, and the Knowledge chunker; the head window's end, on each message from the call's start to the result message's last shard, is the call's start; and, end to end, a compression request carries the result beside its call. - system-changes: a summary ending at an unanswered call doesn't part the call from a result after a long text, on a later shard of its message. Each fails without the fix at 860dfdc (the merge this builds on), at its new assertion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ll with a result still being written, its own result across its shards, and its result in the split fallback The chunkers' round (ae6f358) paired a message holding a tool_result with the message before it whenever that one held a tool_use, by place. Its review (cm829-r5) and two runs showed where that, and the round's other rules, fall short: - A close after a call whose tool_use isn't on its last shard waited only while the call was the listing's last message. Once the answering message's first shard landed, before its result had, the autobiographical chunker could close and keep a chunk with the call and without its result (I1). - A message that makes a call and answers it itself, as a claude.ai export's bundle does, can hold the two on different shards, and nothing kept a chunk from closing between them (I4). - The split fallback cut a refused chunk after any message but a call followed by result messages, so a piece could hold a sharded call without its result (I3). - Pairing by place took two bundles in a row, each answering its own call, for a call and its answer, and stepped the head back one more message than main for nothing (a probe, S2). - Each candidate close scanned whole bodies, so writing one message of 920 shards took 25 to 48 s at ae6f358, where main took about 1 s (I12, measured). callPartedStarts works out, for the whole listing in one pass, where a boundary would part a call from its result: a message answers the one before it when it holds a tool_result for a call that message left open (openCallIds), and a message's own call and result across its shards are a run too. closesHeld adds the listing's end: a close waits inside a message still being written, and inside a call, from its start, that leaves a call open while the message that may answer it hasn't come or is the one still being written. Each chunker works these out once per walk and asks closingPartsCall at each close; the head window's end steps back by them; the split fallback's cuts skip them. For a store without shards the chunkers close where main did, and the head's end differs only where main's ended between a call and the message answering it, which also made the next call. There main sent the call with pairing repair's stub in place of its result once that message folded (a probe on main: S1). Text: the changelog item says which results it covers (a later message's, as parallel calls' results in messages of their own, is as before), and the cache line for such a store. IngressChunkResult's raw-form sentence names the pairing release; picker.ts names redacted_thinking; two system-changes test comments describe the stored-before layout as that. Tests: - body-blocks-in-place: two more pairs for the chunkers, the head and end to end (a call whose text follows its tool_use, answered by a result after a long text; a call that thinks first); a message answering its own call across its shards, under each chunker; at messages without shards, a call answered by a message that makes the next call, and a chain of three, where the head ends before the first call and no stub is sent, and two bundles in a row, where the head ends where main ends it; two sharded bundles in a row, where a chunk closes inside the second; and the end-to-end check reads the request's blocks for the call and its result. - split-stitch-l1: a tool round whose result sits after a long text is indivisible: every piece carrying the call carries its result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m its result, and a message's own call runs from where the call starts Answers the fix round's review (cm829-r6): - The recent window's start stepped back only from a message holding a result, so an edge inside a call stored as shards, after the shard where the call starts and before its answer, left the call before the window and its result in it (I10). It now also steps back while callPartedStarts marks the position before it, as the head window's end does. - A message's own call and result across its shards ran from the shard holding the tool_use, so a boundary between a thinking shard and the call it led to could fall inside it. The run now starts where the call starts, at the message's first shard holding a tool_use or a thinking block, as its doc says, and callPartedStarts no longer keeps where each tool_use was made (I7, and I8's duplicate ids with it). - callPartedStarts' doc says a boundary inside the answering message is held so the message stays whole with its call, wherever in it the result sits (I12); mark's empty ranges need no guards (I13). - IngressChunkResult names an opaque carrier's raw item, which is kept (I3). For a store without shards the window's start moves where main's parted a call from its answer. A probe on main (1fe572e): with the window's edge at the second of two parallel results, each in a message of its own, the compile refused with UncoveredDropError: two messages before the window, in a chunk no summary covered, would have been dropped. Here the window starts at the call, and the call and both results go out raw and paired. Over 600 random stores without shards, 196 of 3,600 window starts differ from main's, each one a start that parted a call from its answer on main and doesn't here; no chunk boundary differs, and 56 of 4,200 head ends differ the same way. Text: the changelog item now names the recent window's start and what is as before (a result in a later message than the one after its call, a close where a pinned message, the head or an earlier chunk interrupts the compressible messages, the Knowledge chunker's last chunk, and the head moving forward to coverage), and the cache line covers a window's edge inside a sharded call. Tests: - body-blocks-in-place: the recent window's start, with its edge on each message inside a call before the message answering it, for each pair (a call stored before; a call whose text follows its tool_use; a call that thinks first); and the head window's end inside a message that thinks, makes a call and answers it itself steps back to its first shard. - split-stitch-l1: the sharded tool round's test asks for a piece that carries the call (a request for fewer than the chunk's four messages), and the plain tool round's fixture gives its result's toolUseId and the host's tool definitions, so its pair reaches the requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…messages that answer it; step the recent window back once; walk only what's asked A call's results can come in several messages after it, as when parallel calls' results come one to a message. callPartedStarts looked one message back, so a chunk could close after the first result, the results after it reached no compression request (a request drops a tool_result whose call isn't in it), and a recent window starting at a later result left the call out: on main, with the window's edge on any result after the first of two to five parallel results, the compile refused with UncoveredDropError (10 of 14 edges), and d4ecc4e still refused at the third of three (Beth-2389's probe, room-203 #210545). Oscar-1005 set the rule for taking this before the measurement (#210552). - callRuns carries a call's open ids across the messages after it that each answer one of them, up to the first message that answers none or makes calls of its own, and marks from the call's start to the shard before the last answering message's last shard. callPartedStarts and closesHeld read it; closesHeld's listing-end holds wait after calls still open, from their start, while a message that may answer them hasn't come or is still being written. openCallIds goes: callRuns gives what it gave. - The recent window's start steps back once, to the start of the call its boundary would part from its results, and doesn't follow a chain of calls (each message answering the call before it and making the next). Following one, a 200-call chain under the Knowledge chunker refused at 3,000 and 6,000 tokens where main compiled (UncoveredDropError): every step adds raw messages to the window. Stepping once, it compiles there, with 15 messages to main's 13; under the autobiographical chunker both refuse there with OverBudgetError, since neither closes inside a chain. The head's end keeps following a chain, since stepping it back shrinks the head. - Cost (Alfred-2570, room-203 #210555): the round walked the whole listing on every write, in closesHeld and in the recent window's start and the head's end, which each ran callPartedStarts over everything. closesHeld now starts at the first message the autobiographical walk asks about, and the window and the head ask callRuns only around their boundary: a pass begins at the last message before the position it answers for that holds no tool_result (a run crosses only messages that answer its calls, so none reaches past that message), and with `until` it ends at the first message from there that leaves no call waiting. Alfred's probe, main and this head side by side on the VPS, one run each: 12,000 messages written in 330 s and 327 s, the last 3,000 of them in 120 s and 121 s, 20 small writes at 12,000 in 274 ms and 307 ms, a long reply with a call and then its result in 104 + 92 ms and 78 + 99 ms, and a compile at 12,000 in 160 ms and 180 ms. One run each can't tell a difference under about 20% from noise. Before this change, in the same conditions, the 20 small writes took 428 ms to main's 253 ms. Measured against main 1fe572e (stores without shards, 600 random ones, both chunkers, two chunk sizes, with and without adaptiveResolution): 363 runs differ in chunks, each first where main closed earlier at a close that parts a call from a result beyond the next message (main's chunks part 639 such pairs, this head's none; taking main's own rule out changes 299 runs); 224 of 4,200 head ends and 196 of 3,600 window starts differ, each where main's parted a call from a result. A Knowledge store compressed on main and reopened here keeps all its summaries, asks for no new one and refuses nothing: Knowledge's close where a summary ends still asks main's question, so the hold shapes only chunks past the summaries. Text: the changelog item names results carried across messages, the window stepping once, chunks and summaries already made kept, and the moved closes in stores without shards; comments at the closes, the head, the split fallback and Knowledge's size close name any answering message. Tests (body-blocks-in-place): - parallel calls' three results, written one by one: one chunk holds the call and all three, under each chunker; - the recent window's start with its edge on any of two, three or five results starts at the call; the head's end with its edge on any of three ends before the call; - in a chain of five calls the recent window's start steps back once; - closesHeld from a position, and callPartedStarts asked around one, agree with the whole listing, for every listing the store passed through as shards landed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eps a call with its parallel results The removal that passes closesHeld the listing's end instead of the autobiographical walk's first message (P20) failed no test: shards land one at a time, so the walk's frontier is short and the pass from the end reaches back over it. A store written while the recent window held everything, then walked once after the window shrinks, has a frontier as long as the store. With parallel calls' three results early in it, the test fails with P20 and at d4ecc4e, and passes here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r and hold count a later parallel result from its call, and a bounded pass stops once its calls are answered Answers the review of the (C) round (cm829-r7): - callRuns kept one set of waiting calls and replaced it whenever a message made calls of its own, so a message answering one of several parallel calls while making another dropped the rest, and their later results were parted from the call (I3). A message that answers some of the waiting calls now adds its own to those still waiting, from the earlier start; once none of the earlier calls waits, its calls start a run of their own. The run still ends at the first message that answers none of the calls: a result that comes much later would otherwise hold every close between (I10, intended). The text says both (I4). - A bounded pass kept walking while `calls` was set, and a result that answered every call left it set, so the head's end and the window's start walked on through every later tool cycle, on every write, as far as the first message that answered none (I8). The pass now ends once no call that started before `until` waits, and `calls` is null whenever none waits. - callStart took the body before a result as its call, so the compression hold on a later parallel result, and the smaller-window floor, started at the result before it, and the Knowledge chunker could summarize the call while its result stayed held (I1, I7). callStart now returns where the calls a result's body answers start, from callRuns, and otherwise the body before as it did. - The Knowledge chunker passes closesHeld the first message its walk asks about, as the autobiographical one does (I11). - Text: the changelog item names how the carry ends, that the Knowledge chunker closes its chunks not yet summarized anew on each pass (I9), and the floor and hold counting a later parallel result from its call; docs/adaptive-resolution-design.md's tail edge cases name the step back for a call and its results (I12); callRuns' doc says the array spans the listing and a chain stretches a pass (I15), and how the head reads the starts (I17); callPartedStarts' doc names a boundary inside a message answering the call (I20). Tests (body-blocks-in-place): - a message answering one of two calls and making its own keeps the other waiting (fails at 2ecba6a); - the smaller-window floor's and the hold's callStart for the third of three parallel results is the call's start, and a hold on that result starts at the call (fails at 2ecba6a); - a message answering none of the calls, a result for another call included, ends their run (I10, I14); - the listing's end holds a close after the first of two results and not after the second (I13); - the recent window's edge test asserts that an edge can fall inside the call (I18); - the walk test compares where the head and the window step back to, from every boundary, over the bounded answer and the whole one (I19). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anarchid
left a comment
There was a problem hiding this comment.
Nightly dual review · round R1 at b1ad54d · reviewers: Codex gpt-5.6-sol (xhigh) and Claude claude-opus-5-5, merged and every claim re-verified by the Claude leg · posted by the maintainer's campaign runner. Verdict: CHANGES_REQUESTED (the formal review event is left to the maintainer). Finding ids R1-n are stable; cite them in fix commits.
Reviewed at b1ad54d against main 1a9e1b7
Verdict: changes requested (0 blockers, 2 majors, 5 minors)
The block-placement half of this PR is careful and well tested: ingress, continuesBlock, joinBodyContent and the region merge all hold up against the code and against probes. Both majors are in the other half, the call/result pairing. A message stored as shards that answers its own call (a bundled cycle) is the shape the description says "moves nothing". At this head it moves the recent window back over the whole unrelated message before it, and it makes tool pruning drop a real result. Neither happens on main. Both fixes are small. Separately, the pairing rework runs whether or not adaptiveResolution is on, and the title doesn't say so.
This is round 1. We have no earlier review on this PR.
Major
R1-1. callStart steps a sharded bundle's result back over the whole body before it, so the recent window, the floor and the hold take in an unrelated message.
src/strategies/autobiographical.ts:18659-18674. callStart asks callPartedStarts only at resultBody - 1. For a body that answers its own call, nothing is carried there, so the fallback runs: it returns the first shard of the previous body that holds a tool_use or thinking, or that body's start when it holds none. It never looks inside the result's own body, where callRuns already marks the call and its result (mark(first, k - 1, first), line 18768). The callers are the recent window's start (:18941-18942, now reached from any shard of the body, through bodyHoldsToolResult), the floor's newest turn (:13328) and holdBoundary (:18865).
- Probe at this head, built
dist: store[User "hi"] [User: a 12,000-character document, 15 shards] [Claude: text 3,000 / tool_use b1 / text 3,000 / tool_result b1 / text 1,500, 10 shards] [User "thanks"], withadaptiveResolution,targetChunkTokens: 200andheadWindowTokens: 0.recentWindowTokenswas sized so the window's start falls on each shard of the bundle in turn, from its second to its last; the first wasn't probed. Every time, the window starts at message 1, the document's first shard: 6,632 tokens, where it was sized for 111 to 2,321. Onmain, the same writes give a window that starts where it was sized, every time. - Why it matters: the window grows by however large the preceding body is, and nothing bounds that. A large enough document before such a message turns a compile that fits into a refusal, and the floor (
clearedBy) then reasons from the inflated turn. The floor and the hold already stepped back to the previous body's start onmain, but only when the result sat on shard 0. The recent window's jump is new. - Fix: in
callStart, answer from the result's own body first. If everytool_resultin it answers atool_useearlier in the same body, return that body's own run start, orresultAtand let thecallPartedStartsstep that follows handle it. Step back into the previous body only when it holds atool_usethat the result answers by id. Add recent-window, floor and hold tests for a sharded bundle that follows a large plain body. The current bundle tests (test/body-blocks-in-place.test.ts:510,:611) cover only the head's end and chunk closes.
R1-2. Tool pruning counts a sharded bundle's own result as an answered call, so with toolResultMaxLastN a real result is pruned for one that isn't sent.
src/strategies/autobiographical.ts:672-700 (answeredToolCalls). The rule says a message's calls wait "from the next message on: pairing repair drops a result in its call's own message". But opened is published to waiting after each stored shard (:697), so a tool_result on a later shard of the same body answers the call. Setting the call's message to bodyStart (:680, this PR) doesn't stop that. On main, the legacy layout kept both blocks on shard 0, one stored message, so the bundle was never counted.
- Probe:
[User] [Claude: text, tool_use real] [User: tool_result real "REAL-RESULT"] [Claude: sharded bundle as in R1-1, result "BUNDLE-RESULT"] [User], withtoolResultMaxLastN: 1and a recent window that holds everything. Onmain, the compile sendsREAL-RESULT. At this head it sends[result of tool 'get_weather' left out: this context shows only the tool's last result]in its place, and the bundle's result isn't sent either, so the compile carries neither. - Fix: collect
openedacross the whole body and publish it atbodyEnd. Results in the body still answer calls that were waiting before it. Add a parity test with one bundle below and above the sharding threshold.
Minor
R1-3. The connectome-host migration in the breaking fragment is incomplete.
changelog.d/829-body-blocks-in-place.breaking.md:28-30 says the Web UI's change is joinBodyContent(shards). But coalesceAndFlatten (connectome-host src/modules/web-ui-module.ts:3777-3782 on main) groups adjacent messages by equal bodyGroupId and sorts the run by shardIndex. Two identical ingestions stored back to back would still go to one joinBodyContent call, and the second body's shard 0, which carries no continuesBlock, would flip the whole input to the legacy join. The fragment should say to split runs with continuesBody (pairwise, store order) and to carry continuesBlock through the Web UI's message type. The grouping itself predates this PR and is a connectome-host bug.
R1-4. Companion changes aren't declared, and there's no merge-order guidance.
CONTRIBUTING ("How changes land") asks for cross-repo companions, which side lands first, and what happens if only one does. Neither agent-framework nor connectome-host has a PR that uses joinBodyContent or continuesBlock. If this ships alone, agent-framework's asCompiled (src/framework.ts:941), which serves its drained deferred writes and its released-window notices, keeps joining new bodies with their other blocks first. Compiles now send those bodies in place, so the two disagree for any sharded body with non-text blocks. agent-framework's StoredCopy also doesn't carry continuesBlock yet, so its side is a little more than the one-line change the fragment describes. Please add a short Companion PRs section, even if it only says "not yet filed; CM can land first because …".
R1-5. The pairing rework applies to every store, not only to adaptiveResolution.
The title and the opening sentence frame this as an adaptiveResolution change. But callRuns, closesHeld and closingPartsCall, the head's end, the recent window's start, the split fallback's groupEnd and the Knowledge chunker's summary close all run whether or not the option is on. The fixed fragment does say so, in its last sentences. Please say it in the title or the first paragraph, or split it into its own PR: it is a separate behaviour change with its own failure modes (R1-1, R1-2), and as one PR the two are hard to bisect apart. One cost belongs in the changelog too. In a probe with targetChunkTokens: 400 and 40 parallel results, each in a message of its own, main closed 11 chunks (parting results from their call), and this head closes one chunk of 45 messages, about 20,000 tokens. Nothing bounds that size, and groupEnd treats the run as indivisible. agent-framework's own layout (a call, then one results message) chunks identically on both, and a chain was already a single chunk on main.
R1-6 (nit). callRuns marking is quadratic in a run's length.
src/strategies/autobiographical.ts:18744-18746 and :18777: each answering message re-marks from calls.from. N parallel results, one to a message, cost about N²/2 writes, while the doc comment calls this one pass. That's harmless at realistic N. Keeping a per-run high-water mark makes it linear.
R1-7 (nit). releaseRawFormsAcrossParts keys parts by block identity.
src/raw-forms.ts:324-325. A block object that appears twice in one message's content and lands on two shards is mapped to its last part only. If it carries a raw form, the form is kept on both shards. This takes contrived input. A Map<ContentBlock, Set<number>> closes it.
Verified fine
- Tests: the full suite passes at the head, 3,292 of 3,292. Merged with today's
main(61 commits past the branch's base), the PR merges cleanly and passes 3,275 of 3,275; the count is lower becausemainreverted #335 and its tests. - Ingress: the chunker's shards are contiguous slices (
src/adaptive/chunker.ts:132), so the span arithmetic holds. Seams,continuesBlockand empty blocks are placed consistently. A mixed declaration is refused before any shard is written, andcontinuesBlocksurvives a reopen. joinBodyContent: it gives back new bodies as given and joins legacy bodies as the windows always did, partial runs included. A middle composite merged with a window's shards of the same body stays one entry.- The automated review's four inline findings are fixed at
fcdf6b44:bodyHoldsToolResultat the window's start, the floor and the hold, and pairing release across parts. - Image strip:
imagesInandreplaceImagestraverse in the same order, so the per-occurrence verdict index lines up._blockHomeis aWeakMap. An image repeated in one message and landing on two shards is stored as two objects, and undermaxLiveImages: 1the newer one is kept. - Provenance: it reads
sourceMessageIdsbeforesourceMessageId, so naming the middle composite by its first shard doesn't change coverage.compositeCoversstill holds for in-place composites. - Pricing: the middle's raw run is priced at the plan's price for each shard (
pseplus the cut's extra), as the windows price theirs.maxMessageTokensdefaults to 0, so the middle's new cut matters only where a host sets a cap belowtargetChunkTokens. - Write path:
ContextManager.addMessageis the only place that writes shards, so no other path dropscontinuesBlock. - Downstream: only agent-framework's
asCompiledand connectome-host'scoalesceAndFlattenjoin shard content. agent-framework's recovery, stored-removal and Discord paths read ids and bounds only. The tool-result-guard link holds: a round's results go in oneusermessage. - Membrane: its native formatter merges adjacent same-role messages (
mergeConsecutiveRoles), as the description assumes. - Issues: #308 and #319 exist as described.
Method. This is a two-reviewer pass. Codex gpt-5.6-sol at xhigh read the diff in a read-only sandbox against this head, with no network: about 15 minutes, 68 shell commands, about 9.9M input tokens (9.5M cached) and 40k output. Alongside it, Claude did the wider pass with network access: it ran the suite at the head and on a merge with main, built main and this head side by side, and ran the probes quoted above against both builds. Each claim from either pass was re-verified before it went in here. Who caught what: R1-1 and R1-2 are Sol's; Claude reproduced both against main and this head and measured them. R1-3 is Sol's, and Claude checked the connectome-host source. R1-4 and R1-5, with the chunk-size and chain probes, are Claude's. R1-6 and R1-7 are Sol's. Sol's other claim, that a repeated image object gets the older shard's verdict, was rejected: once the message is stored, each shard holds its own copy, and the newer one is kept. Sol didn't run the suite or the description's store and timing probes. Neither pass checked the cache and timing figures in the description against a live store, or a live API's handling of a final assistant turn (listed under "Not checked").
…review main brings anima-research#340 (the context observer's revert), 0.22.1, and anima-research#341. The merge is clean in text, but anima-research#340 deleted test/fixtures/over-budget-hook.ts, which reset-fill-order's system-change refusal test imported for assertStage, so the merge didn't type-check (R1-1). That test now matches the refusal's words, as system-changes' does on main. The round answers the review's findings Oscar-1005 placed in it (room-203 #217153): - R1-5: headPositions' doc said every selector sends the head first. Once a reset head is full that holds; while it fills, the hierarchical select walks the window alone and the others pass the empty head sentWindows gives, which the doc now names beside its other empty windows. - R1-8: the two refusals of a head, the adaptive selects' head/tail reservation and the hierarchical head walk, keep their words as the prefix and add that a new reset starts a new head, and this head's conversation becomes history, which compression can fold. The system changes in or before the head go into the next head instead, so the words say conversation. The hierarchical walk adds them wherever it overruns in a head from a reset. The adaptive reservation adds them only where they can't be false (Oscar-1005's call (ii), room-203 #217286): a reset head whose floor fits with the head's system changes beside it, while the head and floor together don't, and whose floor starts at or after the head's end (a compression hold inside the head reaches into it). A filling head is sent as the tail, so its head reservation is empty and the floor holds it: it can't meet that, so the select needs no test of filling there (its removal variant survived, which is how that showed). So a filling head over the budget, a recent window a smaller one clears, and a small reset head beside a big tool result name no reset (Greta-2425's probes, #217258, #217275, #217277). clearedBy names no new lever: a reset starts a new topic. Oscar-1005 kept the transition whole (Oct 23), so this names the way out rather than folding it. Tests: a full reset head over the budget names a reset, and neither a hold inside it nor system changes that don't fit beside the floor do; the filling head names none; nor does the head window disabled after a reset, a recent window a smaller one clears, or a 12-token reset head beside a big tool result; the hierarchical reset-head refusal names it; a head refusal without a reset, under each select, names none, with the newest message past the head. - R1-6: the adaptive retention check's "a change is no turn" skip had no test reaching it since anima-research#298's moved to store order. A test subclass overrides applyImageStripping, which runs after the structural repair and before the check, to take the newest turn's entry out; the compile refuses, and with the skip removed it doesn't (the change after it would stand for the turn). That reaches it through the protected surface, with no new export. R1-2 and R1-3 wait for anima-research#310: this PR goes to draft until anima-research#310 merges, and the round after it merges main, adds the review's first-shard image case, and takes anima-research#310's pull-back over a call's parted results into resetHeadWindowEnd.
… it, and step back only to a call by id Answers the R1 review of this pull request. R1-1: callStart steps back to the calls carried to a result's message across parallel results, or into the message before only for a tool_use there that the result answers by id, from that message's first shard holding a tool_use or a thinking block. A result whose call is in neither place moves no boundary, so a message that answers its own call, on whichever of its shards the two sit, moves none back past its own start. The recent window's start, the smaller-window floor and the compression hold's boundary all ask it. Before, the window stepped back over the whole message before a sharded message that answers its own call, however large. main's window stepped back one message at any result, and its floor and hold a whole message; a compile blanks a result with no call before it as an orphan either way. R1-2: answeredToolCalls adds a message's calls to those waiting for results only after the message's last shard, so a result in its own call's message answers nothing, as for a message stored whole, and toolResultMaxLastN no longer counts it in place of a result that is sent. R1-5: a chunk's close now holds a call only as main's rule does, taken from a message to its shards (closesHeld): through the whole message after the call, every shard of it, and within a message that makes a call and answers it itself, from the call's shard up to its result, matched by id, in one pass over the message's shards. Parallel calls' results one to a message can part after the first, as on main, where the chunk's size closes it. Holding a call with all of its results, as the windows still do, made 40 parallel results of 24,000 characters each one chunk whose request a provider limited to 200,000 tokens refused, and every later chunk's request was refused too, since each carried it raw; compiles at 100,000 and 300,000 tokens refused. A bound worked out from the parts of the compression request was built and dropped after two review rounds, each finding another part of the request it didn't count. In a store without shards chunks now close as on main. callRuns' end-of-listing state, which only the chunkers read, goes with it, and callRuns becomes callPartedStarts. R1-6: callPartedStarts keeps a high-water mark for each run and for a message's own calls, so it marks each position at most once for each. R1-7: releaseRawFormsAcrossParts maps each block to every part holding it, so a block object that sits twice in a message and lands on two shards gives up its raw form at both. R1-3: the breaking fragment says what connectome-host's Web UI needs: carry continuesBlock, take a body's run by continuesBody rather than by an equal bodyGroupId, and join each run with joinBodyContent. Tests: - body-blocks-in-place: a sharded message that answers its own call, after a 12,000-character message: the recent window with its edge on each of the message's shards, the floor and a hold; a result whose call isn't in the message before it; tool pruning with toolResultMaxLastN 1, the message stored whole and as shards; under each chunker, a call whose first result sits on a later shard of a long message, with text after it, stays with every shard of that message, a message that makes calls in turn keeps each with its own result, a result for an earlier message's call releasing none, and a chunk closes between one call's result and the next call, a round's results in one sharded message stay with their calls, and 40 parallel results one to a message close after the first, with every chunk compressed, no request refused for its size, and the context compiling at 100,000 and 300,000 tokens. - body-blocks-in-place: a result on a later shard of the store's first message, with no call before it, leaves the recent window at its edge. - body-instances: the held result's two copies hold the call it answers on their first shards, as the test describes, since callStart now steps back only to a call the result answers by id. - adaptive/ingestion: one block object at both ends of a message gives up its raw form on both shards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hat stay Comments only: the emitted JavaScript and declarations, built without comments, are the same before and after. Of the comments this pull request adds or rewrites, the ones that stay are doc comments on the package's public API (StoredMessage.continuesBlock, joinBodyContent and BodyPart, concatBodyGroups, IngressChunkResult and the chunkIngressMessage contract a subclass overrides, maxMessageTokens, OverBudgetClearedBy, MessageStore's methods) and those an outside API forces, such as the Anthropic API's rule that a tool_use is followed by its tool_result. A comment main had that this pull request rewrote is back to main's words where they're still true, less a sentence or clause the change made false. The comments this pull request added beyond those, the internal helpers' docs and the reasons beside the code, are cut; their reasons are in this pull request's description and in the commit messages that made the code. Comments main has stay, so some reasons beside the code are main's own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Replying to this review. @Anarchid Round 3 is pushed at R1-1, in R1-2, in R1-3. The breaking fragment now says what R1-4. The description has a Companion PRs section. agent-framework's change,
R1-5. You were right that nothing bounded the chunk, and R1-6, in R1-7, in At Written by Beth-2389 (Claude Opus 5.5). — Lottie-2661 |
Anarchid
left a comment
There was a problem hiding this comment.
Nightly dual review · round R2 at 428dfba · reviewers: Codex gpt-5.6-sol (xhigh) and Claude claude-opus-5-5, merged and every claim re-verified by the Claude leg · posted by the maintainer's campaign runner. Verdict: CHANGES_REQUESTED (the formal review event is left to the maintainer). Finding ids R2-n are stable; cite them in fix commits.
Reviewed at 428dfba against main cedab24 (the branch's merge base is 1a9e1b7; main has 24 commits since)
Nightly dual review · round R2 · reviewers: Codex gpt-5.6-sol (xhigh) and Claude claude-opus-5-5, merged, with every claim re-verified by the Claude leg · posted by the maintainer's campaign runner. Finding ids R2-n are stable; cite them in fix commits.
Verdict: changes requested (1 blocker, 1 major, 1 minor)
All seven round-1 findings are fixed. We re-ran round 1's own probes at this head, and the regression tests you added for R1-1 and R1-2 fail without the fix. The blocker comes from main moving under the branch. #314 (renderAsNewest) landed six hours after this head's CI ran. Its tests and docs pin the piecewise middle send that this PR removes, so a merge as it stands turns main red. Merged, the code itself is right: the render and the compile now agree. The major is new at this head. After a head-window reset, a long reply that makes a tool call is now cut in two around the whole pre-reset history. That is reachable from connectome-host's /newtopic.
Round 1
The desk's note that R1 at b1ad54d requested changes holds. It was posted as a comment-type review whose body gave a changes-requested verdict.
| R1 | Disposition | Evidence at this head |
|---|---|---|
R1-1 callStart stepped a self-answering sharded bundle back over the previous body |
Fixed | src/strategies/autobiographical.ts:18576-18600 steps into the previous body only for a tool_use the result answers by id. Round 1's probe now starts the window where it was sized, on every shard of the bundle, except where that falls between the bundle's call and its result. There it starts at the call's shard. It never starts in the 12,000-character message before it. The new tests (test/body-blocks-in-place.test.ts:852) fail on the pre-fix merge 60395e24. |
R1-2 answeredToolCalls published a body's calls per shard |
Fixed | :692-700 publishes opened only after the body's last shard (continuesBody). Round 1's probe sends REAL-RESULT again. The parity test (:914) fails on 60395e24. |
| R1-3 connectome-host migration incomplete | Fixed | The breaking fragment (lines 23-35) now says to carry continuesBlock, to take runs by continuesBody rather than by equal bodyGroupId, and to join with joinBodyContent. That matches connectome-host main's coalesceAndFlatten (src/modules/web-ui-module.ts:3056-3088). |
| R1-4 no companion declaration | Fixed | The description has a "Companion PRs" section: neither is filed, CM lands first, and agent-framework raises its floor. agent-framework main (913afe34) still joins in asCompiled (src/framework.ts:985), and its StoredCopy (:994) has no continuesBlock, as described. Its mid-turn injections go through renderAsNewest (:25878), so they pick up the new form with no change of their own. |
| R1-5 every-store pairing change, unbounded chunks | Fixed | The title and first paragraph now name the window edges. closesHeld (:18661-18688) holds only through the whole message after a call, plus a message's own call up to its result. A differential run over 150 random tool-shaped stores without shards, under both chunkers, gave identical chunks on main and here (1,291 in all). Round 1's 40-parallel-results shape closes 11 and 15 chunks (autobiographical, Knowledge) on both, and its chain and agent-framework shapes chunk identically on both. |
| R1-6 quadratic marking | Fixed | callPartedStarts keeps per-run (calls.marked) and per-body (ownMarked) high-water marks (:18618-18647). A skipped position already carries the same start. |
| R1-7 parts keyed by block identity | Fixed | src/raw-forms.ts:308 is now a Map<ContentBlock, Set<number>>, with a test at test/adaptive/ingestion.test.ts:466. |
Blocker
R2-1. Merged onto today's main, three renderAsNewest tests fail, and #314's doc and unreleased changelog fragment describe the middle send that this PR removes.
#314 merged at 2026-10-26 20:40 UTC. This head's CI ran at 14:34 UTC the same day, so the green checks predate it. The merge has no textual conflict, so nothing prompts a resolution. On the merge of this head with cedab243, the suite gives 3,453 of 3,456. These fail:
test/render-as-newest.test.ts:246"a body too big for the window's recent part … where this version's middle sends it in pieces" (assertspieces > 1at:259):263"a body that starts in the head window and runs on past it … where the compile sends it in pieces" (:274):687"a compression hold, held or timed out, changes nothing a render gives" (:707)
They fail because this PR fixes what they pin. In a probe on the merged build, each of the three scenarios compiles the body as one entry equal to what renderAsNewest returns. So does an assistant body [thinking, text, tool_use, text] under the same windows. On main the same writes compile in 2 or 3 pieces, and that assistant body loses its thinking and its call. The stale prose is all from main:
src/strategies/autobiographical.ts:19970-19971: "Each such message goes throughcappedForSend, apart from a body's shards in the adaptive middle". This PR cuts the middle's shards like any raw message.:19982-19987, the bullet "A body whose shards the compile doesn't send together. The adaptive middle … sends a raw run of a body's shards as their text alone, uncut …". Only its reset clause stays true (R2-2).changelog.d/fix-render-as-newest-null-not-sent.fixed.md:11-16: "The adaptive middle sends its shards' text alone, uncut, and a body across the middle and a window … goes out in more than one entry". This fragment is unreleased, so the next release would ship it next to829-body-blocks-in-place.fixed.md, which says the opposite.
Fix: merge main. Make the three tests assert one entry and render-equals-compile. Cut the doc bullet down to the reset case, and drop the middle exception from the cappedForSend sentence. Amend the #314 fragment, or add a sentence to the 829 fragment saying renderAsNewest now gives these bodies as the compile sends them.
Major
R2-2. After a head-window reset, a long reply that makes a tool call is cut in two around the whole pre-reset history. Its thinking and opening go out before that history, and its call after it.
headWindowEndFrom (src/strategies/autobiographical.ts:18974-18996) steps the head's end back to the start of a call's run (:18987-18990). It then hands the boundary to anchorHeadToCoverage (:18991, body at :19013-19030). When the boundary is unowned and chunk coverage resumes later, the anchor moves the end forward to the first owned message (#122), whatever that splits. Here that message is the reply's second shard. With blocks kept in place, the reply's first shard holds its thinking and no tool_use. So main's own guard (:18984, "predecessor has tool_use → pull back") no longer fires either. After a reset, headPositions (:19070) sends the head first, and middleRanges (:19091) sends the pre-reset history before what follows the head. mergeAdjacentBodyGroupRaw joins only adjacent shards (:15538), so the two pieces stay apart.
- Probe, in the shape of connectome-host's
/newtopic(which callsContextManager.resetHeadWindow):adaptiveResolution,targetChunkTokens: 100,headWindowTokens150 or 300,recentWindowTokens: 300. Store four old messages and compile. Store "NEW TOPIC" and reset the head window to it. Then store user "go", an assistant reply[thinking, ~600-word text, tool_use t1](12 shards), and user[tool_result t1].- At this head, the same output with and without ticks:
u:[NEW TOPIC] u:[go] a:[thinking, BODY…] u:[old 0] a:[old 1] u:[old 2] a:[old 3] a:[…body, tool_use] u:[tool_result] - On
main:u:[NEW TOPIC] u:[go] u:[old 0] … a:[old 3] a:[BODY…] a:[…body] u:["[tool call omitt…"]. The head ends before the reply because its first shard holds thetool_use. The reply stays after the history, in order, but loses its thinking and its call.
- At this head, the same output with and without ticks:
- Why it matters: the model gets the opening of its own reply before the history and before the prompt it answers. Membrane's native formatter merges adjacent same-role messages, so the pre-reset history's last assistant message (
old 3) and the rest of the reply should reach the provider as one assistant turn. That turn holds thetool_usewithout the thinking it began with. We didn't check what a live API does with that when extended thinking is on. A reply qualifies once it is longer than twicetargetChunkTokensand crosses the head's end after a reset. The description and the fixed fragment say a sharded body goes out "wherever its shards render … in one entry", and nothing names this exception. The fragment's "As before: … the head's end moving forward to where chunks resume (#122)" (line 70) undersells it: before this PR, that move didn't split this reply. - The plain-text case is old. A user body across a reset head's end is already split around the history on
main, andmainpins it (test/render-as-newest.test.ts:551). Here it goes out in 2 pieces wheremainsends 3. - Fix: keep the head's end out of a body after a reset. Either re-apply the call-run step (and
bodyBoundsIn) afteranchorHeadToCoverage, or have the anchor refuse a forward target that falls inside a body or a call run and keep the stepped-back boundary. That also closes the plain-body case:551pins. Add the/newtopic-shaped test above tobody-blocks-in-place.test.ts. If you'd rather not touch the anchor here, at least name the reset exception in the description, the fixed fragment and the design doc, and keep it inrenderAsNewest's doc (R2-1).
Minor
R2-3. In a chain of calls, the smaller-window floor counts one call fewer than the recent window keeps, so clearedBy names smaller-recent-window where no smaller non-empty window clears the refusal.
The floor's newest turn (src/strategies/autobiographical.ts:13259-13261) is callStart alone. The recent window's start (:18785-18791) is callStart followed by a callPartedStarts step. The design doc (docs/adaptive-resolution-design.md:401) documents that step for chains: "steps back past the call its edge's result answers to the call before that one".
- Probe (no shards,
adaptiveResolutionon, budget 100 with 20 reserved): four short messages, thenA[text, call c0],B[result c0 of 197 tokens, call c1],C[result c1, call c2],U[result c2]. At this head,recentWindowTokens100,000, 20 and 5 all refuse at "Head/tail reservation exceeded budget" withclearedBy: ['smaller-recent-window']. At 20 and 5 the window starts atB(226 tokens), while the floor counted fromC(29). Only a window smaller thanUitself, which is empty, gets past that stage. Onmain, at 20 and 5 the window starts atCand the compile gets past that stage. Both versions then refuse later, at emission. - Only chain-shaped stores are affected. agent-framework's alternating call/result layout gives the same start for both.
- Fix: have the floor take the same two steps, through one shared helper so they can't drift apart. Add a chain row to
test/overbudget-diagnostics.test.ts.
Verified fine
- Tests: the full suite passes at the head, 3,294 of 3,294, as the description says.
mainatcedab243passes 3,297 of 3,297. This round's tests on the pre-fix merge60395e24fail 16 ofbody-blocks-in-place's 123 and 1 ofadaptive/ingestion's 25, exactly as claimed. - Chunks in stores without shards close as on
main: the 150-store differential above, and round 1's parallel, chain and agent-framework shapes. - The window's start between parallel results: with 40 results one to a message,
mainrefuses at every budget, 1M tokens included (UncoveredDropError, or the tail dropping reserved messages). This head starts the window at the call and compiles at 1M. It refuses only budgets below the run's ~20k tokens. The design doc's tail edge cases say the tail can run past K. callStartedge cases: a body that answers an earlier call and also makes and answers its own steps back to the earlier call through the carried run. A result whose call is two messages back moves no boundary. A self-answering bundle at the store's start is caught by thecallPartedStartsstep that follows.bodyHoldsToolResult/bodyHoldsToolUseread legacy shards and work withadaptiveResolutionoff.428dfba3is comment-only, and the comments it removes are on helpers this PR adds.mainhad no doc on them, andmain's own comments stay.- Downstream: agent-framework
mainand connectome-hostmainstill join shards as the breaking fragment describes. No companion PR exists in either yet. - Design doc,
render.tsandIngressChunkResultdocs match the code at this head.
Method. This is a two-reviewer pass. Codex gpt-5.6-sol at xhigh read the diff in a read-only sandbox at this head, with today's main and the agent-framework and connectome-host main refs on disk and no network. Its first launch stopped at its first step to ask a question and was relaunched with a no-questions preamble. The second run took about 14 minutes and 144 shell commands, with about 6.7M input tokens (6.4M cached) and 34k output. Alongside it, Claude did the wider pass with network access. It ran the suite at the head, on main, on the pre-fix merge with this round's tests, and on a merge of the head with today's main. It ran round 1's probes and the new probes quoted above against main and the head side by side. Each claim from either pass was re-verified before it went in here. Who caught what: R2-1 was found by both independently: Sol from a merge-tree and reading the tests, Claude from the merged suite and the render-versus-compile probe. R2-2 is Sol's: it found the reset split from main's own test. Claude reproduced it, traced it to the #122 anchor, and found that for a reply making a call it is new at this head (main keeps such a reply after the history). R2-3 is Claude's. Both passes reached the same seven round-1 dispositions. Sol couldn't run the suite (its sandbox is read-only) or check live PR state. Neither pass checked a live API's handling of a tool-use turn without its thinking, or the description's cache and timing figures against a live store.
…count the floor's turn as the window does R2-1: main's renderAsNewest tests (anima-research#314) pinned the adaptive middle sending a sharded body in pieces, which this change ends. The three tests now pin the body sent in one entry, as renderAsNewest gives it, and its doc and anima-research#314's changelog fragment name only bodies sent in pieces across a reset head window's end. R2-2: the head window's end stops outside a body that leaves a call waiting for a later result. The loop that stepped it around call runs now also moves a boundary inside such a body to the body's start, and repeats the two steps until neither moves; main's one-message guard runs first, unchanged, and with the step that keeps a call with its result it takes a boundary just after such a body inside it. With blocks in place a reply's call sits on its last shard, so after a head-window reset the head's end could land inside a long reply that thinks and makes a call, and the move to where chunks resume (anima-research#122) then sent its thinking and opening before the history from before the reset and its call after it. A body that answers its own calls gets no such step. Refusing the move to where chunks resume inside a reply, or stepping back after it, kept the reply whole in a probe but left its first shards raw, and the refusal's were chunked out of order once more messages came. The tokens the move counts against its cap no longer count twice the shards the loop stepped back over, so a step back over a long reply doesn't make it give up sooner; the loop reads one call-run pass from the head's start. R2-3: turnStart is where the recent window starts at a boundary, and the smaller-window floor now counts the newest turn from it too. In a chain the floor counted from the call alone and named a smaller window that no non-empty window gives. Tests: - body-blocks-in-place: after a head-window reset, at heads of 150 and 300 tokens, a reply of 12 or more shards that thinks and makes a call goes out in one entry, its blocks in order and its text whole, after the history from before the reset, and a chunk owns its first shard; a reply making three calls answered one to a message goes out in one entry, its blocks in order and its text whole, after the history; a reply that opens with text, with the head's budget ending between its parallel results, goes out whole after the history; where the head's budget reaches past a long body that answers an earlier call and makes and answers its own, the head ends after it; where the head's end steps back to a reply's start, the move to where chunks resume reaches the chunk after its result, and gives up past twice the budget. - render-as-newest: the three tests that pinned a body sent in pieces now pin it sent in one entry, as the render gives it. - overbudget-diagnostics: a chain whose message with a long result the recent window keeps names no smaller window, at windows of 100,000, 20 and 5 tokens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e store's condition The two changelog fragments and renderAsNewest's doc said a reply that makes a call can still go out in pieces across a reset head window's end "in a store whose chunks, made by an earlier version, own it from a later shard". What it depends on is the store's state, not which version wrote it: the chunks begin partway through the reply. This change doesn't write such chunks, and a store main wrote in the review's /newtopic shape at heads of 150 and 300 tokens didn't have them, so the texts now state the condition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R1-6: each call run and a message's own calls kept how far they had marked, so a walk marked each position once rather than a run's whole span again at every later result. That marking is quadratic in a run of parallel results without them, but it costs little: one walk over a listing of 800 parallel results, one to a message, took 0.28 ms with the marks and 0.75 ms without, and writing those 800 results took the same time either way (about 34 s in both, one run each). Nothing a test can read changes with them, so they go. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Replying to this review. @Anarchid This pull request's head is now R2-1. The three R2-2. Before the move to where chunks resume (#122), the head's end now steps out of a body that leaves a call waiting for a later result: a boundary inside such a body moves to the body's start. In your
R2-3. The smaller-window floor now counts the newest turn from where the recent window starts, through one helper, R1-6. The previous round answered R1-6 with two high-water marks in
Without the marks a pass takes longer, but under 2 ms at 800 results, and writing the results takes about as long either way. Removing either mark at From a draft by Beth-2389 (Claude Opus 5.5). — Fiona-2757 |
0.23.1 folded anima-research#314's changelog fragment into CHANGELOG.md and deleted it, and this branch had rewritten that fragment's sentence on where a sharded body goes out in pieces. Its correction of the released entry is now a fragment of its own, 829-render-as-newest-entry.fixed.md. In src/types/strategy.ts, main's new recoverLiteralThinkingWrappedCompression option and this branch's maxMessageTokens doc both stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Anarchid
left a comment
There was a problem hiding this comment.
Nightly dual review · round R3 at fb31126 · reviewers: Codex gpt-5.6-sol (xhigh) and Claude claude-opus-5-5, merged and every claim re-verified by the Claude leg · posted by the maintainer's campaign runner. Verdict: APPROVE (the formal review event is left to the maintainer). Finding ids R3-n are stable; cite them in fix commits.
Reviewed at fb31126 against main f966779 (the branch's merge base is d82ee00, the 0.23.1 release; main has two PRs since, #352 and #353)
Nightly dual review · round R3 at fb31126 · reviewers: Codex gpt-5.6-sol (xhigh) and Claude claude-opus-5-5, merged, with every claim re-verified by the Claude leg · posted by the maintainer's campaign runner. Verdict: APPROVE (the formal review event is left to the maintainer). Finding ids R3-n are stable; cite them in fix commits.
Verdict: approve (0 blockers, 0 majors, 1 minor, 2 nits)
All three round-2 findings are fixed, and this round's new tests fail on the pre-fix merge exactly as the description says. The change does what it claims, and it fixes a live-request defect the description only touches on. On 0.23.1, whenever a long [thinking, text, tool_use] reply is bigger than the recent window, the newest turn goes out without its thinking or its call, and its result is blanked. At this head it goes out whole, thinking first, with the result right after it. What remains is one documentation gap around the reset residual, which is real but already happens on 0.23.1, and two nits. Release note for the maintainer: the breaking fragment means this should ship as a minor (0.24.0). agent-framework and connectome-host both pin ^0.23.1, so they pick it up only when their companion changes raise the floor.
The desk's note that R2 at 428dfba requested changes holds. It was posted as a comment-type review whose body gave a changes-requested verdict.
Round 2
| R2 | Disposition | Evidence at this head |
|---|---|---|
R2-1 renderAsNewest tests/doc/fragment pinned the old middle send |
Fixed | The three tests now assert one entry equal to the render (test/render-as-newest.test.ts). The doc no longer exempts the middle from cappedForSend (src/strategies/autobiographical.ts:20330-20350). #314's unreleased fragment is amended, and changelog.d/829-render-as-newest-entry.fixed.md corrects 0.23.1's entry. This time the merge with today's main is green too (below). |
| R2-2 reset head split a call-making reply around the history | Fixed for stores this head writes; residual documented, one doc gap (R3-1) | headWindowEndFrom steps a boundary inside a body that leaves a call open back to the body's start before the #122 anchor (:19098-19113). Our /newtopic probe (below) sends the reply in one entry after the history at every head budget from 150 to 1,200 tokens. The new tests fail on 885fe042 (6 of 130). |
| R2-3 floor counted a chain's newest turn one call short | Fixed | The floor (:13219) and the recent window (:18903) both go through turnStart (:18676). The three chain rows in overbudget-diagnostics fail on 885fe042 and pass here. |
R1-6 (the quadratic marking in callPartedStarts): e6a0888f takes our round-2 fix back out, with measurements. The walk is again quadratic, but only in the length of a run of parallel results with one result per message. It runs a constant number of times per compile, and the description measures 1.6 ms per pass at 800 results. We accept that trade-off and don't re-raise it.
Minor
R3-1. The reset residual is reachable from every store 0.23.x wrote, but the design doc names only half of it, and no test pins it.
After the body step, anchorHeadToCoverage still moves an unowned boundary forward to the first owned message (src/strategies/autobiographical.ts:19146-19147). In a store whose chunks begin partway through a call-making reply, that message is a later shard of the reply, so after a reset the reply goes out in pieces around the history. The fixed fragment (changelog.d/829-body-blocks-in-place.fixed.md:4-7) and renderAsNewest's doc (:20345-20350) name this case. The design doc doesn't: its Rendering bullet (docs/adaptive-resolution-design.md:225) names only the body that leaves no call waiting, and :405 points back to it.
- It answers the description's open question ("We didn't run whether
mainmakes such chunks in other shapes"): it does. We probed the/newtopicshape with a 19-shard reply[thinking, 7,000 chars, tool_use]and its result, attargetChunkTokens: 100,recentWindowTokens: 300, and heads of 150, 300, 450, 600, 900 and 1,200.- Written at this head: one entry after the history at all six heads, and a chunk owns the reply's first shard.
- Written by 0.23.1: at heads of 450 and above, 0.23.1's chunks begin at the reply's 4th, 5th, 7th and 10th shard. Opened at this head, the reply goes out in 2 entries:
[thinking, tool_use, text]in the head with its result moved after it, then the history, then the rest of the text. 0.23.1 itself sends 3. Every store that already holds such a reply keeps splitting after a reset until the next one.
- This is no regression, since this head sends one entry fewer than 0.23.1. Both fixes round 2 suggested would leave shards raw outside both the head and the chunks, as you showed. So we aren't asking for a code change.
test/body-blocks-in-place.test.ts:1108asserts that a chunk owns the reply's first shard, so nothing pins what the residual does.- Fix: name the second case in the design doc's Rendering bullet. Widen the description's framing from stores written at
428dfbato any store whose chunks begin mid-reply, which includes 0.23.x stores. Optionally add a test that seeds ownership from a later shard and pins the documented 2-entry shape, so a change to the anchor can't make it worse silently.
Nits
R3-2. The breaking fragment doesn't say the migration needs this release. changelog.d/829-body-blocks-in-place.breaking.md:12-34 tells hosts to call joinBodyContent and carry continuesBlock. Both are new here, and both named consumers pin @animalabs/context-manager at ^0.23.1, which a 0.24.0 won't satisfy. Add one clause: "requires this release or later; raise the dependency floor with the change".
R3-3. Two comments no longer say what's true.
test/compile-provenance.test.ts:131is half a sentence ("// text across the shards, the image on shard 0 after its text."). Its first line was deleted.compositeCovers' doc (src/compile-provenance.ts:408-413) dropped "so where a non-text block sits relative to the text is not compared". The code still doesn't compare it: it checks the joined text and the non-text order separately. So a composite that moved atool_useahead of the text it followed, as the legacy join does by design, still reads as unaltered. Keep the second half of that sentence.
Verified fine
- Tests. The full suite passes at the head, 3,543 of 3,543, as claimed. Merged with today's
main(f966779f, which adds #352 and #353), it merges with no conflict and passes 3,585 of 3,585, so R2-1's merge-time breakage doesn't recur. With this head's test files built against885fe042's source,body-blocks-in-placefails 6 of 130 (the reset and head-window tests),overbudget-diagnosticsfails 3 of 166 (the chain rows), andrender-as-newestpasses 64 of 64. All three match the description. - The newest turn keeps its thinking and its call. We ran an agent-framework-shaped live flow: 8 turns, then
go, an 11-shard reply[thinking, text, tool_use], a drain of compression ticks, the result, and a compile. We ran it at recent windows of 100 to 1,500 tokens, heads of 0 and 150, and budgets of 1M, 2,500 and 1,600.- At this head, all 29 compiles that fit send the reply as one entry
[thinking, text, tool_use]immediately before its result. That holds even where chunks own the reply's first 7 or 8 shards. - On 0.23.1, 24 of the 29 send it as text alone, in two entries (one in a single case), with no thinking and no
tool_use, and the result is replaced by text.
- At this head, all 29 compiles that fit send the reply as one entry
- A user body given as
[text, tool_result]. The compile now sends it as given. That shape is invalid on the wire, but membrane 0.8.0'snormalizeToolPairsreorders the turn to results-first (interloper_deferred) before validating. We ran the compiled requests through it, sharded and not, and it accepted every one. agent-framework writes results-only messages (src/framework.tsbuildStoredToolResultContent), and #308's own shape is[tool_result, note], which the new layout keeps. - Ingress and join. Blocks go by text offset (
:2837-2899).joinBodyContentjoins only at declared seams, and falls back to the legacy order when any later part lackscontinuesBlock(src/body-runs.ts:100-127).continuesBlockpersists throughappend's extra fields andtoStored(src/message-store.ts:470,:2115). Shards can't be edited or removed alone (src/message-store.ts:733,:801), so a body's declaration can't drift. The mixed-declaration refusal throws before any shard is written (src/context-manager.ts:637-643).compositeCovershandles composites with several text blocks. - Raw forms.
releaseRawFormsAcrossPartsgives up a Responses reasoning pairing whose members land on different shards. 0.23.1's ingress rebuilt every text block, dropping the text items' raw forms while the reasoning kept its id. This head gives up the pairing whole, which is the id-less shape membrane already sends for history without raw items. - Stores without shards. Chunks are the same as on
main. The window-edge changes, and the one-time prompt rewrite they cause, are disclosed in the description and in the fixed fragment. - Consumers. agent-framework
main(e85c57db) still joins as before inasCompiled, now atsrc/framework.ts:1013; the description's permalink at59d60958is still accurate for that commit. connectome-hostmain(612ec8db)coalesceAndFlatten(src/modules/web-ui-module.ts:3056) also joins as before. Both match the breaking fragment's account. Neither companion PR exists yet. Both consumers pin^0.23.1, so nothing reaches them before they opt in. - Not this PR's. In a run of parallel calls with one result per message, a chunk can close after the first results. The later results then reach no compression request, because
stripUnpairedToolBlocksdrops a result whose call isn't in the request. A probe gave identical results on 0.23.1 and at this head: 3 of 6 results under the autobiographical chunker and 4 of 6 under Knowledge never reach the summarizer. The description discloses it, and it's noted for a separate issue.
Method. This is a two-reviewer pass. Codex gpt-5.6-sol at xhigh read git diff main...HEAD in a read-only sandbox at this head. It had the round-2 review, the author's reply, the description, and agent-framework's and connectome-host's main refs on disk, and no network. It ran about 13 minutes and 82 shell commands, with about 7.8M input tokens (7.5M cached) and 40k output. Its bundled sub-reviewer failed to start, which was harmless. It could not run tests, because the sandbox has no node_modules, or materialize a merge tree. Alongside it, Claude did the wider pass with network access. It ran the suite at the head, on the merge with today's main, and with this head's tests on 885fe042. It ran four probes against 0.23.1 (d82ee000) and the head side by side: the live-flow thinking probe, the /newtopic reset probe (written by either, read at the head), [text, tool_result] through membrane's normalizer, and parallel results through compression. Every claim from either pass was re-verified before it went in here. Who caught what: R3-1 was found by both. Sol found it from the anchor's code and rated it major. Claude reproduced it from stores written by 0.23.1, showed that 0.23.1 itself does worse, and found the design-doc gap, so it is a minor here. R3-2 is Sol's. R3-3 is Claude's. Sol also raised three majors and minors that we did not adopt. The [text, tool_result] order (also raised by Claude) is reordered by membrane, which we confirmed by running its normalizer. The parallel-results compression gap is identical on 0.23.1, so it goes to a separate issue. The quadratic walk was taken out deliberately and measured, so we accept it. Neither pass sent a live API request.
…way through a reply, and pin it Anarchid's R3 review (round R3 at fb31126) approves, with one minor and two nits. This commit answers all three. R3-1: after a head-window reset, a reply that makes a call can go out in pieces around the history from before the reset, in a store whose chunks begin partway through it. The changelog fragment 829-body-blocks-in-place.fixed.md and renderAsNewest's doc named this case. The design doc's Rendering bullet named only the body that leaves no call waiting, and now names both. A new test pins the shape in such a store: two entries, split where the chunks begin. The first, before the history, holds the reply's thinking, its call and the text before that point, with the call's result right after that first entry. The second, after the history, holds the rest of the text. The test builds that store with 0.23.1's head end and chunk closes, from bodies stored before blocks were kept in place. R3-2: the breaking fragment now says that joinBodyContent and continuesBlock require this release or later, so a consumer raises its dependency floor with the change. R3-3: compositeCovers' doc puts back the clause that is still true, that where a non-text block sits relative to the text is not compared, and a test comment whose first half was deleted is deleted whole.
|
Replying to this review. @Anarchid This pull request's head is now R3-1.
R3-2. R3-3. From a draft by Beth-2389. — Fiona-2757 |
With
adaptiveResolutionon, a message that ingress splits into shards now keeps each of its blocks in place across its shards. Wherever its shards render, a compile sends it as it was given, in one entry, with every block in its place and its text blocks with their own boundaries, except in the cases the next paragraph names. A text block cut across two shards comes back as one block of plain text. This is a breaking change for hosts that join a body's stored shards themselves, andjoinBodyContent, now exported, joins them. A body stored before this change goes out as it did in the head and recent windows.After a head-window reset, a body that makes no call and runs across the head's end still goes out in pieces around the history from before the reset, as in 0.23.1. So can a long reply that makes a call, in a store whose chunks begin partway through it, until the next reset, and a store 0.23.1 wrote can hold such chunks. A message other than a user's that answers its own call can also go out as more than one entry: in a probe after a reset, a reply
[thinking, text, tool_use, tool_result, text]went out as two entries,[thinking, text, tool_use, text]and then its result.It also changes something for every store, with or without
adaptiveResolution: the head window's end and the recent window's start now keep a tool call with its results, matched by id, up to the first message after it that answers none of them. Compression's chunks keep a call with the whole message after it, every shard of it, which is 0.23.1's rule for a message applied to its shards. In a store without shards, chunks close as in 0.23.1, and the head's end and the recent window's start move only as the section on such stores below says.Problem
A probe wrote six body shapes and compiled each in four places: the recent window, raw in the adaptive middle, across the middle and the recent window, and the head window. It ran under the default folding and under kv-unified, with no summaries, on 0.23.1 (
d82ee000) and atfb31126e, on Linux with Node 24.22.1. Every run of this pull request's code below is atfb31126e, this head's parent, unless it names another commit, an earlier version of its code or this head's files.fb31126e'ssrcdiffers from this head's only by one comment, insrc/compile-provenance.ts. Every run below, of this pull request's code or of 0.23.1's, was on Linux with Node 24.22.1.In 0.23.1:
[thinking, text, tool_use]goes out as[thinking, tool_use, text], two text blocks go out as one, and[short text, image, long text]goes out as[image, text].[thinking, text, tool_use]goes out as[text], without its thinking or its call.[short text, image, long text]goes out as one text block, without its image. A message[tool_result, long text]goes out as two entries: the placeholder for a result that isn't shown, in place of its result, and then its text.[tool_result, long text]as three.[short text, image, long text], under each folding. Each time, the body went out in more than one entry in 21 of the 30 compiles. In 3 of the 29 compiles after the first, the first entry that differed from the compile before was at or before the body's last entry.At
fb31126e, each of the 48 cases sends the body as it was given, in one entry. In each sweep, the body goes out in one entry in all 30 compiles, and no compile differs from the one before at or before the body.Changes
How shards are stored. Each shard that
chunkIngressMessagemakes now holds its slice of the message's blocks, in order.continuesBlock, which says whether the seam before it split a text block.addMessagerefuses a chunking that declarescontinuesBlockon some shards after the first and not on others, before it writes any shard.How a body is rendered.
joinBodyContent, serves every raw render: the middle's raw run, the kv-unified cache check's rebuild of that run, and the windows' merge. It gives a body stored this way back as it was given. It gives a body stored before this change, which has nocontinuesBlock, as the windows always sent it: its other blocks first, then its text as one block.maxMessageTokensas the windows cut it, and prices each shard as the plan priced it.A tool call and its result at the recent window's start, the smaller-window floor and the compression hold. Where one of these boundaries would fall between a call and its result, it now keeps them together, wherever their shards hold the
tool_useand thetool_result:tool_useor a thinking block.Compression. A chunk's close after a call now waits until the whole message after the call is in the chunk, every shard of it. A message that makes a call and answers it itself keeps the two together across its shards. Where parallel calls' results come one to a message, a chunk can still close after the first result's message, leaving the later results to later chunks than their call. #354 reports that those results then reach no compression request. At the end of the store, a close after a call waits until the message after the call has come.
A long reply that thinks, writes and then makes a call now has its thinking on its first shard and its call on its last, so a chunk can close between them. A probe wrote a 24-shard reply
[thinking, text, tool_use]and its result, and chunked them at a target of 40 tokens. Atfb31126e, the thinking fell in the first chunk, and the call fell in the chunk holding its result: the 7th of 7 under the autobiographical chunker, and the 12th of 13 under the Knowledge chunker. On 0.23.1, the thinking and the call fell in the first chunk and the result in the last. No compression request carried a thinking block, atfb31126eor on 0.23.1. Atfb31126ea request carried the call and its result together, and on 0.23.1 none did.The head window's end and the recent window's start. A call's results are the messages after it that each answer one of its calls, matched by id, up to the first message that answers none of them. A message that answers some of them and makes calls of its own adds its calls to those still waiting.
The head's end, the recent window's start and the split fallback's cuts step around these runs.
Before the move to where chunks resume (Head window ratchet: calibration rises peel the head into late, out-of-order L1s that merge across eras #122), the head's end also steps out of a body that leaves a call waiting for a later result: a boundary inside such a body moves to the body's start. So after a head-window reset, a long reply that thinks and makes a call goes out in one entry after the history from before the reset, unless the store's chunks begin partway through it, as the next bullet says. This answers R2-2 of the review of
428dfba3.A store can hold chunks that begin partway through such a reply, and a store 0.23.1 wrote can. Our own probe, of the
/newtopicshape in the review offb31126e, wrote a reply[thinking, 7,000 characters of text, tool_use], stored as 19 shards, and its result, attargetChunkTokens100 andrecentWindowTokens300. It wrote each store with 0.23.1 (d82ee000) or atfb31126e, as each row says, and compiled it after a reset at each head budget below:fb31126efb31126efb31126e, it went out in one entry after the history, with its result in the entry right after it./newtopicshape in the review of428dfba3with a 12-shard reply, wrote its store at428dfba3, an earlier head of this pull request, and opened it atfb31126e. Its chunks began partway through the reply at a head of 300 tokens, and the reply went out in two entries until the next reset. Written atfb31126eor by 0.23.1, that shape's chunks began at the reply's first shard at heads of 150 and 300 tokens, and the reply went out in one entry.docs/adaptive-resolution-design.md:225),renderAsNewest's doc andchangelog.d/829-body-blocks-in-place.fixed.mdeach name this case.A body that answers its own calls gets no such step. In a probe of the shape in the review of
428dfba3with a reply[thinking, text, tool_use, tool_result, text], the reply went out after the history from before the reset at heads of 150 and 300 tokens, its result in an entry of its own after it. We didn't build a case where the head's end falls inside such a body after its result.Where the head's end steps back over a reply, the move to where chunks resume counts the reply's shards once toward its limit of twice the head's budget.
In a chain, where each message answers the call before it and makes the next, the recent window's start steps back past the call its edge's result answers to the call before that, one call past 0.23.1's step, and no further. The head's end steps back to the chain's start.
The smaller-window floor counts the newest turn from where the recent window starts, through one helper,
turnStart. This answers R2-3.Tool-result pruning orders a sharded reply's calls as one message's, by where their results sit, though the calls sit on different shards. A message's own result counts as no answer, whether the message is stored as shards or not.
renderAsNewest(#314, merged on October 26 and released in 0.23.1) gives a sharded body whole. In 0.23.1, its doc and its changelog entry say the adaptive middle sends a body's text alone, and that a body across the middle and a window goes out in more than one entry. The compile now sends such a body in one entry, as the render gives it. This pull request changes the doc to name only two kinds of body that the compile can still send in pieces: a body that leaves no call waiting for a later result, across a reset head window's end, and a reply that makes a call, in a store whose chunks begin partway through it. A new fragment,changelog.d/829-render-as-newest-entry.fixed.md, corrects 0.23.1's entry, andCHANGELOG.mdkeeps the entry as released. It turns the threerender-as-newesttests that pinned the pieces into tests of one entry. This answers R2-1.A body stored before this change
The probe above wrote its 48 stores on 0.23.1 and compiled them at
fb31126e. 28 compiled byte-identical to what 0.23.1 sent: every case in the head and recent windows, and every body without other blocks raw in the middle. 20 differed from what 0.23.1 sent, each first at the older body: 8 bodies with other blocks raw in the middle, and 12 bodies across the middle and the recent window.A store without shards
A probe wrote 300 random stores without shards: plain text, calls, parallel calls with their results in one message or one to a message, results after text, messages that answer their own call, chains, and results for calls never made. It ran each under both chunkers at targets of 40 and 150 tokens, with
adaptiveResolutionoff and on, on 0.23.1 (d82ee000) and atfb31126e. It compared the chunks, the head window's end at budgets from 50 to 3,200 tokens, and the recent window's start at budgets from 50 to 1,600 tokens.fb31126e. In 256 of them 0.23.1's end parted a call from one of its results andfb31126e's doesn't, and in the other 8 both do.fb31126e, each where 0.23.1's start parted a call from one of its results andfb31126e's doesn't.fb31126edoesn't. In 752 of these, 0.23.1's start parted a call from one of its results andfb31126e's doesn't. In 384 it's the other way round: the message before holds a call whose result comes after the edge's message, which answers none of its calls, and 0.23.1's step back kept that call with its result. In the other 2,240, both starts part a call or neither does.For hosts that join stored shards
The breaking fragment,
changelog.d/829-body-blocks-in-place.breaking.md, says what changes for a host that joins a body's shards itself, and thatjoinBodyContentandcontinuesBlockneed this release or later, so such a host raises its context-manager dependency floor with the change. It names two such callers and the change each needs: agent-framework'sasCompiled(src/framework.ts:996) and connectome-host's Web UIcoalesceAndFlatten(src/modules/web-ui-module.ts:3056).Companion PRs
Neither companion is open yet, and this pull request can merge first. agent-framework's companion needs its context-manager floor raised to the release that carries this. A probe ran agent-framework 0.28.0 (
59d60958) with context-manager 0.23.1 (d82ee000) installed, and then withfb31126e. It injected a long body into a live round at a tool boundary, under a recent window that holds the body and under one smaller than it:[text, image, text], withfb31126e, went into the live round as it was given, and the next request sent it the same way, in one entry, under both windows. With 0.23.1, under the smaller window, the live round carried[image, text], and the next request sent the body as two text entries, without its image.renderAsNewestgave no null for any body the probe injected, and we didn't run a path that reachesasCompiled. We didn't run connectome-host with this pull request.Tests
On Linux with Node 24.22.1, the full suite passes on this head's files, 3,545 of 3,545, from a clean build.
c42388e2's code were each removed in turn, and 10 test files rerun each time:body-blocks-in-place,adaptive/ingestion,tool-pruning,body-instances,tool-pruning-price,l1-recall-raw-pins-frontier,system-changes,split-stitch-l1,render-as-newestandoverbudget-diagnostics. Each removal failed at least one of those tests except the two high-water marks incallPartedStarts, whiche6a0888ftakes out. Atfb31126e, the other 15 were each removed again, and each failed the same number of tests in the same files as atc42388e2. The parts below give what failed without each of them.c42388e2adds or changes, on the merge ofmainbefore it (885fe042): 6 ofbody-blocks-in-place's 130 fail, and 3 ofoverbudget-diagnostics' 166.render-as-newestpasses 64 of 64 there, since that merge already sends the bodies its three tests pin in one entry. The one newbody-blocks-in-placetest that passes there pins where the head ends beside a long body that answers an earlier call and makes and answers its own.fb31126e, on 0.23.1's source (d82ee000):adaptive/ingestiondoesn't load there, since 0.23.1 doesn't exportjoinBodyContent. In the other 17 files, 129 of 918 tests fail, 98 of them amongbody-blocks-in-place's 130. All 943 tests in the 18 files pass atfb31126e.test/body-blocks-in-place.test.tsis new, and holds most of this pull request's tests:/newtopicshape of the review of428dfba3at heads of 150 and 300 tokens, a reply that thinks, writes 7,000 characters of text and makes a call, which the test asserts is stored as at least 12 shards, goes out in one entry after the history, and a chunk owns its first shard. In a probe of that shape with ticks drained, at885fe042a 6-shard reply went out in one entry at both heads, and a 12-shard reply in two entries at a head of 300; atfb31126eboth went out in one entry;anchorHeadToCoverage) taken out, both tests fail, with the reply in one entry. With that move ending one message past the first message a chunk owns, both fail where they check the split;Changed elsewhere:
render-as-newest: the three tests that pinned a body sent in pieces now pin it sent in one entry, as the render gives it.overbudget-diagnostics: the floor counts a chain's newest turn from where the recent window starts, at windows of 100,000, 20 and 5 tokens, and counts a result after a long text with its whole body and its call.adaptive/ingestion: placement, seams, raw forms, plain pieces,continuesBlockthrough the store and a reopen, and the refusal.system-changesandsplit-stitch-l1: a result after a long text, on a later shard of its message, stays with its call.thinking-binding: a thinking-led reply long enough to shard keeps its thinking when the host notes it.tool-pruning-price: a sharded reply's calls are ordered as one message's.body-run-price,live-image-accounting,capped-message-price,whole-body-pins,kv-unified-render-state,kv-unified-marker-boundaries,compression-hold,calibration-bandandbody-instances. Two comments incompile-provenancethat described the old layout are gone.What each part is for
continuesBlockkeeps text-block boundaries across a seam, and tells a body stored before this change apart from one stored this way.joinBodyContentis the one join every raw render uses. In 0.23.1 the windows and the middle sent the same body differently, as the probe above shows. Hosts can call it too.Its branch for a body stored before this change joins such a body as the windows always did, which keeps the 28 byte-identical cases above. Where an older body's blocks sat wasn't stored, so its shards can't be rewritten into the new layout.
Recording each block's shard for the image strip reuses, renamed, the map that pairing repair kept in 0.23.1 for a moved result.
Pricing the middle's run as the plan priced it, and cutting its shards as the windows do, leaves the middle no estimate or cut of its own.
Naming the middle's first shard lets the merge join a body across the middle and a window into one entry.
callStartis one helper at the recent window's start, the floor and the hold. It steps into the message before only for a call there that the result answers by id. Stepping in for any call there failed 4body-blocks-in-placetests.turnStartis where the recent window starts at a boundary, and the floor counts the newest turn from it too, so the two can't drift apart. With the floor counting fromcallStartinstead, 3overbudget-diagnosticstests failed.The body step in the head's loop moves a boundary inside a body that leaves a call waiting to the body's start, unless the body starts before the head does. Without it, 6
body-blocks-in-placetests failed. The loop reads onecallPartedStartspass from the head's start.body-blocks-in-place's tests.body-blocks-in-placepins where the head ends there.Counting the reply's shards once keeps the move to where chunks resume giving up at 0.23.1's count. Without it, 1
body-blocks-in-placetest failed./newtopicshape of the review of428dfba3with each applied to this branch before its R2-2 fix, both kept the reply whole but left its first one or two shards in no chunk, raw outside the head. In a longer probe of the refusal, added to an earlier version of this round, in a store whose chunks began partway through the reply, its raw shards were chunked after the chunks that follow them.closesHeldis 0.23.1's rule for a chunk's close, a call held with the message after it, applied to every shard of that message, plus a message's own call held up to its own result, by id. It holds the end of the store too. Without the hold through the whole message after a call, 24body-blocks-in-placetests and 1system-changestest failed. Without the hold on a message's own call while it waits, 17body-blocks-in-placetests failed. Matching the call's result other than by id, or holding every shard after the message's first call, failed 3 each. Without the hold at the end of the store, 16 failed. With a close that asks only 0.23.1's question, whether the chunk's last message holds atool_use, in place ofclosesHeld, 12body-blocks-in-placetests failed where a boundary hint closes a chunk, 28 at the autobiographical chunker's own close, and 12 and 1system-changestest at the Knowledge chunker's close.The Knowledge chunker's close where a summary ends asks 0.23.1's question of each message whole.
callPartedStartssays in one pass where a boundary would part a call from any of its results, matching results to calls by id. The head's end,turnStart,callStartand the split fallback read it. Itsfromanduntilstart a pass at the last message before the position asked about that holds notool_result, and end it once no call that started beforeuntilis waiting.b1ad54d, ande6a0888ftakes them out. In a run of 800 parallel results, one to a message, written with them (def320c7) and without them (e6a0888f) side by side, one run each, onecallPartedStartscall over all of the store's messages took 0.21 ms with them and 1.58 ms without (each the time of 20 calls and one read of the messages, divided by 20), and writing the 800 results took 44.4 s with them and 44.3 s without. Removing either atc42388e2failed none of the 10 test files rerun.The recent window stepping back no further than the call before its edge's call keeps a chain of calls from taking the window back to the chain's start.
answeredToolCallswaiting for a body's end before its calls wait for results keeps a sharded message's own result from counting as an answer. Without it, 1body-blocks-in-placetest failed.Releasing raw forms across shards (
releaseRawFormsAcrossParts) keeps one shard from replaying, through a raw form, what another shard holds. It records each block's shards as a set, so one block object that sits twice in a message gives up its raw form at both. Recording only one shard per block failed 1adaptive/ingestiontest.Refusing a mixed
continuesBlockdeclaration keeps one rule for telling the two layouts apart.bodyHoldsToolResultchecks every shard of a boundary's message for a tool result, at the three boundaries and at the Knowledge chunker's close where a summary ends.Placing a sharded reply's calls at its first shard for tool-result pruning is one argument.
The test scaffolding in
body-blocks-in-place:withPair,PAIRS,STRATEGIESandheadAtrun each way a call and its result sit on shards through each chunker, the head, the recent window and compression;parallelandwithWriteswrite a call's results one to a message;withProviderwrites a store through a stand-in provider that refuses requests over 200,000 tokens, andansweredWithCallreads which results share the call's chunk;withStoreopens a store withMEMBRANE, a stand-in summarizer, so a reset, a compile and ticks run as a host's would;resetThenReplywrites the history, the reset and a reply with its results, and sets the head's budget to end where a test asks;entriesWithlists the compiled entries holding a kind of block, andhistoryAtfinds the history from before the reset.In
split-stitch-l1,build'sshardedResultoption writes a result after a long text, and withtoolRoundit gives the host's tool definitions.test/_harness/legacy-ingress.tsis the one copy of the older stored layout that the tests of older bodies build on.Weighed and not taken: keeping the old layout and having only the middle send every block. That smaller change would have needed no stored field and no breaking change, but it leaves the windows sending a body's other blocks ahead of its text, as the probe above shows on 0.23.1. We took the full change because the order of a message's blocks carries meaning.
Not verified
callPartedStartsabove timed writes of that one shape, and nothing here timed a compile. No test counts the work a walk does.changelog.d/829-body-blocks-in-place.breaking.md,changelog.d/829-body-blocks-in-place.fixed.mdandchangelog.d/829-render-as-newest-entry.fixed.md.Beth-2389 wrote this change, and Ezra-2423 wrote seven of its commits: a merge of
main, five that keep a call with its results at the window edges and in compression, and a test of them. Oscar-1005 chose among the candidate fixes for R2-2. Cedric-2315 read each earlier round of its code and the rebase's resolution. Alfred-2570 read the earlier rounds, read an earlier version of the fixes for R2-1 to R2-3 with probes of his own, and ran his R2-2 probes again on the source ofc42388e2. Greta-2425 weighed each part's cost against what it buys, and read an earlier answer to the automated review and its fixes. Phoebe-2419 read what the change does for a model reading its own context. The house's internal review ran 14 times on this pull request, the last on the source ofc42388e2with an earlier version of one test file, and the findings judged worth fixing in the code and tests are fixed at this head.Fiona-2757 wrote this description, from Beth-2389's drafts.