Repository navigation
fix(mcpl): a retarget opens its new target past work a removal cancels - #594
Merged
nissa-seru merged 1 commit intoOct 19, 2026
Merged
Conversation
The retarget check in handleChanged left a channel decided open (or tuned out) to any lifecycle work outstanding for it, since such work reads its target again before it confirms anything. Work queued before its server's registrations were removed cancels when it would run instead, so where that was all that was outstanding, nothing opened the new target. The check now takes outstandingWorkCarriesOut, which counts outstanding work only when its newest operation was queued since the server's last removal, as the failed-open put-back already does. It needs the channel registered again in a way that queues no lifecycle work, before the old work settles: from a push (ensureChannelRegistered), or while the host doesn't direct the server's lifecycle. The predicate's doc and the put-back's comment now say that a push's registration queues nothing, so it's a registration by channels/register, or as added by channels/changed, that carries a decision out after a removal. Tests: a channel decided open, and one tuned out, registered again from a push after its server's removal while its old target's open is held, then retargeted, opens the new target; so does one registered again without channels.lifecycle and retargeted once the grant is back. And a retarget while an open queued since the removal is in flight queues nothing more: that open goes to the new target, once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nissa-seru
pushed a commit
to rufalupa666-netizen/agent-framework
that referenced
this pull request
Oct 20, 2026
Main has moved since the last merge (037aa79), by 26 pull requests, among them anima-research#589 (the channel tools and speech say what happened), anima-research#597 (publishForAgent's refusal says why), anima-research#595 (channel_decline's status), anima-research#601, anima-research#568 and anima-research#594 (channel opens and retargets), anima-research#495, anima-research#596 and anima-research#599 (code_execution's channel and how a script's ending reads), anima-research#617 (a sleeping agent wakes for the host's heartbeat server) and anima-research#616 (stop() ends each live conversation fork). Two files conflict: - src/mcpl/channel-registry.ts: routeSpeech's no-locus branch. Main gives the agent a reason in its own words and keeps the old text for the log; this branch rewrote the comment above it. Both are kept. - test/event-gate.test.ts: both sides append a describe block after the quiesce tests. Both blocks are kept, this branch's first. 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.
A retarget of a channel decided open (or tuned out) now opens the new target when the only lifecycle work outstanding for the channel was queued before its server's registrations were last removed. That work cancels when it would run, so before, nothing opened the new target and the channel stayed unopened there although decided open.
Problem
Line numbers are at this pull request's head, except where they say
main.handleChanged(src/mcpl/channel-registry.ts) reconciles a retargeted channel decided open or tuned out only when no lifecycle work is outstanding for it (lifecycleOutstanding.has, line 1107 onmain). The reason is that an operation queued or running reads its target again before it confirms anything, so it opens the new target itself.An operation queued before
removeServerdoesn't. At its removal-epoch check (runLifecycleOperation, line 2612) it throwsChannelLifecycleCancelledErrorand opens nothing. Where that was all that was outstanding, the retarget queued nothing.That takes a channel registered again after the removal, before the old work settles, in a way that queues no lifecycle work:
ensureChannelRegistered);channels.lifecycle), whose grant then comes back.A registration that reconciles queues new work, and that work carries the decision out.
Changes
outstandingWorkCarriesOut(line 2745), which fix(mcpl): an open by speech or a reply that fails puts back the decision it replaced #568 added for the failed-open put-back (line 3142). It counts outstanding work only when its newest operation was queued since the server's last removal (lifecycleQueuedRemovals).channels/register, or as added bychannels/changed, that carries a decision out after a removal, since a push's registration queues no lifecycle work.What each part is for
main's check, the condition fails the four new tests.removeServerinstead, withmain's plain check, fails two tests. One is the new test that retargets while work queued since the removal is in flight. The other is fix(mcpl): an open by speech or a reply that fails puts back the decision it replaced #568's "when a reply's open fails while its server is removed and added again as initially open, the default put back follows the new registration". A cancelled operation decrements its channel's count when it settles (line 2727), so the reset lets it delete the count of the work queued since the removal.What callers will notice
channels/openfor the new target when it retargets a channel decided open or tuned out that came back after the server's removal by one of those two routes, before the old work settled. The host sends it where it directs the server's lifecycle (channels.lifecycle) at the retarget.Tests
channel-lifecycle-order. Four new tests. In each, a channel's open to A is held, and then its server is removed.channels.lifecycle, and retargeted once the grant is back.open A, thenopen B, and the channel open.open A,open B,open C.main's source and this change's test file, the four new tests fail and the file's other 128 pass. Each fails having sent onlyopen A.npm run typecheckis clean, andnpm testran 5,407 tests, 5,380 passing, 0 failing and 27 skipped. Each of the 253 test files run alone gave the same totals.main's CI at63835621(Ubuntu, Node 24.21.0) ran 5,403 tests, 5,375 passing and 28 skipped. The four more here are the new tests. The one skip fewer is a Bun test, which CI skips because Bun isn't on its PATH.mainf63bbfa8(run 39321194725) passes all four Build & Test legs, Ubuntu and macOS each with Node 20 and 24, and the packed install. The changelog check passes too (run 39321194547).Not verified
changelog.d/retarget-after-removal-opens.fixed.md.🤖 Written by Cuthbert-2453 (Claude Opus 5.5), contributing through nissa-seru. Petra-1012 (Claude Opus 5.5) found it reading #568, and read each revision.
Description checked and posted by Lottie-2661 (Claude Opus 5.5).