Repository navigation
feat(llm): resolve KV-routed sessions through the selection core - #84
Draft
ishandhanani wants to merge 1 commit into
Draft
ishandhanani wants to merge 1 commit into
ishandhanani wants to merge 1 commit into
Conversation
ishandhanani
had a problem deploying
to
external_collaborator
October 6, 2026 08:39 — with
GitHub Actions
Failure
The frontend KV plane stops running its own session layer. It hands the selection core a `SessionBinding` (the request's session key or subagent group, plus any explicit target the binding must agree with) under `Lease` admission; the core resolves the session through the partition's `AffinityResolver`, enforces the binding with the scheduler's pin rule, and returns the uncommitted `Hold` in `Selected`. The host commits it through the same resolver once dispatch has returned a response stream and ties the lease to that stream, as before; a failed dispatch binds nothing. Hard bindings move from an exact `pinned_worker` to the `Hard` requirement: selection is limited to the bound worker while it is eligible, an ineligible target selects normally, and a hard-bound request no longer waits in the router for its busy worker. A binding whose worker discovery no longer knows is dropped before selection and re-bound to the worker the request ran on. A live hard binding the request's own constraints steered away from is rejected before dispatch (`AffinityResolver::check_dispatch`) and the binding is kept. A full table routes the request without affinity instead of failing it. The frontend installs its own liveness source, discovery plus the DP-rank range, on the partition resolver; the prefill KV host uses its partition's table, so prefill and decode bind independently as they did. Query-only selections (route previews, prefill-busy probes, query_instance_id) read the binding without a hold. The explicit-pin rank merge is replaced by the pin rule: a worker-only target on a multi-rank worker narrows selection to that worker and the binding, or selection, chooses the rank. Builtin routing modes keep the coordinator for now. Host tests cover commit after dispatch and lease release with the stream, no binding on a failed dispatch, re-binding from a departed worker, and explicit-target agreement; a core test covers the lease-admission hold and query bindings. Fleet-wide admission for a busy bound worker is covered by the scheduler tests in the previous PR. Signed-off-by: Ishan Dhanani <ishandhanani@gmail.com>
ishandhanani
force-pushed
the
idhanani/session-affinity-unification-p3
branch
from
October 6, 2026 20:42
e1e021a to
b9fd095
Compare
ishandhanani
had a problem deploying
to
external_collaborator
October 6, 2026 20:42 — with
GitHub Actions
Failure
ishandhanani
added this pull request to stack #89
October 6, 2026 20:45
This branch had an error being deployed
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.
Summary
Third PR of the session-affinity unification stack (base:
idhanani/session-affinity-unification-p2). The frontend KV plane stops running its own session layer and uses the sharedAffinityResolverthrough the selection core.Before. The frontend acquired the session itself (
AffinityCoordinator), pinned aHardbinding as an exactpinned_worker, checked the bound worker's liveness only after a failed selection, retried once, committed after dispatch, and returned HTTP 529 when the table was full. The core never saw the session (SessionBinding::None).After. The KV plane hands the core a
SessionBinding(the request's session key or subagent group, plus any explicit target the binding must agree with) underLeaseadmission. The core resolves the session through the partition's resolver, enforces the binding with the scheduler's pin rule, and returns the uncommittedHoldinSelected. The host commits through the same resolver once dispatch has returned a response stream and ties the lease to that stream, as before.How the frontend KV host maps to the resolver
x-dynamo-session-affinity-idcontext, subagent group key (unchanged, host-side)DiscoveryLiveness: discovery knows the worker and the bound rank is within its DP range (today'saffinity_target_is_valid), installed on the partition resolver viaSelectionPartition::session_affinity_with_livenessLease: core resolves, scheduler applies the pin rule, hold returned inSelected::affinity_holdbind_affinity); a failed dispatch releases the hold and binds nothingtracked_stream)await_with_cleanup_policywrapper); a decode leg with staged KV waits through a stop under the cleanup budgetAffinityResolver::check_dispatch: a liveHardbinding the request's own constraints steered away from is rejected before a worker starts the request; the binding is keptPrefill and decode: the KV prefill host now uses its own partition's table (
kv_chooser.host_affinity) instead of a free-standing one. Prefill and decode keep separate tables, which is deliberate: they bind to different worker pools and may carry different TTLs (the prefill card'ssession_affinity_ttl_secs).Behavior changes (frontend KV, session affinity enabled)
Decisions with defaults from the design, each needing sign-off:
pinned_workerrequests still wait in the router (unchanged).InvalidArgument, binding kept (was: rejected at selection, binding kept).Hardrequest whose bound worker the overload detector has shed selects another worker and is then rejected before dispatch (InvalidArgument), binding kept. Before, the exact pin ignored overload and dispatched to the overloaded worker. This follows decision 1 of the design but is the most visible change for operators running the frontend overload detector; flagging it explicitly.AffinityResolver::full_table_fallbacks, a counter on the resolver that is not yet exported as a metric) instead of returning HTTP 529 (ResourceExhausted).InvalidArgument(now by the core, mapped fromSelectionError::BadRequest).Where the core and the frontend differ on a live
Hardmismatch: the core rejects at commit and the table drops the binding; the frontend rejects before dispatch and keeps the binding (nothing ran; the request, not the session, caused the mismatch). Recorded in the design note; open to aligning either way.Builtin routing modes are unchanged in this PR (next PR).
Validation
Run with
RUSTC_WRAPPER=and a per-worktreeCARGO_TARGET_DIR.cargo test -p dynamo-kv-router --features standalone-selection --libcheck_dispatchtest, +1 core lease-admission test)cargo test -p dynamo-llm --lib kv_routercargo test -p dynamo-llm --lib session_affinitycargo test -p dynamo-custom-policy-builtin -p dynamo-mockercargo test -p dynamo-ext-proccargo clippy -p dynamo-kv-router --features standalone-selection,bench --all-targets -- -D warningscargo clippy -p dynamo-llm -p dynamo-ext-proc -p dynamo-custom-policy-builtin -p dynamo-mocker --all-targets -- -D warningscargo fmt --all -- --checkNew host tests (
routing_host/tests.rs):kv_session_commits_after_dispatch_and_releases_with_the_stream,kv_failed_dispatch_does_not_bind_the_session,kv_binding_to_a_departed_worker_rebinds_on_dispatch,kv_explicit_target_must_agree_with_the_session_binding. Core:lease_admission_returns_the_session_hold_for_the_host_to_commit. Resolver:check_dispatch_rejects_only_a_live_hard_mismatch.Existing frontend tests that pin the new semantics unchanged:
stale_affinity_rank_recovers_within_request,stale_affinity_rebind_classifies_once_without_intermediate_abort,config_watch_gap_preserves_hard_affinity,request_constraints_preserve_worker_only_affinity,migration_exclusion_preserves_hard_affinity_without_widening_or_escaping_hard_pins,kv_stopped_decode_request_survives_a_contended_session_affinity_wait,route_preview_does_not_acquire_session_affinity,aborted_route_plan_drops_pending_affinity_initialization, the subagent group tests.Deleted test:
affinity_pin_supplies_rank_for_matching_explicit_worker(kv_selection.rs), with themerge_affinity_pinhelper it tested; the rank merge is replaced by the pin rule (behavior change 5). "Hard no longer waits for a busy bound worker" is covered at the scheduler level (hard_affinity_admission_stays_fleet_wide, PR 1); the frontend test harness has no queueing fixture, so no frontend-level test was added for it.Python validation ran on the stack tip (Phase 6 head, which includes this change) after
maturin develop --uv --features select-servicein<worktree>/.venv, withetcdv3.5.21 andnats-serverv2.10.26 onPATH:pytest tests/router/test_router_e2e_with_mockers.py -k session_affinitytest_mocker_session_affinity[etcd],[file],test_disagg_per_role_session_affinity): two KV frontends, the replicated binding overrides each frontend's own KV-prefix placementpytest lib/bindings/python/tests/test_kv_bindings.py -k "affinity or session"pytest tests/router/test_router_e2e_with_mockers.py(whole file, 56 tests)Flaky on CI: TCP round-robinskip marker). First pass with aselect-service-only build failed 5 tests on missing build features (dynamo.indexerneedskv-indexer; the AIS perf model needsais-forward-pass); rebuilt withselect-service,kv-indexer,ais-forward-pass(a subset of the shipped wheel's feature set) and those 5 passed in 90 sRelated Issues