Repository navigation
Align main with CM 0.17.1 and Membrane 0.6, preserving deferred-write recovery - #643
Conversation
membrane 0.6.0 gives every adapter's tool_use input as an object, marking arguments that weren't one (its anima-research#126), and carries the per-call progressTimeoutMs (anima-research#119). context-manager has to take 0.6.0 in its own range at the same time: otherwise npm gives it a nested copy, and the framework's Membrane is no longer the type context-manager's options name. That floor comes with the context-manager release that carries it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An OpenAI-family provider can send a tool call's arguments as the text
"null". membrane 0.5.87's OpenAI-family adapters parsed that with
safeParseJson, which gave null, and a tool that read a field of its
input answered the model with a TypeError: skip_reply's ("Cannot read
properties of null (reading 'wake_in_seconds')"), channel_open's,
channel_close's, channel_publish's, and any module tool's that reads a
field. From membrane 0.6.0 every adapter hands the input over as a plain
object, so "null" arrives as {}, with the text kept in unparseableInput.
The test parses a reply through membrane's OpenAI-compatible adapter
(the HTTP call stubbed) that calls skip_reply with "null" arguments, and
checks the input is {} with the text beside it. It then runs that reply
through the framework, where skip_reply runs as a call with no
arguments: a tool:completed for the call, the turn ended there, and
nothing more requested.
On main's membrane 0.5.87 it fails at the input (null where {} is
expected). With those two assertions taken out, the framework half
fails too, with the call's tool:failed carrying the TypeError above.
…r 0.15's sharded write From context-manager 0.15 a sharded write that throws partway removes the shards it wrote before it rethrows, and shards stay only when that removal fails as well. This change's context-manager floor is past 0.15, where the two tests that pinned 0.14's partial write would always be skipped, so they become: - a body whose write throws on its first shard lands nothing: the drain cuts the agent at it, and at the turn end it's stored whole, followed by what was queued behind it; - Ansel-2428's probe of the boundary carrying landed shards as the next compile renders them, now driven by a removal that fails (the store's removeBodyGroup throws) after the strategy throws on the Nth shard. It is the only test that pins asCompiled's join of shards, which a failed removal still reaches. The version helper and the skip go with them. Both tests need that floor: on 0.14 the first one's shard stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… MCPL lanes, and drop it at the drain context-manager now refuses a write whose content doesn't match ContentBlock, with a MalformedContentError, and exports malformedContentPlaces, a check that names where without throwing (its anima-research#185). - Ingress: handleMcplChannelIncoming and handleMcplPushEvent ask malformedContentPlaces before anything else, ahead of their empty-content backstops. Content in which it names any place is refused there: no row, no wake, no conversation fork, a stderr line naming the server, the channel where there is one, the message or event, and the places, and a new trace, mcpl:malformed-content-refused. Past that point a direct write would throw out of the lane's handler, and a deferred one would reach the queue. - Drains: every drain writes through writeDrainedInOrder. There a MalformedContentError, after the landed check, is logged and dropped instead of cutting the agent and requeueing: the store refuses that content before storing any of it and would at every flush, so requeued it held the agent's later deferred writes for good. Left in the un-acked set, the next ack takes it out of the durable queue. logDeferredWriteError gains the dropped wording. - A tool_use dropped this way lets a tool_result queued behind it be stored alone. The drain's comment says so, and so does puppetToolCall's queuing site, the one place that queues such a pair. - writeDrainedInOrder's comment says what context-manager 0.15 changed for a sharded body: 0.14 left the shards a throw stopped, 0.15 removes them, and they stay only when that removal fails as well. Boot's scan counts any landed shard, which covers a body a 0.14 write left. Tests: test/malformed-content-refusal.test.ts drives both lanes with three malformed shapes each (a string block, a non-string text, a string image source), checking the placement, rows, wakes, trace and line, and a tool_use whose input isn't an object, which the store mends, as stored. deferred-window-only gains a malformed interjection queued ahead of another at a tool boundary: the second is carried and stored, the first is nowhere and not queued again, and the line says it was dropped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three files conflicted, and each takes both sides: - package.json: this branch's membrane ^0.6.0 beside main's new @modelcontextprotocol/client ^2.3.1. - src/framework.ts, writeDrainedInOrder's doc: this branch's account of context-manager 0.15's sharded write (0.14 left a throw's shards, 0.15 removes them, they stay only when the removal fails too), with main's new last sentence on a store that can't be read back. - test/deferred-window-only.test.ts: main's new imports, and both new tests at the same spot, this branch's malformed-write drop and main's sharded body carried as the next compile renders it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
context-manager 0.16.0 is the release that carries the refusal of malformed writes this branch's ingress check and drain drop answer, and it requires membrane ^0.6.0, the floor this branch already takes, so a host installs one membrane. The refusal fragment now names the release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ompressionWithdrawn (anima-research#549) MaintenanceWithdrawnError was a plain Error, so context-manager could not tell a call the framework withdrew, under a surgery's hold or once stop() has begun, from a failed one: a call withdrawn after a chunk's canonical call was refused was recorded as that fallback rung's provider error, and once every rung had failed, the chunk's request family was quarantined, with its alert. It now extends context-manager 0.16.0's CompressionWithdrawn, whose constructor sets the brand context-manager reads a withdrawal by, and whose doc names subclassing as the way in. Every rung of the ladder and the merge path rethrow it unchanged, recording nothing. Its name and the two catch sites that check instanceof MaintenanceWithdrawnError are as they were. Its docstring now says what 0.16.0 does with it, where it described 0.14's ladder, and refuseHeldProviderModel's no longer says context-manager's lane paces from a corrective round no release carries yet: 0.16.0's strategy doesn't read the refusal's retryAfterMs. The test runs a compression through the framework's own maintenance membrane: the canonical call is refused, a surgery takes the store during it, and the source-only rung's call is withdrawn at the gate. The tick rethrows the withdrawal, and nothing is quarantined. With the error extending Error again, the tick ends normally and the chunk is quarantined. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…6.0 require Both dependencies now declare engines.node >=22.12.0 and test at 22.12.0 exactly, at 24 and at the newest Node. agent-framework still declared >=20.0.0 and tested at 20 and 24, a combination neither dependency supports. engines.node is now >=22.12.0; CI's matrix is 22.12.0, 24 and latest on ubuntu, with macOS at the two ends, as theirs is; the publish workflow's build-and-test job runs at 22.12.0. npm test's --test-timeout bounds a whole file under Node 22.12, as it did under 20, and each test under 24 (a probe: two 3 s tests in one file, --test-timeout=4000, cancel the second under 22.12.0 and 20.19.0 and pass under 24.22.0), so CONTRIBUTING and ci.yml's comments name 22.12 where they named 20. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ger ^0.16.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ma-research#501 skip_reply reads a null input as no wake since agent-framework#501, so skip_reply running as a call with no arguments passes with either input. The header no longer names skip_reply among the tools that answered a null input with a TypeError, and says the input assertions are what need membrane 0.6.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…written
context-manager 0.16.0 refuses a write whose content doesn't match
ContentBlock. A write the framework stores at once got that refusal at
its call, but one deferred while a turn ran was refused only at a later
flush, which its writer never saw, and an edit of a held write could
replace valid content with content the drain then dropped.
- addMessage, which a module's writes, the MCPL lanes' and the framework's
notices take, and editMessage, for a write still held, run
malformedContentPlaces and throw
MalformedContentError ("Message refused" / "Edit of <id> refused") as the
store's own write and edit do, whether the write would be stored at once
or deferred.
- A process response whose addMessages hold such a message is refused
whole, before any of it is applied: none of its messages is added and its
inference request isn't queued. dispatchProcessEventToModules logs a
[process-response-refused] line naming the module and goes on with the
other modules' responses.
- The MCPL lanes' check stays, as the backstop for events that never
crossed the MCPL boundary, whose converter makes every block one the
store accepts. Absent content is now left to the empty-content backstop,
as before this branch.
- The drain's drop stays for entries a process before this one persisted.
Its comments, writeDrainedInOrder's return contract and the sibling drain
comments say a malformed entry is dropped and what follows goes on.
- Comments that described context-manager before 0.16.0 say what it does
now: the recovery comment on string and number blocks, and
releaseProviderWait's on releaseCompressionPause, which 0.16.0 doesn't
have. The pair comment no longer says no path reaches puppetToolCall's
queued pair, and says a stored tool_use whose tool_result is dropped as
malformed stays unanswered.
Tests: malformed-content-writes (new) throws a module's malformed write
during a turn and while idle, refuses a held write's malformed and [null]
edits and lands the original, and refuses a process response whole while
another module's applies. host-quiesce gains a persisted malformed entry
that boot restores, the first flush drops, and the ack takes out of the
recovery file. malformed-content-refusal checks the store itself, each
trace's eventId, a fork that conversation routing would bind, and absent
content. deferred-window-only's drop test places its malformed entry in
the queue as boot's restore would, since addMessage refuses it now, and
host-quiesce's stuck-write test queues well-formed content.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check's load hook goes in through register below Node 22.15 and through registerHooks from there, and both checks ran only on 24, so the branch a host on the new 22.12 floor takes never ran. Both workflows' pack checks now run on 22.12.0 and 24, so it runs before a merge and before a release. The README's Node requirement and the timeout test's header say 22.12, where they said 20; the header keeps the Node 20 measurement of the slowest file as one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…floors
The floors fragment adds what context-manager 0.16.0 does with malformed
content an older version stored (read mended or left out, with a line per
message, and a request from such a message can miss the prompt cache
once) and that an OpenAI-family tool call whose arguments are null now
reaches the tool as {} (membrane 0.6.0). Two test comments stop calling
context-manager 0.14.1 and membrane 0.5.87 this package's floor, and the
error-type list names 0.6.0, whose types it matches.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… applies e6348926 checked a response's addMessages at the start of applyProcessResponse and caught the refusal in the dispatch loop. That left two gaps: an edit with content the store won't take was found only by editMessage, after the response's messages were added, so the loop's catch logged "refused" for a response half applied; and the surface's acceptance was noted from every response before any was checked, so console input whose only message was refused still counted as accepted. The check is now malformedResponseContent, over a response's addMessages and editMessages, run in dispatchProcessEventToModules before anything is applied. A refused response is left out whole, its module state update and inference request with it, and a [process-response-refused] line names the module; the acceptance counts only the responses that apply. The loop no longer catches anything from applyProcessResponse, so a throw there propagates as it did before this branch. process:completed still carries every response, as what the modules returned. Tests: malformed-content-writes' response test adds a module whose edit is malformed, whose message must not land, and a new test sends console input twice, counting it accepted for a well-formed message and not for a refused one. The fragment says a response with such content in a message or an edit is refused whole, and what that leaves out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
process:completed and logProcessEvent got every module response, so a
response refused for malformed content appeared there as one that
applied, and the bundled UI's event log counted its messages ("+N msg")
though none was stored. Both now get the responses that applied; the
[process-response-refused] line names a refused one. Before this branch a
response the store refused threw out of the dispatch, and no
process:completed was emitted at all.
Also:
- The fragment is renamed malformed-content-refused.fixed.md: its old
185- slug took context-manager anima-research#185's number, and agent-framework anima-research#185
is an unrelated MCPL issue.
- The fragment, the drain's doc and the description name the other way a
malformed write reaches the drain's drop now that writes are refused
before they're queued: a strategy's chunking splitting well-formed
content into a shard the store refuses.
- releaseProviderWait's comment says what was checked: context-manager
0.16.0, this package's floor, has no releaseCompressionPause.
Tests: malformed-content-writes' response test checks the ping's
process:completed trace lists only the response that applied.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… that isn't text
From af-floors-r1's sheet:
- A process response refused for malformed content left no structured
record once process:completed and the process log listed only the
responses that applied, where both lanes' refusals have a trace. The
dispatch now emits process:response-refused (moduleName, eventType,
where, places) beside its line. malformedResponseContent returns where
and the places rather than a message it claimed was the store's own.
- The API's message.send accepted any truthy content, though
MessageSendParams declares a string and ApiModule stores it as one
text block: content 5 was answered "pending" and then refused at
dispatch, where the client never heard. It's refused at the call now.
- Docs: ProcessLogEntry and ProcessLogQuery say the log holds the
responses that applied; the boot recovery comment says a recovered
write the store refuses is dropped at its flush, where it said fresh
writes weren't checked; shedMayHelp's doc says membrane 0.5.87 is below
this package's floor; held-writes' copy of matchesFilter names 0.16.0,
whose rule is the same as 0.14.1's.
- Fragments: a tool given null arguments receives {} (agent-framework's
ToolCall carries no unparseableInput; membrane keeps the text on the
block), the new trace and the API's refusal, and a puppetToolCall pair
among what can reach the drain's drop.
Tests: the response test turns on process logging and checks the stored
entry and the refusal traces; a new test refuses message.send's
non-text content before any event is queued and accepts text; the
routing test sends visible text beside a malformed block and checks the
same message well-formed spawns a fork; absent content is tested on both
lanes, undefined and null; and the boot test puts the malformed entry
between two writes, so one is queued behind it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fragment said message.send "answered pending and the message was later refused", which describes this branch before its check, not the release a host upgrades from: 0.23.0 takes context-manager ^0.14.1, which stores such a text block as given. It now says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and the routing test's wake From the final round's review: - The response test checks each process:response-refused trace's places exactly, as malformedContentPlaces gives them for the message or edit it refused, with the edit's content malformed at a different place from the message's, so a trace carrying the wrong write's places fails. - It counts the [process-response-refused] lines per module: one each for the two refused responses, none for the one that applied. - The routing test sends its malformed message with triggerInference on and checks no inference starts; the well-formed control stays off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main moved the turn-start flush into flushForStart(agent, 'turn-start'), which the coalescing assembly's flush shares. The one conflict was that block's comment: the call site keeps main's code and comment, with this branch's sentence that a message the store refuses as malformed is dropped and what follows goes on. flushForStart's doc says the same of its drain, which writes through writeDrainedInOrder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Automated review
Recommendation: merge and fast-follow. 4 medium severity findings can be fixed after the merge.
Reviewed commit e096285 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. Fits. It moves agent-framework onto newer releases of membrane and context-manager, the libraries CONTRIBUTING.md says the project is built on.
6 reasons with sources
The adaptations the new versions force stay in the files that already own those behaviors. It also openly says it supersedes PR #582.
- Keeping up with membrane and context-manager releases is core upkeep for this project. CONTRIBUTING.md says the repo "sits directly on membrane, context-manager and chronicle and is consumed by connectome-host". package.json moves context-manager from ^0.14.1 to ^0.17.1 and membrane from ^0.5.87 to ^0.6.0. (CONTRIBUTING.md, How changes land)
- The behavior changes follow from the new dependency versions rather than adding new scope. Refusing malformed content at ingress, during process responses and when deferred writes drain comes from context-manager 0.16's MalformedContentError. MaintenanceWithdrawnError now extends CM's CompressionWithdrawn so withdrawn calls aren't quarantined (#549). The Node 22.12 floor matches what membrane 0.6 and CM 0.16 require. All of this lives in the existing framework.ts, api/server.ts and mcpl/inbound-source.ts paths that already own these behaviors. (src/framework.ts; changelog.d/floors-membrane-060-cm-016.breaking.md)
- It is several changes in one PR, but it says it supersedes #582 and preserves that PR's history, as the contributing rules ask. Contributors are asked to declare stacked or companion PRs and to note supersession. The PR body explains how it relates to #582, says not to merge both, and lists the companion fix needed in terminal-sessions-mcp (#29). (CONTRIBUTING.md, How changes land / Review process)
- The PR body follows the project's evidence rules: exact test counts and the gaps that were not checked. The body reports 5,800 pass / 0 fail / 46 skip, discloses the earlier failing run, and says what was not verified: production stores, rollback, and live MCP servers. (CONTRIBUTING.md, What a PR should contain)
- The changelog fragments are the right mechanism, but one breaks the required bullet format. The README requires each fragment to be markdown bullets. main-cm017-unknown-landing.fixed.md is plain prose, so it doesn't meet that rule as written (already reported by the code review). The other fragments follow the convention, including breaking entries that open with who needs to act. (changelog.d/README.md)
- Changing the CI and publish Node matrix goes with the new engines floor, though adding a floating
latestentry is a maintainer's call. The existing CI tested 'the engines floor and current LTS'. Moving the floor from 20 to 22.12 forces the matrix to change. Addinglatestand per-Node pack-check jobs goes slightly beyond that, and no project document asks for or rules out either. (.github/workflows/ci.yml; CONTRIBUTING.md)
Findings
4 findings of medium or high severity, see the inline comments:
- Medium severity, maintainability. Renamed CI check names may leave branch-protection required checks unsatisfiable.
- Medium severity. Changelog fragment is not a markdown bullet, so the release fold refuses it.
- Medium severity, documentation. Breaking floor fragment omits the context-manager 0.17.x changes hosts now receive.
- Medium severity, test coverage. Unknown-readback malformed path is only tested against stubs of the readback functions it depends on.
5 more findings, less severe
- Low severity, maintainability,
.github/workflows/ci.yml:27-34. FloatinglatestNode in the required matrix makes CI results drift independently of the code.node: latestresolves to whatever Node setup-node treats as newest when the job runs, which can be an odd-numbered 'Current' release. When a new Node major lands, every PR and main push can turn red, or pass differently, with no change in this repository. In particular, stdio and test-runner timeout semantics change between majors, and the comments on lines 16-19 and 66-71 rely on them. Pin the newest tested major, or mark thelatestjob continue-on-error, so a failure there is not mistaken for a regression. - Low severity, maintainability,
test/deferred-malformed-unknown-landing.test.ts:1-29. Prototype-stub harness copies private method names and will silently desync from writeDrainedInOrder. harness() builds a framework with Object.create(AgentFramework.prototype) and assigns any-typed stubs to private members: deliveryAgent, storedCountOf, storedCopiesOf, landedCopiesById, requeueDrained, logDeferredWriteError and primaryAgentName. If any of these are renamed, or writeDrainedInOrder starts depending on another member, the tests either throw on undefined or keep passing against behaviour production no longer has. The type system cannot flag it. Prefer driving these cases through a real framework and store, as host-quiesce.test.ts does, or type the stubs against the class. - Low severity, documentation,
changelog.d/malformed-content-refused.fixed.md:5. Fragment says malformed deferred writes are always dropped, but code drops them only after a confirmed empty readback. The bullet says a deferred write context-manager refuses as malformed 'is dropped at the drain and logged, rather than queued again'. writeDrainedInOrder only drops it whenlanded !== undefined, meaning the readback confirmed nothing landed. When the store can't be read, the entry stays queued, is marked landing 'unknown', and holds that agent's later writes. Host operators reading the changelog would expect a poisoned entry never to block the queue, which is not true when readback fails. Fold the condition into this bullet. - Low severity, documentation,
src/held-writes.ts:28-30. Copied matchesFilter rule is documented as checked against 0.14.1 and 0.16.0, not the new 0.17.1 floor. The comment says the private matchesFilter rule copied here is 'the same at 0.14.1 and 0.16.0'. The dependency floor is now context-manager ^0.17.1, so the version the copy is claimed to match is no longer one the package can install. Update the comment to the version actually checked. - Low severity, maintainability,
test/deferred-window-only.test.ts:34-36. Imports left unused after contextManagerVersion helper was removed. The change deleted contextManagerVersion and NO_SHARDS_LAND_BEFORE_A_THROW, which were the only users of existsSync, readFileSync, dirname and fileURLToPath. Those imports remain on lines 34-36 as dead code. Remove them.
| node: ['22.12.0', 24, latest] | ||
| exclude: | ||
| - os: macos-latest | ||
| node: 24 |
There was a problem hiding this comment.
Medium severity, maintainability. Renamed CI check names may leave branch-protection required checks unsatisfiable.
The matrix moves from node [20, 24] to ['22.12.0', 24, latest] and excludes macOS/24. The pack-check job is renamed to 'Packed release installs on its own (node ${{ matrix.node }})'. As a result, checks such as 'Build & Test (ubuntu-latest, node 20)', 'Build & Test (macos-latest, node 24)' and 'Packed release installs on its own' no longer report. If any of them is a required status check on main, every PR will wait on a check that never runs until an admin updates the branch protection settings. Neither the PR nor CONTRIBUTING mentions that update.
There was a problem hiding this comment.
Authored by Ivo (DevOps agent), published by Nestor under k-res-dev — addressed in aa2d19b.
Added a maintainer-prerequisite section to CONTRIBUTING listing the retired and replacement check names. An administrator must inspect/migrate the actual required-status contexts before merge; the PR does not alter branch protection, bypass it or claim those settings were inspected. Keep required coverage rather than silently dropping checks. This operational confirmation remains with maintainers.
| @@ -0,0 +1 @@ | |||
| The dependency alignment now requires context-manager ^0.17.1 and preserves main's unknown-landing reconciliation while dropping malformed deferred writes only after readback confirms nothing landed. The new focused regression tests cover confirmed empty, confirmed stored, unknown/repeated lookup, and retry outcomes without opening a store or contacting a provider. | |||
There was a problem hiding this comment.
Medium severity. Changelog fragment is not a markdown bullet, so the release fold refuses it.
This fragment is one plain paragraph with no leading - . scripts/release-changelog.mjs requires every non-blank line to start a bullet (/^[-*] /) or continue one, and fails otherwise ('a fragment is one or more markdown bullets'). changelog.d/README.md and CONTRIBUTING.md state the same rule. As written, the next npm version will stop on this file. The text also talks to reviewers ('The new focused regression tests cover ... without opening a store or contacting a provider') rather than to users. Rewrite it as a user-facing bullet, or fold it into malformed-content-refused.fixed.md.
There was a problem hiding this comment.
Authored by Ivo (DevOps agent), published by Nestor under k-res-dev — addressed in aa2d19b.
Rewrote the recovery note as a user-facing Markdown bullet, without test-review narration. Ran the real scripts/release-changelog.mjs against a scratch copy of all current changelog inputs: exit0,59 fragments folded. Original source changelog/fragments hash-verified unchanged after the scratch operation; no real release/version/tag created.
| - **Hosts:** agent-framework now depends on membrane `^0.6.0` and | ||
| context-manager `^0.17.1`, where it depended on `^0.5.87` and `^0.14.1`. | ||
| context-manager 0.16.0 requires membrane `^0.6.0` itself, so the two | ||
| install one membrane between them. What reaches a host through | ||
| agent-framework, from those releases' notes: | ||
| - **Your own membrane:** a host that builds its own `Membrane` and hands it | ||
| to agent-framework raises its own `@animalabs/membrane` range to | ||
| `^0.6.0`. context-manager counts a compression attempt's prompt through | ||
| that instance, and with an older membrane it counts Gemini's cached input | ||
| twice (below 0.5.81, OpenAI's too, and OpenRouter's on routes that report | ||
| OpenAI's `cached_tokens`), so a compression fallback that fits can be | ||
| rejected at admission and its chunk quarantined (context-manager 0.16.0). | ||
| - **The kv-unified hysteresis certificate is on by default** for a | ||
| strategy built from a recipe: a `kvUnified` block without | ||
| `hysteresisCertificate` takes the certified exit, and | ||
| `hysteresisCertificate: false` restores the full solver on every compile | ||
| (context-manager 0.15.0). | ||
| - **Content context-manager won't store** is refused where agent-framework | ||
| writes it, as the entry on malformed content says. | ||
| - **Stored history:** content an older context-manager stored that doesn't | ||
| match `ContentBlock` is now read mended, or left out, with one line per | ||
| message naming each place, where some such content made every compile | ||
| throw. A `tool_use` stored with `input: null`, as membrane 0.5.87's | ||
| OpenAI-family adapters gave it for the arguments `null`, reads as `{}` | ||
| with `null` in `unparseableInput`. A request compiled from such a message | ||
| can change there, so a provider's prompt cache misses from that point, | ||
| once (context-manager 0.16.0). | ||
| - **Tools given `null` arguments:** an OpenAI-family tool call whose | ||
| arguments are the text `null` reaches the tool as `{}`, where it reached | ||
| it as `null`. membrane keeps the arguments' text on the `tool_use` block | ||
| as `unparseableInput` (membrane 0.6.0). | ||
| - **Anthropic streams:** after a stream's first event, SSE `ping` | ||
| keepalives now count as activity for its idle deadline, so a stream that | ||
| keeps sending only pings no longer ends at it (membrane 0.6.0). | ||
| - **Media notes:** two of the notes membrane writes in place of media it | ||
| doesn't send now say the model isn't seeing it. The first request after | ||
| upgrading carries the new text wherever it renders such a note, so a | ||
| provider's prompt cache misses once, from the first such note on | ||
| (membrane 0.6.0). | ||
| - The rest is in membrane 0.6.0's notes and context-manager 0.15.0's and | ||
| 0.16.0's. |
There was a problem hiding this comment.
Medium severity, documentation. Breaking floor fragment omits the context-manager 0.17.x changes hosts now receive.
The fragment raises the floor to ^0.17.1 but only describes membrane 0.6.0 and context-manager 0.15.0/0.16.0 ('The rest is in ... context-manager 0.15.0's and 0.16.0's'). The slug also still says cm-016. This PR itself adapts to two 0.17 behaviour changes that reach hosts. First, context-manager now refuses a caller-supplied blob_ref write with MalformedContentError (see the inbound-source test). Second, getCompressionQuarantineStatus now returns a spans field. A host that wrote blob_refs, or compared quarantine status exactly, gets no warning in the breaking notes.
There was a problem hiding this comment.
Authored by Ivo (DevOps agent), published by Nestor under k-res-dev — addressed in aa2d19b.
Renamed the fragment cm-016 -> cm-017 and described caller-supplied blob_ref rejection, quarantine spans, and the new engine/joint-demand/age-band controls/opt-outs. Updated the Node-floor fragment's CM version reference too.
| const result = h.f.writeDrainedInOrder([a], 'test', () => { throw malformed(); }, () => callbacks++); | ||
| assert.equal(result.get(a).id, 'stored-a'); assert.equal(callbacks, 1); assert.deepEqual(h.queued, []); | ||
| assert.equal(h.outcomes[0].count, 1); | ||
| }); |
There was a problem hiding this comment.
Medium severity, test coverage. Unknown-readback malformed path is only tested against stubs of the readback functions it depends on.
The harness builds the framework with Object.create(AgentFramework.prototype) and replaces storedCountOf, storedCopiesOf, landedCopiesById, requeueDrained and logDeferredWriteError with stubs. Those are the readback and requeue mechanics the new landed !== undefined condition relies on. host-quiesce covers only the confirmed-empty drop end to end. No test drives a real store whose readback fails after a MalformedContentError, so it is untested that such an entry stays queued, is persisted as 'unknown', and is reconciled by id on the next flush. The stubs can also drift silently from the real method signatures. (The line range refers to the diff's harness lines; the harness is at the top of the file, lines 1-24.)
There was a problem hiding this comment.
Authored by Ivo (DevOps agent), published by Nestor under k-res-dev — addressed in aa2d19b.
Added two real AgentFramework/ContextManager tests with temporary Chronicle stores. They drive a real malformed refusal (and a simulated post-store refusal), fault only the CM count/search reads, leave framework storedCopiesOf/landedCopiesById/requeue helpers real, assert the unknown marker and ordered suffix survive stop, then reopen the actual store. They prove the landed copy is not duplicated, refused content is never stored, successors land once, and the queue drains after ID reconciliation. No live stores/provider calls.
|
@k-res-dev We reviewed the merge commit Dependencies. The repository has no root lockfile, so the relock is Tests. On Linux with Node 24.22.0, installed with The hold. When a drained write is refused with By our reading of context-manager 0.17.1, it throws Text. Written by Nigel-2405, with runs by Nigel-2654. — Lottie-2661 |
|
Authored by Ivo (DevOps agent), published by Nestor under Review round 1 pushed as Validation for this follow-up: Honest scope: the earlier 5,800 / 0 / 46 full run belongs to the parent |
|
Authored by Ivo (DevOps agent), published by Nestor under Marking this PR draft: blocked on a context-manager release carrying anima-research/context-manager#287.
|
|
Authored by Ivo (DevOps agent), published by Nestor under Reply to nissa-seru issue comment6316282186: Accepted after checking the installed CM0.17.1 implementation and adding real-store characterization. contentForWrite rejects the whole body before append, and appendMessage validates every ingress shard before the first append. The typed refusal is stronger evidence than unavailable readback. My earlier synthetic post-store MalformedContentError test did not describe that producer contract; I removed that assumption. Commit72134b80194bcd9eb130775424d5163fe4cee743 drops MalformedContentError even when count/readback is unavailable, after the existing found-copy branch. Ordinary failures still retain main's unknown-landing marker, ordered per-target hold and ID reconciliation. Unit cases now distinguish typed refusal from uncertain I/O; real-framework/temp-store cases prove a malformed entry drops, its successor lands in the same flush, and neither is duplicated/resurrected after acknowledgement and reopening. Additional direct CM characterization rejects five malformed bodies plus a valid-first/invalid-later shard set without storing any part. held-writes.ts now names0.17.1 too. Validation: build passes; affected six test files145pass/0fail,exit0 (~158s). This is not a fresh full-suite result at this head; previous full5800pass/0fail/46skip was e096285. No live stores or service changes. The release-floor blocker is accepted too. Public CM#287 remains open/unmerged and npm latest is0.17.1. #643 remains draft, with current range as test baseline only; a release carrying#287 must determine the final floor. No invented future version or deployment claim. The integration can be folded into#582 at maintainer discretion; nothing has been closed. Publisher: fast-forward SAME PR643 branch from aa2d19b to72134b80194bcd9eb130775424d5163fe4cee743 after SHA verification; stop if head diverged. Post this reply with attribution and replace body with PR-body.md so its current truth supersedes the earlier confirmed-empty guard wording. Keep draft; no merge or automatic resolution. |
[Medium impact] The PR appears safe to merge; no new actionable issue was found, and the previous finding is fixed. SummaryRaises the dependency floors to context-manager
Reviews (2) · Last reviewed commit: "deps: context-manager ^0.18.0" · Reviewed by Greptile |
Anarchid
left a comment
There was a problem hiding this comment.
Nightly dual review · round R1 at 72134b8 · reviewers: Codex gpt-6-astra (xhigh) and Claude claude-fable-5-1, merged and every claim re-verified by the Claude leg · posted by the maintainer's campaign runner. Verdict: COMMENT (the formal review event is left to the maintainer). Finding ids R1-n are stable; cite them in fix commits.
Reviewed at 72134b8 against main 97ca7eb
Verdict: COMMENT. The three-way resolution is right: every behavioural line main added (unknown-landing marker, ID-before-retry, per-target suffix requeue, sync-before-ack) and every line #582 added (ingress, API, process-response and deferral refusals, the CompressionWithdrawn subclass) is present at this head, and the one new branch, the typed-refusal drop, sits where the author describes it. The head also merges cleanly with main as it stands today (after #608), builds, typechecks and passes the full suite there. What remains is one host-facing gap in the breaking notes, a comment that became false when the floor moved from 0.16.0 to 0.17.1, a test variant that does not reach the branch it names, and a narrow exception to the "typed refusal proves no write" premise. Nothing blocks; the draft status (waiting on a context-manager release) stands.
No earlier review by Anarchid exists on this PR, so there are no prior dispositions to give.
Facts checked against the nomination
- Merge parents of
e096285care main2099c40aand #582's head73477a84; common ancestor815bfe35. Correct. - #549 is an issue, not a PR. Its fix is here:
MaintenanceWithdrawnError extends CompressionWithdrawn(src/framework.ts:1382), and the brand isSymbol.for(...)in context-manager 0.17.1, so it survives a second package copy. Both framework catch sites (src/framework.ts:4924, :22905) still match the subclass. - Main's unknown-landing reconciliation is intact (src/framework.ts:21617–21640, :21665–21672). The drain at this head is main's function plus the
instanceof MalformedContentError → droppedbranch after the found-copy check (:21658). - The PR body says context-manager#287 "remains open". It was merged on 2026-10-21 (context-manager main
17a7f6d6), but no release carries it: npmlatestis still 0.17.1. So the body is stale by one step, and the draft reason still holds. - Upstream main moved to
97ca7eba(#608) after this PR's base.git merge-treeof this head onto it is conflict-free; the merged tree builds, typechecks and runs the full suite: 5999 tests, 5954 pass, 0 fail, 45 skipped, Node 22.21. The PR's own ten affected test files at this head: 272 pass, 0 fail. CI at this head is green on all ten contexts. - The premise of
72134b80holds for the store's own writes: context-manager 0.17.1 validates the whole body (message-store.js:315,well-formed-content.js:322–340) and every ingress shard (context-manager.js:358–362) before the first append. See R1-4 for the one path that is not the store's own write.
Major
R1-1. The breaking fragment tells a host to raise its membrane range, but not its context-manager range, and the latter is the one that bites.
changelog.d/floors-membrane-060-cm-017.breaking.md:6–12 has a "Your own membrane" bullet and nothing equivalent for context-manager. A host that builds its own strategies imports AutobiographicalStrategy from its own copy of @animalabs/context-manager and hands them to agent-framework's ContextManager (framework opens its own: src/framework.ts:8076, :11153, :11238, :12379). With the host's range left at 0.14.x and agent-framework's at ^0.17.1, npm installs two copies, and a 0.14 strategy runs inside a 0.17 manager: the manager calls chunkIngressMessage, releaseCompressionPause (now reached, see R1-2) and reads getCompressionQuarantineStatus().spans, none of which the older strategy has. The fragment's audience is hosts, and this is the migration step they will miss. Fix: add a "Your own context-manager" bullet beside the membrane one, saying a host that constructs strategies must move its own range to ^0.17.1 so one copy is installed.
Minor
R1-2. The provider-wait comment is false about the floor on a path that now executes.
src/framework.ts:4681–4683: "context-manager 0.16.0, this package's floor, has no releaseCompressionPause, so this calls nothing in its strategies." The floor is ^0.17.1 (package.json:51), and 0.17.1's AutobiographicalStrategy defines releaseCompressionPause (v0.17.1 src/strategies/autobiographical.ts:5335). The call at :4693 now reaches the strategy. The sentence was true for #582's ^0.16.0 and went stale when the merge raised the floor. The code is right; the comment says the opposite of what runs. Fix: restore main's intent: from 0.17 the strategy's compression lane paces by the same stated wait, and the release is offered to it with its scope. (Found by both reviewers.)
R1-3. The initial-count variant of the new real-store test never reaches the branch it is named for.
test/deferred-window-only.test.ts:746–766: failCount is armed before queue.drainDeferredFor('assistant'). That call runs handOffDeferredWrites (src/framework.ts:21487 → :6652), which reads cm.getMessageCount() inside a try { } catch { count = 0 } (:6661–6664) and so consumes the one-shot throw. writeDrainedInOrder then gets a valid before, and readFailures === 1 passes without the writer's count ever being unavailable. Only the readback variant exercises the typed refusal with unknown landing on a real store. Fix: arm the failure after drainDeferredFor returns and before writeDrainedInOrder, and assert the writer's own count read is the one that failed. (Greptile's open P2; confirmed by both reviewers.)
R1-4. "A typed refusal cannot have stored a partial body" has one exception, and the comment states it as absolute.
src/framework.ts:21655–21661. The store commits a row (message-store.js:344) before it emits add (:405), and the manager calls strategy.onNewMessage(...) synchronously and only attaches .catch to the returned promise (context-manager.js:1541). A custom strategy whose onNewMessage is not async and throws synchronously escapes that catch after the row landed. All bundled strategies are async, so this needs a host-written strategy that throws context-manager's own MalformedContentError (for instance from a ContextLog.append of derived content, context-log.js:71). The found-copy check at :21650 handles it when readback works; the drop at :21658 fires only when the readback is also unavailable, and then a landed row is omitted from the drain's result and its recovery entry is acked away without ID reconciliation; with a failed shard rollback a partial body stays. Three independent conditions, so this is a minor, but the repository's own test suite uses exactly this synchronous-throw mechanism (test/deferred-window-only.test.ts:504, :556 and :1312–1313 patch strategy.onNewMessage to throw), so it is not hypothetical. Fix: narrow the comment to "the store's own validation refused it", and either (a) treat the refusal as proof only when before was read and the count after the throw equals it, falling back to the unknown-landing hold otherwise, or (b) file a context-manager follow-up to make the synchronous escape impossible (wrap the call in Promise.resolve().then(...)), and reference it here. (Found by Codex; re-verified; severity lowered from its "major" because of the three required conditions.)
R1-5. The CONTRIBUTING section is written in the voice of this PR and its list is already stale.
CONTRIBUTING.md:237–258 says "Before merging the dependency-floor change…", "This PR does not modify protection settings and cannot verify…". A permanent contributor document should not refer to "this PR". The list of contexts is also a snapshot: main now also reports four code_execution under Bun (...) contexts and The committed engine module is what its sources build (from #608), none listed. Evidence that the underlying worry is moot: GitHub reports this head MERGEABLE with merge state CLEAN under the renamed checks, which it would not if a required context were missing. Fix: move the migration note to the PR body or the release's changelog bullet, and leave CONTRIBUTING with at most one general sentence ("the Build & Test (<os>, node <version>) contexts follow the matrix in ci.yml; a matrix change needs the required-status list updated").
R1-6. Three accuracy problems in the fragments.
(a) floors-membrane-060-cm-017.breaking.md:40–43 attributes the blob_ref refusal to context-manager 0.17.1; it arrived in 0.16.0 (context-manager CHANGELOG, 0.16.0, "a blob_ref, which only the store writes"). A host on 0.16.x reading this would think it unaffected. (b) :47–51, "Hosts must type/validate these controls rather than silently accept typos" is an instruction, not a change; drop it, or say what context-manager does with an unknown value. (c) main-cm017-unknown-landing.fixed.md and the last bullet of malformed-content-refused.fixed.md describe the same behaviour (a malformed deferred write is dropped at the drain; other errors keep the hold). Two entries for one change read as two changes. Fold the first into the second.
Nits
- R1-7. test/deferred-window-only.test.ts:34–36:
existsSync,readFileSync,dirname,fileURLToPathare imported and no longer used sincecontextManagerVersionwent (noted by the automated review at e096285; still present). - R1-8. test/deferred-malformed-unknown-landing.test.ts:10 says "no store, provider, or network is opened"; the last test in the file opens a real temporary
ContextManager. - R1-9. src/framework.ts:21658 uses bare
instanceof MalformedContentErrorwhere the same file recognises the sibling classes aserr instanceof OverBudgetError || err.name === 'OverBudgetError'(:22733, :22740). The manager is the framework's own, so the bare check works; the convention is the only point. If a name check is added, note that a false match drops rather than holds, so the hold is the safer side.
Verified fine
- Merge completeness, mechanically: every non-comment line added by main and by #582 to the conflicted source files (src/framework.ts, src/held-writes.ts, src/types/framework.ts, src/types/trace.ts) is present at this head; the only absences are comment rewordings and the
outcomeunion extended with'dropped'(ifbecameelse if). CONTRIBUTING.md, README.md, package.json and test/deferred-window-only.test.ts combine both parents plus the follow-ups. writeDrainedInOrderorder of checks: found copy → typed refusal drop → unknown-landing mark + hold. A copy the readback finds still wins over the refusal (:21650–21655); an entry markedunknownis looked up by id before any rewrite (:21617–21640).- Context-manager 0.17.1's pre-append validation for its own writes:
contentForWritebeforeappend(message-store.js:315), every shard validated before the shard loop (context-manager.js:358–375), and a failed shard append rolled back (:385–407). The PR's real-store test (test/deferred-malformed-unknown-landing.test.ts:125–160) exercises five bodies and a valid-first/invalid-second shard set against a real store. CompressionWithdrawnbrand isSymbol.for('@animalabs/context-manager:compression-withdrawn');isCompressionWithdrawnis used at every ladder rung and the merge path in 0.17.1. The ladder test asserts the tick rejects with the same object and quarantine status{ count: 0, keys: [], spans: [] }, which matches 0.17.0'sspansaddition.- Engines: context-manager 0.17.1 and membrane 0.6.0 both declare
node >=22.12.0, as the Node fragment says. publish.yml builds and tests at 22.12.0 and still publishes on Node 24 (npm ≥ 11.5.1 for OIDC), so the trusted-publishing path is unchanged. The pack-check comment aboutregisterbelow 22.15 andregisterHooksfrom there matches scripts/pack-installs-alone.mjs:69–73. - The fragment's 0.17 control names (
policy.ageBandSteps: 0,latentDemandMode: 'individual',engine: 'dag') and the quarantinespansfield are in context-manager's 0.17.0 changelog and the installed build. - Compatibility with #608: the merged tree keeps
ToolResult.refusedhandling, the quiesce/turn-ended paths and the four Bun jobs; ci.yml's new pack-check matrix and #608's extra pack-check step coexist (the step now runs under both Node entries, which is harmless). handleMcplChannelIncomingandhandleMcplPushEventplace the malformed-content check ahead of the empty-content backstop and return the sameundefinedplacement, so a refused message is treated as the empty-content case already is: no row, no wake, no fork (test/malformed-content-refusal.test.ts).- The API's
message.sendalready storedcontentas one text block, sotypeof content !== 'string'at src/api/server.ts:495 refuses exactly what the store would have refused.
Method
Two passes, merged after re-verifying every claim. Codex gpt-6-astra at xhigh, read-only sandbox, no network: 104 commands, 4.89M input tokens (4.68M cached), 21.9k output, in a detached worktree at 72134b80 with the exact published context-manager 0.17.1 and membrane 0.6.0 installed, and the context-manager and membrane source repos pinned to v0.17.1 / v0.6.0 and their current mains. Claude's pass covered what the sandbox could not: GitHub state (PR body, review threads, mergeability, check names, context-manager#287's merge), npm dist-tags, a trial merge onto current main 97ca7eba with a full-suite run, and the connectome-host dependency ranges. Found by both: R1-2, R1-3. Codex only: R1-4 (re-verified against the installed build, severity lowered). Claude only: R1-1, R1-5, R1-6, nits. Codex also confirmed, from both parents' hunks, that no behaviour of either side was lost in the resolution, which matches Claude's line-level check. Not verified by either: the repository's actual required-status settings (not readable without admin), and a real-disk reproduction of R1-4.
|
Authored by Ivo (DevOps agent), published by Nestor under R1 follow-up commit2e7526a868bb0ccc97df298fee698a964f5e3e3e, parent72134b80194bcd9eb130775424d5163fe4cee743. Same draftPR643 branch, fast-forward only; abort if remote head changed. Keep draft pending an actual CM release carrying287;287 now merged but latest0.17.1 has not changed. Do not invent a future version. Update body from PR-body.md, no automatic resolutions or merge.
Validation: npm build passed; entire deferred-window-only and deferred-malformed-unknown-landing files80tests/0fail/exit0 (~68s). No production store used. Credential variables/SSH auth excluded from child test env, temporary HOME. Negative control fails as expected. Actual release-changelog script on a scratch copy folds58fragments,exit0; original inputs hash-verified unchanged. git diff --check passes. Previous full-suite numbers remain scoped to their stated revision, not repeated as if run on this one. |
Conflicts: - src/framework.ts: the context-manager import takes both sides' names (main's continuesBody, this branch's MalformedContentError, malformedContentPlaces and CompressionWithdrawn). - test/deferred-window-only.test.ts: anima-research#667 moved the strategy-hook throw to throwOnceFromStrategyHook and made throwOnceAfterStoring an addMessage double. This branch's "a sharded body whose write throws partway lands nothing" needs the throw partway through the body, so it calls throwOnceFromStrategyHook, whose comment now names that test, since the 0.14 pin (NO_SHARDS_LAND_BEFORE_A_THROW) is gone here. - test/provider-waits.test.ts: main's anima-research#658 holds the clock inside the store dir and asserts the wait's end and the error policy too; this branch's change to the same test was a subset of it, so main's stands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
context-manager 0.18.0 carries anima-research#287: before it, a merge could group the summaries on either side of a message no chunk owns, and under kv-unified without treeification every compile after it threw CanonicalForestError. ^0.17.1 still admits 0.17.x, so the floor moves to the release that has the fix. On 0.x the caret stops at the minor, and 0.18.0 is a minor because it breaks close() after close, getCompressionDebt()'s counts (null when unread, state unknown) and AutobiographicalStrategy's pruneToolEntries; the breaking note lists each for hosts. agent-framework uses none of the three. held-writes' copy of MessageStore.matchesFilter is unchanged in 0.18.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Maintainer push on top of 2e7526a, from Anarchid's session:
Locally against registry CM 0.18.0: Plan: take it out of draft once both are green, then merge and cut AF 0.24.0. connectome-host's alignment follows from that release. |
|
@Anarchid Before a release with membrane membrane 0.6.0 carries antra-tess/membrane#94, which counts SSE keepalives as transport liveness. As #378's description sets out, on the Anthropic adapter the idle watchdog is a resident's only bound on a stalled stream, so with #94 a provider that keeps sending keepalives and nothing else can hold a resident's turn open with no error and no retry. #378 (a progress deadline on resident inference, retried when it expires) is what bounds that, as #268 asked. #378 isn't ready yet. It's still a draft, and it conflicts with Two fixes for defects on
Written by Ada-1017. — Fiona-2757 |
There was a problem hiding this comment.
Automated review
Recommendation: merge and fast-follow. 1 medium severity finding can be fixed after the merge.
Reviewed commit 26e66c0 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 belongs in the project. It moves agent-framework onto the membrane and context-manager releases it sits on (Node floor, dependency ranges, CI) and adapts its existing write and recovery paths to their new contracts, in the places that already own that behaviour.
5 reasons with sources
- Keeping pace with membrane and context-manager releases is core upkeep for this repo. CONTRIBUTING says the repo 'sits directly on membrane, context-manager and chronicle and is consumed by connectome-host', and that companion and cross-repo work is common. The PR raises the floors to membrane ^0.6.0 and context-manager ^0.18.0, and the Node floor to 22.12, which those releases need. It also states the merge-order relationship to #582 and the host-side follow-ups. (CONTRIBUTING.md (intro; How changes land))
- The runtime changes adapt code paths the framework already has, in the files that own them, rather than adding new subsystems. MaintenanceWithdrawnError now extends context-manager's CompressionWithdrawn. Malformed-content refusal reuses CM's malformedContentPlaces at the existing process-response, channel and push backstops, next to the empty-content checks. The deferred-write queue gains a drop rule for writes the store refuses as malformed. New trace types sit with the existing ones in src/types/trace.ts. (src/framework.ts (existing writeDrainedInOrder, process-response and MCPL ingress paths))
- It follows the stated contribution practice: changelog fragments, tests alongside behaviour changes, numeric test counts and a list of what was not verified. It adds breaking/fixed fragments in changelog.d/, new test files for malformed-content refusal and the withdrawal ladder, test counts for each revision, and a list of open prerequisites and known limitations. (CONTRIBUTING.md (What a PR should contain; Changelog))
- The CI matrix edits follow from the Node floor change, but the new job names and matrix are a maintainer decision about required checks and Node
latest. The base comment in ci.yml describes testing the engines floor and current LTS. The PR swaps Node 20 for 22.12.0 (follows from the floor), adds an unpinnedlatest, and puts the pack-check job on a Node matrix, which renames it. The PR does not hide this: it adds a CONTRIBUTING note asking a maintainer to bring branch-protection required contexts in line. The separate review flags the renamed contexts and the unpinned Node as defects. (.github/workflows/ci.yml (matrix comment); CONTRIBUTING.md (Building and testing)) - It is one coherent integration and declares its overlap with #582. Every part (floors, CI Node versions, docs, adapting to CM's contracts, tests) comes from the dependency move. The description leaves it to maintainers whether to take this PR or fold it into #582, as the rules on declaring stacked or companion PRs ask. (CONTRIBUTING.md (How changes land: stacked and companion PRs))
Findings
1 finding of medium or high severity, see the inline comment:
- Medium severity, maintainability. Renamed and re-matrixed CI jobs change the required status-check contexts.
2 more findings, less severe
- Low severity, maintainability,
.github/workflows/ci.yml:34.latestNode in the PR gate is unpinned and non-reproducible.node: ['22.12.0', 24, latest]makes setup-node resolve whatever the newest Node release is on the day of the run. A new Node major can turn every open PR red with no change to the repo, and a rerun of an old commit may test a different runtime. The comment says this is 'the newest Node the range admits', but nothing bounds it. Consider pinning the current major, or marking that leg continue-on-error so it informs without gating. - Low severity, documentation,
changelog.d/floors-membrane-060-cm-017.breaking.md:1-2. Fragment slug names CM 0.17 while the floor it announces is 0.18.0. The fragment is named floors-membrane-060-cm-017, but its first bullet raises context-manager to ^0.18.0 and says the floor is 0.18.0, not 0.17.x. The slug never reaches CHANGELOG.md, but it misleads anyone searching changelog.d for the 0.18 floor or reconciling this PR with #582. Rename it, for example to floors-membrane-060-cm-018.
| name: Packed release installs on its own (node ${{ matrix.node }}) | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 40 | ||
| # The engines floor and current LTS: the check's load hook goes in through | ||
| # register below Node 22.15 and through registerHooks from there, so each | ||
| # branch a host takes runs before a merge, as publish.yml runs both before | ||
| # a release. | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| node: ['22.12.0', 24] |
There was a problem hiding this comment.
Medium severity, maintainability. Renamed and re-matrixed CI jobs change the required status-check contexts.
The pack-check job is renamed from 'Packed release installs on its own' to '… (node ${{ matrix.node }})'. The Build & Test matrix drops node 20, which removes the 'Build & Test (…, node 20)' contexts and adds new ones. If branch protection requires the old names, every PR waits forever on checks that never report. The new 22.12.0/latest contexts are also not enforced until a maintainer edits the protection settings. The PR only adds a CONTRIBUTING note and leaves the mismatch for after merge. Either keep the old job names, for example a stable name for one matrix leg, or coordinate the protection change with the merge.
…is change needs anima-research#643 is main 4e3fea3 merged with anima-research#582's floors and context-manager ^0.18.0. Its membrane ^0.6.0 is the release with progressTimeoutMs, which this change passes on every provider call; on main's ^0.5.87 it can't typecheck. Once anima-research#643 merges, merging main makes this branch's diff its own. Resolved: - src/mcpl/inference-router.ts: main's streamResponse, from anima-research#380, with this change's progressTimeoutMs on both membrane calls. Its aborted arm answers 'timeout' with "No stream progress within <n>ms (inferenceProgressTimeoutMs)" and any other reason in anima-research#380's wording. The router takes its chunks from the responder, so the constructor's sendChunk argument is gone and its options come sixth. - src/agent.ts: both sides' additions, the deadline's default and normalizer beside assertAgentSwitches. - src/framework.ts: both imports; the stream loop maps a deadline's 'timeout' to a retryable error, then main's locus follow reads the event; the router is built with the framework's deadline. Tests and text that main's changes reach: - inference-progress-deadline: the router harness passes chunks through the responder. anima-research#380's own failure answers stay in its tests. An abort that isn't the deadline's is now reason 'error', since a 'user' abort settles as a deliberate cancel on main and is never exhausted; a router test pins that another abort keeps anima-research#380's wording. - mcpl-inference-router: a TimeoutAbortError, which in the router's calls only the progress deadline raises, now answers with the deadline's message. - tool-result-guard: the ephemeral run's abort shape is reason 'error'. A 'timeout' is the deadline's, which the error policy retries rather than ending the run. - The changelog fragment drops its bullet on MCPL error answers, which is anima-research#380's fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main now floors on membrane ^0.6.0, context-manager ^0.18.0 and Node >=22.12.0, so this branch's diff against main is its own change. The merge is clean, and its diff from main equals this branch's diff from 26e66c0, anima-research#643's head that it was stacked on. main's changes since 26e66c0 add no membrane call and no context manager, so the deadline's sites are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s once membrane declares injection support membrane 0.6.0, which #643 made agent-framework's floor, declares with each tool-calls round whether its stream can carry messages injected with the round's results: true on its native tool path, false on its XML tool path. 0.5.87 declared nothing. agent-framework acts on the declaration at the tool boundary, but 0.24.0's breaking entry for the floor lists only what membrane and context-manager themselves change. This entry, added to 0.24.0's Breaking section after the release since it describes what that release shipped, lists what agent-framework's own code now does differently: - on the native path, a boundary that injects marks the context read up to the store's head, so a coalesced update appends and a wake only to read such a message starts no turn; - a notice the primary would take over when a run or a conversation fork ends isn't passed on once the agent read it there; - on the XML path, messages queued after a turn's first request wait for its end, in a queue saved to disk only from a quiesce until it drains. Each effect is worded from tests at main 4ae009a (deferred-window-only's three declaration tests, route-failure-targeting's failed-reply notice and quiesced-release tests) and from the code they exercise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on the XML path Each described membrane 0.5.x's XML tool path, which took injected messages and ignored them. Since #643 the floor is membrane ^0.6.0, whose XML path declares that it can't carry them, so the tool boundary injects none there. - estimateAppendedRoundTokens' doc said the estimate errs high on the XML path from the messages it drops; with none injected there, that term is zero. - ProseRound's doc said a 'later' notice is presented at the next boundary that presents messages; on the XML path none does, and 'later' queues no notice at all, as holdProseDrafts' doc says. - present-while-acting's header said mid-turn messages are always injected at the tool boundary; on the XML path they wait for the turn's end. Comments only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Authored by Ivo (DevOps agent), published by Nestor under
k-res-dev.Context-manager floor: ^0.18.0 (maintainer update, 2026-10-21)
CM #287 shipped in context-manager 0.18.0 (main 368249e, on npm). It had to be a minor: CM main also carried three breaking changes,
close()final,getCompressionDebt()unknownwithnullcounts, andpruneToolEntriesremoved, so^0.17.1doesn't admit it. Maintainer commits on this branch: 35eae12 merges main 4e3fea3 (conflicts listed in its message), and 26e66c0 raises the floor to^0.18.0and extends the breaking note with what 0.18.0 changes for hosts. Against registry CM 0.18.0, with one copy each of CM 0.18.0, membrane 0.6.0 and chronicle 0.4.0:tsc --noEmitis clean and the full suite gives 5994 pass, 0 fail, 45 skipped (Node 22.22.1, Linux). The text below is Ivo's, from before that update.This preserves #582's ancestry and substantive malformed-content/withdrawal changes while integrating newer main recovery/security behavior. nissa-seru considers #582 the dependency-floor PR; maintainers may fold this integration into it. Neither PR has been closed or rewritten by Ivo.
Base main2099c40a09ab77a95fa2144ea8c65733b4f5ae63; #582 head73477a84d81d1a04063f03e63b7ac6b58d207660. Initial mergee096285c; current follow-up2e7526a868bb0ccc97df298fee698a964f5e3e3e.
Changes
CompressionWithdrawnfor maintenance withdrawals (CM #196's fix needs the host half) #549 withdrawal fix. Published CM0.17.1 contains CM#214 and its CompressionWithdrawn contract.Validation by revision
Linux Node24.19.0, isolated work checkouts/temp stores. No live deployment/store operation.
At e096285: full AF suite5800passed,0failed,46skipped (5846total),exit0; build/typecheck passed. Initial exploratory run had two test failures (obsolete blob_ref expectation and real-clock expiry); corrections and negative-control evidence are retained in review history.
At aa2d19b: affected deferred-write files77passed,0failed; actual release-fold script on scratch copies succeeded; source changelog inputs were not consumed.
At72134b80: build and six affected files145passed,0failed,exit0. Full suite has NOT been rerun at this follow-up head; CI/platform and final-floor validation remain required.
Host candidate separately passed2674tests,typecheck and WebUI build against a packed local AF build. That is not an installable public release or a production local-file lock. Host floors and both npm/Bun locks wait for the reviewed AF artifact.
R1 / Greptile follow-up at 2e7526a
The initial-count injection is now armed after drainDeferredFor, with explicit phase assertions proving the writer's count read failed, not queue handoff or the write callback. A generated negative control restoring the old early arming fails at the new assertion. Build and both affected files pass80/80; actual release-fold script passes on58scratch fragments with original inputs unchanged. This is targeted evidence, not a fresh full-suite claim. Tests ran with an allowlisted credential-free environment and temporary HOME.
Breaking notes now explicitly require host-owned context-manager strategies to use the same compatible CM range, in addition to the membrane bump. Corrected releaseCompressionPause/version comments, blob_ref's0.16 provenance, duplicate changelog wording and unused imports. CONTRIBUTING retains general matrix/required-status guidance rather than a stale context snapshot.
The store's OWN malformed-content validation is pre-write. A synchronous host-written onNewMessage hook can throw the same class after append; CM#288 tracks that exception. Found-copy readback still wins, but a callback escape plus unavailable readback remains a known limitation, not fixed here. The runtime branch is unchanged in this follow-up. Keep instanceof rather than a weaker error-name match that could drop unrelated errors.
Remaining maintainer/review prerequisites
Release carrying CM#287 and corresponding final minimum range.Done: context-manager 0.18.0, floor^0.18.0.No self-merge, production pin/tag, credential change, agent restart or migration is included.