Skip to content

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

Open
nissa-seru wants to merge 23 commits into
anima-research:mainfrom
nissa-seru:beth/829-body-blocks-in-place
Open

nissa-seru wants to merge 23 commits into
anima-research:mainfrom
nissa-seru:beth/829-body-blocks-in-place

Conversation

@nissa-seru

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

Copy link
Copy Markdown
Contributor

With adaptiveResolution on, 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, and joinBodyContent, 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 at fb31126e, on Linux with Node 24.22.1. Every run of this pull request's code below is at fb31126e, this head's parent, unless it names another commit, an earlier version of its code or this head's files. fb31126e's src differs from this head's only by one comment, in src/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:

  • The head and recent windows send a body's other blocks ahead of its text, and its text blocks as one. A reply [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].
  • The adaptive middle sends a raw body's text alone. A reply [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.
  • A body across the middle and the recent window goes out as two entries, and [tool_result, long text] as three.
  • A 30-turn sweep moved the recent window's edge through a body, one compile per turn, for a text body and for [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 chunkIngressMessage makes now holds its slice of the message's blocks, in order.

  • A text block keeps its own boundaries, and is cut where a seam falls inside it. The pieces of a cut text block are plain text.
  • Every other block goes on the shard that holds its place in the text, so a reply that ends in a call has the call on its last shard.
  • Each shard after the first stores continuesBlock, which says whether the seam before it split a text block.
  • A raw replay form is kept only where all its blocks land, unchanged, on one shard, and given up otherwise. So is a reasoning pairing.
  • addMessage refuses a chunking that declares continuesBlock on some shards after the first and not on others, before it writes any shard.

How a body is rendered.

  • One join, 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 no continuesBlock, as the windows always sent it: its other blocks first, then its text as one block.
  • The middle's raw run sends each shard as any raw message is sent, cut at maxMessageTokens as the windows cut it, and prices each shard as the plan priced it.
  • A body whose shards fall in the middle and a window goes out as one entry.
  • The image strip judges each image at the shard that holds it, in both strip paths, through the merge, and through pairing repair's copies.

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_use and the tool_result:

  • At a message whose result answers a call in the message before it, the boundary starts at that call's first shard holding a tool_use or a thinking block.
  • At a later one of parallel calls' results, each in a message of its own, it starts at the call those results answer.
  • A message that answers only its own calls moves none of the three back past its own start. The recent window at a result whose call is in neither place starts at its edge.
  • A result written after a long text sits on its message's last shard, and each boundary looks at every shard of the message. This fixes The recent window can start inside a split tool-result message, leaving the result and its call out of the raw tail #308.

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. At fb31126e, 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, at fb31126e or on 0.23.1. At fb31126e a 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 /newtopic shape in the review of fb31126e, wrote a reply [thinking, 7,000 characters of text, tool_use], stored as 19 shards, and its result, at targetChunkTokens 100 and recentWindowTokens 300. It wrote each store with 0.23.1 (d82ee000) or at fb31126e, as each row says, and compiled it after a reset at each head budget below:

    Head budget (tokens) 150 300 450 600 900 1,200
    The reply's first shard a chunk owns, in 0.23.1's store 1st 3rd 4th 5th 7th 10th
    Entries holding the reply, written by 0.23.1 and opened at fb31126e 1 2 2 2 2 2
    Entries holding the reply, written and compiled by 0.23.1 2 3 3 3 3 3
    Entries holding the reply, written and opened at fb31126e 1 1 1 1 1 1
    • Where it went out in two entries, the first, before the history from before the reset, held the reply's thinking, its call and the text of the shards before the first one a chunk owns (738, 1,107, 1,476, 2,214 and 3,321 characters), and the reply's result came in the entry right after that first one. The second, after the history, held the rest of the text. It went out this way after compression ticks and after four later messages. After a second reset and four more messages, it went out in one entry at every head budget, with its result in the entry right after it.
    • At a head budget of 150, written by 0.23.1 and opened at fb31126e, it went out in one entry after the history, with its result in the entry right after it.
    • 0.23.1 itself, at a head budget of 150, sent the reply's text alone, in two entries, and no entry held its thinking, its call or its result. At the other head budgets it sent the reply in three entries.
    • An earlier probe, of the /newtopic shape in the review of 428dfba3 with a 12-shard reply, wrote its store at 428dfba3, an earlier head of this pull request, and opened it at fb31126e. 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 at fb31126e or 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.
    • The design doc's Rendering bullet (docs/adaptive-resolution-design.md:225), renderAsNewest's doc and changelog.d/829-body-blocks-in-place.fixed.md each name this case.
  • A body that answers its own calls gets no such step. In a probe of the shape in the review of 428dfba3 with 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, and CHANGELOG.md keeps the entry as released. It turns the three render-as-newest tests that pinned the pieces into tests of one entry. This answers R2-1.

A body stored before this change

  • In the head and recent windows it goes out as it did.
  • Raw in the middle it now sends its other blocks, ahead of its text, as the windows always sent it.
  • Across the middle and a window it goes out as one entry.
  • The recent window's start at a result whose call is such a body starts at the body's first shard.

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 adaptiveResolution off and on, on 0.23.1 (d82ee000) and at fb31126e. 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.

  • Chunks: all 2,400 runs gave 0.23.1's chunks, 9,030 in all.
  • The head window's end: 264 of 16,800 differ, each ending earlier at fb31126e. In 256 of them 0.23.1's end parted a call from one of its results and fb31126e's doesn't, and in the other 8 both do.
  • The recent window's start: 3,544 of 14,400 differ.
    • 168 start earlier at fb31126e, each where 0.23.1's start parted a call from one of its results and fb31126e's doesn't.
    • 3,376 start one message later, each at a message holding a result that answers no call in the message before it. 0.23.1 stepped back a message there, and fb31126e doesn't. In 752 of these, 0.23.1's start parted a call from one of its results and fb31126e'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 that joinBodyContent and continuesBlock need 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's asCompiled (src/framework.ts:996) and connectome-host's Web UI coalesceAndFlatten (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 with fb31126e. 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:

  • A body that makes a call and answers it itself went into no live round, with either.
  • A body [text, image, text], with fb31126e, 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.

renderAsNewest gave no null for any body the probe injected, and we didn't run a path that reaches asCompiled. 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.

  • Removals: 17 pieces of 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-newest and overbudget-diagnostics. Each removal failed at least one of those tests except the two high-water marks in callPartedStarts, which e6a0888f takes out. At fb31126e, the other 15 were each removed again, and each failed the same number of tests in the same files as at c42388e2. The parts below give what failed without each of them.
  • The tests c42388e2 adds or changes, on the merge of main before it (885fe042): 6 of body-blocks-in-place's 130 fail, and 3 of overbudget-diagnostics' 166. render-as-newest passes 64 of 64 there, since that merge already sends the bodies its three tests pin in one entry. The one new body-blocks-in-place test that passes there pins where the head ends beside a long body that answers an earlier call and makes and answers its own.
  • The 18 test files this pull request adds or changes, as they were at fb31126e, on 0.23.1's source (d82ee000): adaptive/ingestion doesn't load there, since 0.23.1 doesn't export joinBodyContent. In the other 17 files, 129 of 918 tests fail, 98 of them among body-blocks-in-place's 130. All 943 tests in the 18 files pass at fb31126e.

test/body-blocks-in-place.test.ts is new, and holds most of this pull request's tests:

  • five shapes in four places under both foldings, each sent as given in one entry, and bodies stored before this change in each place;
  • four ways a call and its result can sit on shards, each run through the recent window's start, the head window's end and each chunker, and end to end to a compression request that carries the call and its result together;
  • the recent window's start and the hold at a result after a long text, and the recent window's start, the floor and the hold at a later one of parallel results and at a message that answers its own call;
  • the head window's end at a call and the message answering it, with and without shards, in chains, and between parallel results. After two bundles that each answer their own call, the head ends where 0.23.1 ends it, after the first;
  • after a head-window reset, in the /newtopic shape of the review of 428dfba3 at 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, at 885fe042 a 6-shard reply went out in one entry at both heads, and a 12-shard reply in two entries at a head of 300; at fb31126e both went out in one entry;
  • in two tests new at this head, after a head-window reset, in a store built with 0.23.1's head end and chunk closes from bodies stored before this change, at heads of 450 and 900 tokens, where the store's chunks begin partway through the reply: the reply goes out in two entries, split where the chunks begin, one before the history and one after, with its result right after the first. In a run of these tests on this head's files with one logging line added, the chunks began at the reply's 4th and 7th shards, and the two entries held 1,107 and 5,893 characters, and 2,214 and 4,786, as in the stores 0.23.1 wrote in the probe above. On this head's files, with the move to where chunks resume (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;
  • after a reset, a reply that thinks and makes three calls, answered one to a message, goes out in one entry after the history, and so does a reply that opens with text where the head's budget ends between its parallel results;
  • where the head's end steps back to a reply's start and chunks begin after its result, the head ends where chunks resume, and at a budget whose double falls short of that, at the reply's start;
  • 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 that body.

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, continuesBlock through the store and a reopen, and the refusal.
  • system-changes and split-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.
  • Updated where they pinned the old layout: body-run-price, live-image-accounting, capped-message-price, whole-body-pins, kv-unified-render-state, kv-unified-marker-boundaries, compression-hold, calibration-band and body-instances. Two comments in compile-provenance that described the old layout are gone.

What each part is for

  • continuesBlock keeps text-block boundaries across a seam, and tells a body stored before this change apart from one stored this way.

  • joinBodyContent is 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.

  • callStart is 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 4 body-blocks-in-place tests.

  • turnStart is 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 from callStart instead, 3 overbudget-diagnostics tests 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-place tests failed. The loop reads one callPartedStarts pass from the head's start.

    • Cut: a separate guard that stepped back before a body leaving a call waiting just before the boundary. In an earlier version of this round, with the body step in place, removing it failed none of body-blocks-in-place's tests.
    • Cut: a guard that stepped back before any body holding a call. It ended the head a whole body short of its budget, before a long body that answers an earlier call and makes and answers its own, and in probes of an earlier version of this round, no compile showed that body's earlier call parted from its result. The last test in body-blocks-in-place pins 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-place test failed.

    • Weighed: refusing that move when it would land inside a body, and applying the body and call-run steps again after it. In a probe of the /newtopic shape of the review of 428dfba3 with 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.
  • closesHeld is 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, 24 body-blocks-in-place tests and 1 system-changes test failed. Without the hold on a message's own call while it waits, 17 body-blocks-in-place tests 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 a tool_use, in place of closesHeld, 12 body-blocks-in-place tests failed where a boundary hint closes a chunk, 28 at the autobiographical chunker's own close, and 12 and 1 system-changes test at the Knowledge chunker's close.

    • Weighed: holding a call with all of its results in one chunk, as the windows do. The test with 40 parallel results of 24,000 characters, one to a message, pins that every chunk compresses with none refused for its size.
  • The Knowledge chunker's close where a summary ends asks 0.23.1's question of each message whole.

  • callPartedStarts says 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, callStart and the split fallback read it. Its from and until start a pass at the last message before the position asked about that holds no tool_result, and end it once no call that started before until is waiting.

    • Cut: two high-water marks, which let a walk mark each position at most once for each run and each message's own calls. An earlier round added them for R1-6 of the review of b1ad54d, and e6a0888f takes 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, one callPartedStarts call 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 at c42388e2 failed 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.

  • answeredToolCalls waiting 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, 1 body-blocks-in-place test 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 1 adaptive/ingestion test.

  • Refusing a mixed continuesBlock declaration keeps one rule for telling the two layouts apart.

  • bodyHoldsToolResult checks 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, STRATEGIES and headAt run each way a call and its result sit on shards through each chunker, the head, the recent window and compression;
    • parallel and withWrites write a call's results one to a message;
    • withProvider writes a store through a stand-in provider that refuses requests over 200,000 tokens, and answeredWithCall reads which results share the call's chunk;
    • the head tests' withStore opens a store with MEMBRANE, a stand-in summarizer, so a reset, a compile and ticks run as a host's would;
    • resetThenReply writes the history, the reset and a reply with its results, and sets the head's budget to end where a test asks;
    • entriesWith lists the compiled entries holding a kind of block, and historyAt finds the history from before the reset.

    In split-stitch-l1, build's shardedResult option writes a result after a long text, and with toolRound it gives the host's tool definitions. test/_harness/legacy-ingress.ts is 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


  • Changelog fragment added: changelog.d/829-body-blocks-in-place.breaking.md, changelog.d/829-body-blocks-in-place.fixed.md and changelog.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 of c42388e2. 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 of c42388e2 with 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.

… 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>

@github-actions github-actions Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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. joinBodyContent sits in body-runs.ts beside continuesBody, bodyStart and bodyBounds. It is exported on the same index.ts line as those helpers. Ingress chunking stays in chunkIngressMessage, 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.md and a .fixed.md fragment. The breaking fragment names agent-framework's asCompiled and connectome-host's coalesceAndFlatten, 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:

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. joinBodyContent returns 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 same LegacyIngress override, which rebuilds the old shard layout from super.chunkIngressMessage, appears in test/body-blocks-in-place.test.ts, test/overbudget-diagnostics.test.ts and test/tool-pruning-price.test.ts (as LegacyIngressStrategy). 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.

Comment thread src/strategies/autobiographical.ts Outdated
Comment on lines 18033 to 18035
if (boundary > 0 && boundary < messages.length && this.hasToolResult(messages[boundary])) {
boundary--;
boundary = this.callStart(messages, boundary);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/strategies/autobiographical.ts Outdated
Comment on lines 12915 to 12918
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/strategies/autobiographical.ts Outdated
// 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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/raw-forms.ts Outdated
Comment on lines +323 to +340
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Replying to this review.

fcdf6b44 answers all six findings, the four inline ones in their threads.

  • The design doc's "in every region" (low): it now says an older body's shards render as the windows joined them wherever two or more render together, and that a shard rendered alone goes out as stored.
  • The copied LegacyIngress helper (low): it's one helper now, test/_harness/legacy-ingress.ts, used by the three test files.

Two changes in the same commit don't come from your review. A chunking that declares continuesBlock on its first shard is no longer refused, since no join uses that declaration, while one that declares it on some later shards and not others still is. And the floor now counts a result message that is the store's first message with its whole body.

The full suite passes at fcdf6b44, and removing each code fix for the four inline findings fails at least one test.

Written by Beth-2389.

— Lottie-2661

@nissa-seru

Copy link
Copy Markdown
Contributor Author

@antra-tess @Anarchid We're asking you both to review this PR at fcdf6b44 before we merge it. It changes what the context strategies send a model for every message long enough to be split into shards, and it's a breaking change for hosts that join a body's stored shards themselves.

What it changes for hosts and their users:

  • With adaptiveResolution on, a split message now goes out as it was given, in one entry, wherever its shards render. In a probe on main at c17e7c3d, the head and recent windows put its other blocks ahead of its text, and the adaptive middle sends its text alone, so a reply's tool call and any image go unsent there, and a split tool result reads as a result that isn't shown.
  • New shards store their slice of the message's blocks in order. A host that joins stored shards itself moves to joinBodyContent, now exported. The two we know of are agent-framework's asCompiled and connectome-host's Web UI, and "For hosts that join stored shards" gives each one's change.
  • A store written before this change can miss the prompt cache once, at its first request after the upgrade, from the first older split message that now renders differently. "Bodies stored before this change" lists those cases, with a probe's counts.
  • It also fixes The recent window can start inside a split tool-result message, leaving the result and its call out of the raw tail #308.

As of 2026-10-22 22:01 UTC it merges with main (4e0959a8) without conflicts.

Written by Beth-2389.

— Lottie-2661

Beth-2389 and others added 3 commits October 23, 2026 22:26
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>
@nissa-seru

Copy link
Copy Markdown
Contributor Author

@antra-tess @Anarchid 757e5016 merges main (c8119361) into this branch, three commits on fcdf6b44, the head we asked you to review. We're asking you to review it at 757e5016 before we merge it. The merge commit's resolution and the two commits after it are what changed: git diff fcdf6b44 757e5016 also shows everything main brought in.

  • The merge, d95fb9ed: one conflict, in autobiographical.ts, at the adaptive plan's price for a middle message. fix(pins): every reader reads a pin over a long message whole, and the plan's over-budget refusal names the pins #330 moved that price into middlePrice, which the plan's chunks and the over-budget refusal's rawPinsDiagnostics share, and priced a shard uncut there, as main's shard path sent it. This branch's shard path cuts each raw shard as it cuts any raw message, so the resolution keeps the shared middlePrice and prices a shard the same way: the refusal now prices raw pins as the plan and the shard path do.
  • 4ab977c5: three comments that still described the middle's shard path as sending a shard's text.
  • 757e5016: #​330's test "prices a marked message's parts uncut, …" asserted the uncut price, which this branch ends. It now asserts that the mark covers every part and is priced at 400 to 450 tokens for each, each part cut at its cap of 300.

The full suite passes at 757e5016, and removing each piece of the change fails at least one test, middlePrice's shard rule included.

Written by Beth-2389 (Claude Opus 5.5).

— Lottie-2661

Beth-2389 and others added 2 commits October 23, 2026 22:58
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>
@nissa-seru

Copy link
Copy Markdown
Contributor Author

@antra-tess @Anarchid 7236560d merges main again, now at 4fbe6c94 with #331, the thinking binding. It's two commits on 757e5016. We're asking you to review it at 7236560d before we merge it.

  • The merge, 8953ff86: one conflict, in autobiographical.ts's imports, where #​331 and this branch each added names. The resolution takes both.
  • 7236560d: a test beside #​331's test of a sharded reply that isn't noted. A reply that leads with its thinking and is long enough to shard 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, and the noted render and the compile join its shards alike.

The full suite passes at 7236560d, and removing each piece of the change fails at least one test.

Written by Beth-2389 (Claude Opus 5.5).

— Lottie-2661

Ezra-2423 and others added 7 commits October 25, 2026 02:08
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 Anarchid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"], with adaptiveResolution, targetChunkTokens: 200 and headWindowTokens: 0. recentWindowTokens was 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. On main, 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 on main, 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 every tool_result in it answers a tool_use earlier in the same body, return that body's own run start, or resultAt and let the callPartedStarts step that follows handle it. Step back into the previous body only when it holds a tool_use that 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], with toolResultMaxLastN: 1 and a recent window that holds everything. On main, the compile sends REAL-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 opened across the whole body and publish it at bodyEnd. 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 because main reverted #335 and its tests.
  • Ingress: the chunker's shards are contiguous slices (src/adaptive/chunker.ts:132), so the span arithmetic holds. Seams, continuesBlock and empty blocks are placed consistently. A mixed declaration is refused before any shard is written, and continuesBlock survives 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: bodyHoldsToolResult at the window's start, the floor and the hold, and pairing release across parts.
  • Image strip: imagesIn and replaceImages traverse in the same order, so the per-occurrence verdict index lines up. _blockHome is a WeakMap. An image repeated in one message and landing on two shards is stored as two objects, and under maxLiveImages: 1 the newer one is kept.
  • Provenance: it reads sourceMessageIds before sourceMessageId, so naming the middle composite by its first shard doesn't change coverage. compositeCovers still holds for in-place composites.
  • Pricing: the middle's raw run is priced at the plan's price for each shard (pse plus the cut's extra), as the windows price theirs. maxMessageTokens defaults to 0, so the middle's new cut matters only where a host sets a cap below targetChunkTokens.
  • Write path: ContextManager.addMessage is the only place that writes shards, so no other path drops continuesBlock.
  • Downstream: only agent-framework's asCompiled and connectome-host's coalesceAndFlatten join 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 one user message.
  • 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").

nissa-seru pushed a commit to nissa-seru/context-manager that referenced this pull request Oct 26, 2026
…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.
Beth-2389 and others added 2 commits October 26, 2026 05:28
… 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>
@nissa-seru nissa-seru changed the title fix(shards): a sharded body keeps each block in place and goes out as given, wherever its shards render 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 Oct 26, 2026
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Replying to this review.

@Anarchid Round 3 is pushed at 428dfba3f. It answers R1-1 to R1-7: 60395e24a merges main (1a9e1b71), 044d007d4 holds the fixes and their tests, and 428dfba3f changes comments only. Thank you for the probes: R1-1's and R1-2's are now tests that fail without the fixes.

R1-1, in 044d007d4. callStart now steps into the message before only for a call there that the result answers by id, or back across parallel results to the calls they answer. So a message that answers its own call moves no boundary back past its own start. That changes one case on main too: a result whose call isn't in the message before it, nor among parallel calls' results before it, moves no boundary. By our reading of main, its recent window stepped back one message there, and its floor and hold a whole message, and a compile blanks such a result as an orphan either way. New tests take a sharded message that answers its own call, after a 12,000-character message, and check the recent window with its edge on each of the message's shards, the floor and the hold. Another test covers a result with no call before it.

R1-2, in 044d007d4. answeredToolCalls adds a message's calls to those waiting only after the message's last shard, so a result in its own call's message answers nothing however the message is sharded. The new test sends your shape with toolResultMaxLastN: 1, with the message stored whole and as shards, and in both the earlier call's result is the one sent.

R1-3. The breaking fragment now says what coalesceAndFlatten needs: carry continuesBlock on its message type, take a body's run by continuesBody rather than by equal bodyGroupId, and join each run with joinBodyContent.

R1-4. The description has a Companion PRs section. agent-framework's change, asCompiled to joinBodyContent(landed) with StoredCopy carrying continuesBlock, needs its context-manager floor raised to the release that carries this, so this can land first. We ran agent-framework 0a4a2c69 against main and against this head:

  • A body holding a call and its result is never injected mid-turn there, since isInjectableMidTurn holds any message with tool blocks back to the turn's end.
  • A long body with an image, under a window smaller than it, goes into the live round in asCompiled's order, where the next request sends it as given. The prompt is rewritten from that body at the next request, as it was on main. Nothing is parted, doubled or refused.

R1-5. You were right that nothing bounded the chunk, and 044d007d4 narrows what compression holds to what the layout needs. A chunk's close after a call now waits until the whole message after it is in the chunk, every shard of it, and a message that makes a call and answers it itself keeps the two in one chunk across its shards. That's main's rule for a message holding a tool_use, taken from a message to its shards, so in a store without shards chunks close as on main, parallel results one to a message included. In your 40-result case chunks close where main closes them, with the call's first result held with it, and in our run with 40 results of 24,000 characters and a stand-in provider refusing requests over 200,000 tokens, every chunk compresses. agent-framework stores a round's results in one message, so its rounds stay whole. We didn't split the rest out. The head window's end and the recent window's start keep a call with all of its results in every store: the block layout alone would part a call from its result there, since a result after a long text lands on a later shard, and the same rule stops a window's start between parallel results, where a compile on main could refuse (UncoveredDropError). The title, which now names the window edges, and the first paragraph say so. That later requests carry an uncompressed chunk raw is main's own, and is #346.

R1-6, in 044d007d4. Each run keeps a high-water mark, so marking is linear.

R1-7, in 044d007d4. releaseRawFormsAcrossParts maps each block to the set of parts holding it. A new test puts one block object with a raw form at both ends of a message, and both copies give it up.

At 428dfba3f, on Linux with Node 24.22.1, the full suite passes 3,294 of 3,294 from a clean build.

Written by Beth-2389 (Claude Opus 5.5).

— Lottie-2661

@Anarchid Anarchid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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" (asserts pieces > 1 at :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 through cappedForSend, 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 to 829-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 calls ContextManager.resetHeadWindow): adaptiveResolution, targetChunkTokens: 100, headWindowTokens 150 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 the tool_use. The reply stays after the history, in order, but loses its thinking and its call.
  • 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 the tool_use without 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 twice targetChunkTokens and 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, and main pins it (test/render-as-newest.test.ts:551). Here it goes out in 2 pieces where main sends 3.
  • Fix: keep the head's end out of a body after a reset. Either re-apply the call-run step (and bodyBoundsIn) after anchorHeadToCoverage, 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 :551 pins. Add the /newtopic-shaped test above to body-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 in renderAsNewest'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, adaptiveResolution on, budget 100 with 20 reserved): four short messages, then A[text, call c0], B[result c0 of 197 tokens, call c1], C[result c1, call c2], U[result c2]. At this head, recentWindowTokens 100,000, 20 and 5 all refuse at "Head/tail reservation exceeded budget" with clearedBy: ['smaller-recent-window']. At 20 and 5 the window starts at B (226 tokens), while the floor counted from C (29). Only a window smaller than U itself, which is empty, gets past that stage. On main, at 20 and 5 the window starts at C and 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. main at cedab243 passes 3,297 of 3,297. This round's tests on the pre-fix merge 60395e24 fail 16 of body-blocks-in-place's 123 and 1 of adaptive/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, main refuses 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.
  • callStart edge 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 the callPartedStarts step that follows. bodyHoldsToolResult/bodyHoldsToolUse read legacy shards and work with adaptiveResolution off.
  • 428dfba3 is comment-only, and the comments it removes are on helpers this PR adds. main had no doc on them, and main's own comments stay.
  • Downstream: agent-framework main and connectome-host main still join shards as the breaking fragment describes. No companion PR exists in either yet.
  • Design doc, render.ts and IngressChunkResult docs 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.

Beth-2389 and others added 4 commits October 26, 2026 23:39
…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>
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Replying to this review.

@Anarchid This pull request's head is now e6a0888f. It answers R2-1, R2-2 and R2-3, and takes out what the previous round added for R1-6. It is four commits on 428dfba3: 885fe042 merges main at cedab243, c42388e2 holds the fixes and their tests, def320c7 rewords one limit in renderAsNewest's doc comment and two changelog fragments, and e6a0888f takes out the two high-water marks in callPartedStarts. At this head, on Linux with Node 24.22.1, the full suite passes 3,466 of 3,466 from a clean build. The description is updated for this head. Every run named below was on Linux with Node 24.22.1.

R2-1. The three render-as-newest tests now assert that the compile sends the body in one entry, equal to what renderAsNewest gives: the body too big for the recent window, the body that starts in the head window and runs past it, and the body released from a compression hold. renderAsNewest's doc no longer leaves the adaptive middle out of cappedForSend, and its list of bodies the compile doesn't send together now names two: 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. #314's unreleased fragment says the same.

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 /newtopic shape, at heads of 150 and 300 tokens, with 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, the compile now sends the reply in one entry, its blocks in order, after the history from before the reset, and a chunk owns it from its first shard. A reply that makes three calls, answered one to a message, also goes out in one entry after the history. Both are new tests in body-blocks-in-place.

  • The two fixes you proposed: we probed your shape with each applied to this branch before its R2-2 fix, with replies of 12 and 23 shards at heads of 150 and 300 tokens. One had the move to where chunks resume refuse a target inside a body or a call run. The other applied the body and call-run steps again after that move. With each, the reply went out whole after the history in all 8 of its runs, but its first one or two shards were in no chunk, raw outside the head. At this head a chunk owns the reply from its first shard.
  • A body that answers its own calls gets no such step. In a probe of your shape with 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.
  • The plain body that main pins in render-as-newest.test.ts (:551 at cedab243, :549 at this head) still goes out in pieces after a reset, as on main.
  • One case can still split: after a reset, a reply that makes a call can still go out in pieces, until the next reset, in a store whose chunks begin partway through it. In a probe of your shape written and chunked at 428dfba3 and opened at this head, the 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 at this head, or by main (cedab243), the same 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. We didn't run whether main makes such chunks in other shapes.

R2-3. The smaller-window floor now counts the newest turn from where the recent window starts, through one helper, turnStart. Your chain shape is now a row in overbudget-diagnostics: at windows of 100,000, 20 and 5 tokens, the refusal names no smaller window. The three rows fail at 885fe042.

R1-6. The previous round answered R1-6 with two high-water marks in callPartedStarts, and e6a0888f takes them out. We wrote a message making N calls and then their N results, one to a message, with the marks (def320c7) and without them (e6a0888f), side by side, one run each. A pass is one callPartedStarts call over all of the store's messages. Each figure for a pass is the time of 20 such calls, plus one read of the store's messages, divided by 20.

N one pass, with / without writing the N results, with / without
100 0.06 / 0.09 ms 928 / 821 ms
200 0.03 / 0.11 ms 3,027 / 3,090 ms
400 0.07 / 0.18 ms 10,069 / 9,647 ms
800 0.21 / 1.58 ms 44,362 / 44,252 ms

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 c42388e2 failed none of the 10 test files we reran, and with both out the full suite passes, so we took them out rather than keep code that no test pins.

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 Anarchid left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 main makes such chunks in other shapes"): it does. We probed the /newtopic shape with a 19-shard reply [thinking, 7,000 chars, tool_use] and its result, at targetChunkTokens: 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:1108 asserts 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 428dfba to 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:131 is 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 a tool_use ahead 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 against 885fe042's source, body-blocks-in-place fails 6 of 130 (the reset and head-window tests), overbudget-diagnostics fails 3 of 166 (the chain rows), and render-as-newest passes 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.
  • 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's normalizeToolPairs reorders 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.ts buildStoredToolResultContent), and #308's own shape is [tool_result, note], which the new layout keeps.
  • Ingress and join. Blocks go by text offset (:2837-2899). joinBodyContent joins only at declared seams, and falls back to the legacy order when any later part lacks continuesBlock (src/body-runs.ts:100-127). continuesBlock persists through append's extra fields and toStored (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). compositeCovers handles composites with several text blocks.
  • Raw forms. releaseRawFormsAcrossParts gives 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 in asCompiled, now at src/framework.ts:1013; the description's permalink at 59d60958 is still accurate for that commit. connectome-host main (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 stripUnpairedToolBlocks drops 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.
@nissa-seru

Copy link
Copy Markdown
Contributor Author

Replying to this review.

@Anarchid This pull request's head is now 2843fb36, one commit on fb31126e. It answers R3-1, R3-2 and R3-3, and adds the test R3-1 suggests. Its only change to src is a doc comment. 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. The description is updated for this head. Every run named below was on Linux with Node 24.22.1.

R3-1.

  • The design doc: its Rendering bullet (docs/adaptive-resolution-design.md:225) now names the second case, in the words of changelog.d/829-body-blocks-in-place.fixed.md: a reply that makes a call, in a store whose chunks begin partway through it.

  • Our probe of your shape: a 19-shard reply [thinking, 7,000 characters of text, tool_use] and its result, at targetChunkTokens 100 and recentWindowTokens 300, and at your six heads of 150, 300, 450, 600, 900 and 1,200 tokens. It wrote each store with 0.23.1 (d82ee000) and opened it at fb31126e, whose src differs from this head's only by that comment.

    • At heads of 300, 450, 600, 900 and 1,200 tokens, 0.23.1's chunks began at the reply's 3rd, 4th, 5th, 7th and 10th shards, in that order. Opened at fb31126e, the reply went out in two entries, and 0.23.1 itself sent it in three.
    • At a head of 150, the chunks began at the reply's first shard, and the reply went out in one entry.
    • After a second reset, the reply went out in one entry at every head.

    These agree with your figures at heads of 450 and above. The description now says that a store 0.23.1 wrote can hold such chunks, with these figures.

  • The test: a new test in body-blocks-in-place builds such a store with 0.23.1's head end and chunk closes, at heads of 450 and 900 tokens. It pins the two-entry shape: the reply split where the chunks begin, one entry before the history and one after, with its result right after the first. Taking out the move to where chunks resume (anchorHeadToCoverage), or ending that move one message later, fails both.

R3-2. changelog.d/829-body-blocks-in-place.breaking.md 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 (src/compile-provenance.ts) has its still-true clause back: where a non-text block sits relative to the text is not compared. The half sentence at test/compile-provenance.test.ts:131 is deleted.

From a draft by Beth-2389.

— Fiona-2757

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The recent window can start inside a split tool-result message, leaving the result and its call out of the raw tail

2 participants