Reassignments run in the source node's lane and batch by AgentStartBatchSize (GH-3749, GH-3748) - #3757
Merged
jeremydmiller merged 1 commit intoJul 31, 2026
Conversation
…tchSize (GH-3749), with work-scaled reply windows on every batched agent command (GH-3748) Two defects and a mitigation, all from the same field investigation (GH-3753: a rolling deploy carrying a projection version bump could not converge): GH-3749 defect 1: ReassignAgent ran in the DESTINATION node's dispatcher lane while doing its blocking StopAgent round trip against the SOURCE. Lanes are strictly serial, so a source that was merely busy (every stop slow behind the side-effect gate) froze every batched AssignAgents chunk queued for a healthy destination — measured in the field as 22 reassignments starving 1,290 placements on an idle node. ReassignAgent is now keyed to OriginalNode, so a slow or dead source wedges nothing but its own lane; the start half already cascades into the destination's lane via the dispatcher. GH-3749 defect 2: reassignment was the one command type batchCommands never chunked, so a rebalance emitted thousands of individual stop round trips that AgentStartBatchSize had no effect on (verified by the reporter at 50, 100, 400, and 1500). A new ReassignAgents command batches all agents moving between the same two nodes, stops them in one round trip, and cascades one AssignAgents for exactly the CONFIRMED subset — an unconfirmed stop is never followed by a start elsewhere, preserving the GH-3698 single-copy invariant. GH-3748 mitigation: the reply window for every batched agent command (AssignAgents, StopRemoteAgents, ReassignAgents) now scales at 30 seconds per agent once a batch exceeds 20 agents, via the shared AgentBatchTimeouts. The old 30s + 1s-per-agent window assumed an agent starts in about a second; a Marten shard behind a version bump measures 27s p50 / 215s max, so no achievable batch size could ever satisfy the old window (15 of 17 chunks timed out at AgentStartBatchSize = 100). This widens the backstop; it does not decouple acknowledgement from the work itself, which is GH-3748's root shape and stays open. The SlowTests harness gains agent_reassignment_at_scale: one node owning the old agent generation with slow stops, then a version-bump deploy (new agent generation + two joining nodes) — asserting the fresh nodes are carrying agents within a minute, full convergence within minutes, and that the placed count never marches backwards by more than one in-flight chunk (the field failure went 2,401 → 1,885). Passes with this change; fails on main without it. Note SlowTests is not part of any CI workflow — it is a locally-run harness. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013eR4GL278688VhyhrGcttJ
This was referenced Aug 1, 2026
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.
Closes #3749. Mitigates #3748 (root ack-decoupling stays open — see below). Part of the #3753 campaign.
What changed
GH-3749 defect 1 — wrong lane.
ReassignAgentran in the destination node's dispatcher lane while doing its blockingStopAgentround trip against the source. Lanes are strictly serial, so a source that was merely busy (every stop slow behind the projection-version-bump side-effect gate) froze every batchedAssignAgentschunk queued for a perfectly healthy destination — the field measurement was 22 reassignments starving 1,290 placements on an idle node.ReassignAgent.DestinationNodeIdis nowOriginalNode.NodeId, so a slow or dead source wedges nothing but its own lane, restoring the invariantAgentCommandDispatcher's doc comment claims. The start half already cascades throughEnqueueinto the destination's own lane, so it still orders behind that node's queued work — which also covers the GH-3698 pending re-target case, so the narrowStopInSourceLaneflag is gone.GH-3749 defect 2 — never batched. Reassignment was the one command type
batchCommandsnever chunked; a rebalance emitted thousands of individual stop round trips andAgentStartBatchSizehad no effect on any of them (reporter verified at 50, 100, 400, 1500). NewReassignAgentsbatches all agents moving between the same two nodes, chunked byAgentStartBatchSize: oneStopAgentsround trip, then oneAssignAgentscascade for exactly the confirmed-stopped subset — an unconfirmed stop is never followed by a start elsewhere, preserving the GH-3698 single-copy invariant. Unconfirmed stragglers are re-decided by a later evaluation.GH-3748 mitigation — per Jeremy: every batched reply window, 30s/agent past 20 agents. Shared
AgentBatchTimeouts.ReplyWindowFor(n)(30s + n×1sup to 20 agents,30s + n×30sbeyond) now backsAssignAgents,StopRemoteAgents(previously a flat 30s default at any batch size), andReassignAgents. The old 1s-per-agent assumption is off by 1–2 orders of magnitude for a Marten shard behind a version bump (27s p50 / 215s max measured), so no batch size could ever satisfy the old window. Not the root fix: acknowledgements still wait for the work itself, and the single-agent 60s ack window (WolverineRuntime.Agents.cs) is untouched because the >20-agents condition can't apply to it — so #3748 should stay open for ack/work decoupling.Evidence — new SlowTests harness (
agent_reassignment_at_scale)Miniature of the reported deploy: one node owning 240 old-generation agents with 2s stops (busy source), then a version-bump deploy — universe grows to 480 and two fresh nodes join, so reassignments and first-time placements are in flight together. Same knobs as the reported cluster (batch 50, parallelism 10).
MaxAgentStartParallelismThe unfixed run reproduces every headline symptom of #3753: a node handed hundreds of placements holding zero, the total marching backwards, and cluster-wide start concurrency capped at a single node. The test fails on main and passes here (verified both ways), and the pre-existing GH-3698
agent_assignment_at_scaleharness still passes.wolverine.slnxbut not run by any CI workflow or Nuke target — both scale harnesses are locally-run. The 261 CoreTests agent tests (11 new: lane keying, confirmed-subset cascade, value equality, chunking throughEvaluateAssignmentsAsync, reply-window arithmetic) do run in CI.🤖 Generated with Claude Code
https://claude.ai/code/session_013eR4GL278688VhyhrGcttJ