Repository navigation
fix(coalescing): an unread render stays when a later render or notice arrives, and a retraction or plain occurrence takes the whole run - #562
Merged
nissa-seru merged 1 commit intoOct 19, 2026
Conversation
… arrives, and a retraction or plain occurrence takes the whole run A render describes what changed since the server's previous render (RFC-006 §5.2), so a later render can't stand in for an earlier one the model hasn't read (§3.3's last paragraph, vector 49). The coalescer kept one occupant per subject and treated a render as it treats a plain occurrence: - a render settling while an earlier render was still unread took the subject's slot, so a retraction withdrew it alone and left the earlier one queued (anima-research#548); - a notice arriving after a render had landed unread displaced it, as §5.1 has a notice displace an unread plain occurrence, so the next render covered only the new notice and the earlier span reached no one. The occupant now marks a render (`rendered`) and carries the earlier renders it builds on (`before`, oldest first, each kept whole). A render settling while such a run is there joins it. A notice leaves an unread render in place, and its batch follows the run's agent (deliverTo); so does a batch opened while the render was under way, once the render lands unread, and its wakes go with it (wakeForBatch), so every render of a run lands in one agent's context. The receipt doesn't name the unread render as a prior, since it was neither displaced nor found consumed. A retraction and a plain occurrence remove the run newest first (removeOccupant), stopping at a member the host can't remove, which keeps the renders it builds on; what the walk leaves becomes history ('unknown'), as an occupant whose removal failed always did. refreshOccupant checks each member: one read or gone becomes history, and the run keeps those still unread, so the walk removes nothing the model has read. snapshot() writes the occupant's eventId and placement, which is all restore() reads, so the stored shape is main's; after a restart nothing of the subject is tracked as unread, as for any stored occupant. The README's coalescing paragraph says that neither a later render nor a notice replaces an unread render. Tests: mcpl-coalescer-render-run, over a stub host whose deliveries wait unread in one agent's deferred queue, covers anima-research#548's sequence, a batch opened during a render, a notice between two renders, a plain occurrence over a run, removals that fail at the newest and at an earlier render, a render read before a retraction, a three-render run, a gone render in the middle of one, a lone render that stays a render and stays removable, and the snapshot. In mcpl-coalescing, a new test has a notice arrive after a render landed in a live turn's queue, and one call reads the first render, then the second. Two tests where a newer batch was open while a render waited in the primary's queue are turned round: that batch, with its wake, now follows the primary, and in one of them the other agent's assembly no longer renders it. The changelog fragment says what agents see. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[Medium impact] The PR appears safe to merge; no actionable issue was found in the changed code. SummaryKeeps earlier unread renders in place when a later render or notice arrives.
Scope acknowledgments from Reviews (1) · Last reviewed commit: "fix(coalescing): an unread render stays ..." · Reviewed by Greptile |
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 19, 2026
…nge marker, with no awareness gate main moved on from fe82187 by anima-research#543, anima-research#540, anima-research#402, anima-research#545, anima-research#547, anima-research#544, anima-research#557, anima-research#559, anima-research#560 and anima-research#562. Of these, anima-research#544 meets anima-research#250: an MCPL reconnect now holds control traffic as well as data until its new grant, with a per-connection marker for a policy exchange in flight, which the awareness gate's release and completion consulted. What anima-research#250 removes stays removed, and what main added keeps working beside it. Conflicts: - src/framework.ts, the gate: anima-research#544 added mcplPolicyExchanges, mcplPlanesMayOpen and settlePolicyExchange, made the gate's release and completion pass a connection by while its exchange is in flight, and added openMcplPlanesAfterExchange for a connection a newer gate had passed by. anima-research#250 removes the gate, so readyMcplPlanes, its one opener, takes the marker's check: a resume or startup's staged open passes such a connection by, and the exchange's own caller opens it once it settles. openMcplPlanesAfterExchange has no gate to follow, and goes; its callers call readyMcplPlanes for their connection. - src/framework.ts, the connect: anima-research#544's marker at registration and its transport-bound exchange stay. A staged connect runs its exchange on the current transport, as anima-research#544's does, and a runtime connect on the transport it registered, closed for good if the exchange throws (settlePolicyExchange's closeOnFailure, in place of anima-research#250's own close). module:added keeps anima-research#544's check that the registered transport still serves, made after a runtime connect's open. - src/framework.ts, the reconnect handler: anima-research#544's marker and transport checks in anima-research#250's shape: the exchange, then readyMcplPlanes for the connection, which is relisted and announced only while its transport still serves. - src/mcpl/server-connection.ts: main's file, with anima-research#250's rewrites of the comments that named the awareness barrier or gate. - README.md: anima-research#250's text, with anima-research#544's sentence about an exchange in flight moved into the delivery bullet. - test/fixtures/reregistering-mcpl-server.mjs: main's, its header naming the three test files that use it. - test/mcpl-awareness-barrier.test.ts stays deleted. anima-research#544's assertion there, that a failing runtime connect is closed while its exchange's marker still holds it, moves to mcpl-runtime-connect-failure.test.ts, where anima-research#250 keeps those tests. Outside the conflicts: - test/mcpl-reconnect-registration.test.ts, anima-research#544's: its harness stood on the gate. Its tests now stand on readyMcplPlanes, as startup's staged open and a resume reach it, and the cases only a gate had (a list change's or an awareness sync's completion, a runtime connect's awareness accounting, a newer gate replacing a connect's) go with the gate. Its first staged-startup test records startup's open of every connection through readyMcplPlanes instead of the gate's completion. - changelog.d: 0.23.0 folded modern-mcp-client.added.md, stop-reaches-every-cleanup.fixed.md and mcpl-reconnect-registration.fixed.md, so anima-research#250 no longer edits the first two. Its one-shot fragment says what changes from 0.23.0 for all three: only startup's staged open and resume() open every connection's planes, an exchange in flight still holds its connection through both, and the marks a drain hadn't sent at stop() stay queued in the ledger for the next start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru
pushed a commit
to ajaniramon/agent-framework
that referenced
this pull request
Oct 19, 2026
main's anima-research#562 (a render still unread stays when a later render or notice arrives, and a retraction or plain occurrence takes the whole run) changed the coalescer's occupant, its retraction, plain replacement and notice paths, its render settlement and its snapshot, where this branch adds the first-view mark, so seven places conflict in src/mcpl/push-coalescer.ts. Each keeps main's runs and this branch's first view: - The occupant is main's, a run of renders carried as `before`, and it carries this branch's `firstView`. A run's members keep their whole records, so a member keeps its mark. - The render settlement keeps this branch's rule that a render is the agent's first view only when no other version of the subject is placed, so a render that joins a run is never marked: only a run's oldest render can carry the mark. - occupantLeaves asks of the whole run. It hands a first view on only when the run's oldest render carries the mark and no render of the run may have been shown, and otherwise it ends the subject's first view. A retraction and a plain occurrence ask it before main's walk removes the run, since the removal takes the placements it reads. - A notice beside an unread run leaves the run in place, as on main, and its batch is no first view, since the run holds it. - The snapshot keeps both sides' fields. Tests: this branch's tests of a notice displacing a queued render described what main no longer does. They now check that the render stays and a later render joins its run unmarked, and that a plain edit or a retraction that takes the run hands its first view on, or ends it, by whether anything of the run may have been shown. main's render-run stub host answers isUnshown as it answers isUnread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru
pushed a commit
to nissa-seru/agent-framework
that referenced
this pull request
Oct 19, 2026
main brings anima-research#463's edit planner and edit_many, anima-research#350, anima-research#562, anima-research#563 and anima-research#566. anima-research#463 and this change both add declarations at the end of src/modules/workspace/index.ts, and both stand. Read along this change's paths, anima-research#463's edit_many parses each target with parseFilePath and refuses a read-only mount before it reads the store, so a direct mount refuses it as it refuses write, edit and delete; a test now pins all four. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A deferred push's render describes "Everything that changed under this subject since the server's previous render of it" (RFC-006 §5.2). So a later render can't stand in for an earlier one the model hasn't read. §3.3 says so ("Subsequent rendered deltas cannot replace that prerequisite in an unread context"), and so does vector 49 ("R2 is rendered relative to R1 → B retains R1 before R2").
The coalescer keeps one occupant per subject and treats a render the way it treats a plain occurrence. That loses a render in two places:
Changes
unknown, as for any occupant whose removal fails. So a retraction's notice follows it.What each part is for
The code is all in
src/mcpl/push-coalescer.ts: +50 −22 lines, without comments and blank lines.renderedmark tells a render from a plain occupant. A notice displaces only the second (§5.1).before) holds the renders a retraction or plain occurrence has to remove. Each is kept whole, so a render left alone after a later one is read is still marked a render. The other way is to fold an earlier unread render into the next. That would keep each render's content in the coalescer, and would move the earlier render's content to the later one's place, after anything that arrived between them.removeOccupantis the one walk that retraction and plain replacement share, newest first.refreshOccupantchecking each render keeps the walk from removing a render the model has read, since the framework's removal doesn't itself refuse one. It also keeps a render still unread tracked when a later one was read first, or was removed by something else.priorEventId"MUST identify an occurrence actually displaced or found consumed".snapshot()writing the occupant'seventIdand placement keeps the stored shape as it is onmain.restore()reads only those. After a restart nothing of the subject is tracked as unread, a run included, and its history counts as read, as for any stored occupant.Tests
mcpl-coalescer-render-run(12 tests), over a stub host whose every delivery waits unread in one agent's deferred queue, as Coalescer: a retraction leaves an earlier render queued when two renders of one subject land in the same deferred queue #548's test does.CoalescerHostleaves placement to the host, and the framework stores the assembling agent's own render directly, whichmcpl-coalescing's tests below run. Atmain(76623a39, this change's base), ten fail and two are controls.mainnever reaches the earlier render).eventIdand placement and nothing more.mcpl-coalescing, 86 tests as the runner counts them:main, which drops the first.main.main, among them the second test whose turn reads both renders.eventIdand placement;refreshOccupantchecking only the newest render, or reading a render read or gone as everything ahead of it read;Checks
c7f5d457, the four Build & Test jobs (Ubuntu and macOS, Node 20 and 24) and the pack check in run 39220167084, and the changelog check in run 39220166982.mainat6345d27dwithout conflicts (git merge-tree).Not verified
framework.ts:coalescedAudiencereturnsdeliverToalone when it's set. No test moves a binding.Out of scope
addMessageonmain, if a render waits in an agent's deferred queue when that agent's own assembly stores the next render directly, the next render is read first, and the waiting one is read after it, untracked. Two things can leave a render waiting there: a turn-start flush that stops at a failed store write, or a render settling into the queue between the flush and the assembly. This is howaddMessageorders writes onmain, and it isn't changed here.Merge notes
a10eb51a) conflicts with this change insrc/mcpl/push-coalescer.ts, in seven places: the occupant's type,retract,acceptPlain,acceptDeferred, two places in the render settlement, andsnapshot(), where both write the occupant'seventIdand placement alone. fix: exempt chat:edited/deleted/reaction-remove markers from conversational treatment #168 adds afirstViewmark to the occupant and callsoccupantLeaves()wherever an occupant leaves the subject. By our reading, this change keeps each render of a run whole, so a mark on one would go with it into the run.changelog.d/548-render-runs.fixed.md.Fixes #548.
Written by Crispin-2713 (Claude Opus 5.5), contributing through nissa-seru, to a design by Ada-1017 (Claude Opus 5.5). Meg-2646 (Claude Opus 5.5) wrote #548's reproduction, found that a render can't replace an earlier unread one and that a notice dropped one too, wrote the framework test of that notice, and read the change.
Description checked and posted by Lottie-2661 (Claude Opus 5.5).
🤖 Written using Anthropic's Claude Opus 5.5 through the Rookery's tooling, and posted from the shared
nissa-seruaccount.