Skip to content

Pin the group-affinity invariants GH-3792 relies on, and simulate a version bump with slow starts - #3804

Merged
jeremydmiller merged 1 commit into
mainfrom
gh-3792/group-affinity-regression-pins
Aug 3, 2026
Merged

Pin the group-affinity invariants GH-3792 relies on, and simulate a version bump with slow starts#3804
jeremydmiller merged 1 commit into
mainfrom
gh-3792/group-affinity-regression-pins

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Immediate follow-up to #3792, adding the regression coverage identified while reviewing it. Relates to #3753, #3779, #3785.

Four new tests, each verified red against the pre-#3792 implementation

Three placement pins in distribute_by_group_affinity:

  1. A settled blue/green split is a fixed point — re-evaluating the settled state (same fleet, same capabilities, everything running where the last evaluation put it) moves nothing. Database affinity is per agent family, not per database: 73% of shard databases have durability and projections on different nodes #3785 counted ~45,000 ReassignAgent decisions in six minutes during a rollout ramp; the partition incumbent rule (members[0].AssignedNode, all-members-on-one-node) is subtle enough to regress silently, and churn here means agent restarts at 512-database scale.
  2. A rolling restart costs at most two hosts per database. The partition key is the exact declaring node-id set — flagged in Fix DistributeByGroupAffinity assigning a bumped projection version to nodes that cannot build it #3792 as a deliberate judgment call, with an offer to relax it. This pins the invariant that makes the strictness safe to keep or relax: overlapping-but-unequal capability sets still land every database on ≤2 hosts. Anyone coarsening the key can now refactor against this.
  3. Asymmetric fleets strand nothing. The per-node ceiling is computed over all nodes including incapable ones, so a brand-new green fleet declaring only the bumped agents is where the arithmetic would break first: nothing unassigned, every bumped agent on green, neither green node idle.

One end-to-end sim in slow_agent_start_convergence — the #3753 deploy shape:

#3753 is not about slow starts in general (production converges fine without a version bump). It is slow starts and a blue/green capability split at the same time — and until #3792 those two conditions were each covered by tests that passed while their intersection failed. The existing sim ran homogeneous capabilities through DistributeEvenly; this variant drives the real NodeAgentController.EvaluateAssignmentsAsync with:

  • grouped agents through DistributeByGroupAffinity, as EventSubscriptionAgentFamily uses for a multi-database store
  • a blue leader whose own family cannot enumerate the green agents — they reach the grid solely through node capabilities, exactly as in production
  • the field-calibrated long-tailed start costs (p50 27s / p95 82s / 215s tail)

and asserts convergence in bounded rounds, zero cross-fleet placement at any point during the wave, zero stops/reassignments/double-starts, and the two-hosts-per-database pool bound holding throughout.

Harness seams (no existing behavior changed)

  • FakeAgentFamily: constructor over explicit agent names (names may carry path segments to model grouped, versioned agents), and an overridable Distribution.
  • SlowStartCluster: overload taking the leader's family + per-node capability sets, deriving the agent universe from the capability union the same way NodeAgentController seeds the real grid.

Verification

🤖 Generated with Claude Code

https://claude.ai/code/session_0116vfBcKwcjWn8msM4ZjkuA

…ersion bump with slow starts

Follow-up test coverage for GH-3792 (blue/green group affinity), all four verified to
fail against the pre-3792 implementation:

- a_settled_blue_green_split_does_not_churn_on_the_next_evaluation: the settled split
  state is a fixed point of the placement. GH-3785 counted ~45,000 ReassignAgent
  decisions during a rollout ramp, and the partition incumbent rule
  (members[0].AssignedNode, all members on one node) is subtle enough to regress
  silently.
- a_rolling_restart_keeps_every_database_on_at_most_two_hosts: pins the invariant that
  makes the strict partition key (exact declaring node-id set) safe to keep or later
  relax -- overlapping-but-unequal capability sets still cost no third pool set.
- asymmetric_fleets_strand_nothing_and_load_every_capable_node: the per-node ceiling is
  computed over ALL nodes including incapable ones, so a brand-new green fleet that
  declares only the bumped agents is where the arithmetic would break first.
- a_version_bump_converges_with_slow_starts_and_no_cross_fleet_placement: the GH-3753
  deploy shape end to end. GH-3753 is not slow starts in general -- production converges
  without a version bump -- it is slow starts AND a capability split at once, and until
  now each condition was covered by tests that passed while their intersection failed.
  Drives the real leader evaluation with DistributeByGroupAffinity, a blue leader whose
  family cannot enumerate the green agents, long-tailed start costs, and asserts
  convergence, zero cross-fleet placement, zero churn, and the two-hosts-per-database
  pool bound throughout.

Harness seams, added without touching existing behavior: FakeAgentFamily takes explicit
agent names and an overridable Distribution; SlowStartCluster takes a leader family and
per-node capability sets, deriving the agent universe from the capability union exactly
as NodeAgentController seeds the real grid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0116vfBcKwcjWn8msM4ZjkuA
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant