docs: refine BEP-1053 ROUTER frontend mode after design review#12331
docs: refine BEP-1053 ROUTER frontend mode after design review#12331achimnol wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the BEP-1053 design document to reflect post–design-review decisions for ROUTER frontend mode, clarifying routing placement/HA, key custody/visibility, and desired-state propagation semantics across Manager ↔ coordinator ↔ router worker.
Changes:
- Refines terminology and core entities (publications, aliases, authority/node) and documents per-authority scoping.
- Updates key issuance/visibility and revocation semantics (explicit-at-issuance shrink-only visibility, two-tier revocation, optional strict all-live-nodes confirmation).
- Expands coordinator/worker design details (per-node liveness via
node_id, conditional snapshot polling viaknown_revision, and hash-only key custody).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| `accepted_traffics`. The ROUTER worker is the inference/HTTP worker; with the | ||
| unbounded-slot treatment above, `pick_worker()` selects it for inference | ||
| circuits. | ||
| - **Operational constraint (documented, not enforced):** do **not** colocate a |
There was a problem hiding this comment.
Could we fail fast instead of silently misrouting? For example, when an app proxy worker is registered, could we raise an error if a ROUTER and an app proxy worker type that can receive legacy inference traffic are registered at the same time?
There was a problem hiding this comment.
Agreed. Adopted as-is in the second review round (2026-07-15). Clarified that:
- Registration now rejects with an explicit error any combination that would make one coordinator host both a ROUTER worker and a stock worker whose
accepted_trafficsincludeinference, in either registration order. Interactive stock workers still colocate freely.
Doc relocation:
- Since the doc was restructured in 4d3f1ef, this now lives in
BEP-1053/coordinator.md("FrontendMode.ROUTERand registration") with a Decision Log entry in the main doc, andBEP-1053/migration.mddocuments the operational path for existing deployments (a dedicated coordinator for the ROUTER authority; rollback steps included).
| aliases JSONB # ["alias-a", …] additional names that route identically | ||
| mappings JSONB # [{"endpoint_id": UUID, "ratio": float}] |
There was a problem hiding this comment.
Is there a reason to keep this in a JSONB column instead of normalizing it into a separate table?
There was a problem hiding this comment.
Reasoning for keeping aliases as an embedded column rather than a child table, on the coordinator specifically:
- Every read is whole-entity. The only consumers (the snapshot builder and the model-updated event) always emit the full publication. A child table would add a join/aggregate to every snapshot and event build, only to reassemble the exact array we stored.
- Every write is whole-entity. Mutations arrive as full-state upserts (
PUTwith complete state, last-writer-wins), so a column makes an upsert a single-row write; a child table turns each upsert into a delete-and-reinsert of child rows inside a transaction, for the same end state. - Separation of concern. The heavy burden of validation (e.g., uniqueness of alias) is already handled by the Manager.
model_publication_namesstores primary + aliases as rows with anis_primaryflag andUNIQUE(authority, name) (BEP-1053/manager.md, 4d3f1ef).
- This coordinator-internal design can be updated whenever we want in the future, though, without impacting the wire protocol. Even we proceed with BEP-1005 (merging the coordinator into the manager), the coordinator-specific mirror could be migrated as a "cache" layer.
| `?known_revision=`; an unchanged revision yields a cheap `304`. (An explicit | ||
| query param is used rather than HTTP `ETag`/`If-None-Match` because the |
There was a problem hiding this comment.
For extensibility, I think it would be good to introduce a middleware layer and align the implementation with it.
There was a problem hiding this comment.
Recorded the trade-off as an Open Question (BEP-1053/coordinator.md "Snapshot endpoint" since 4d3f1ef).
The catch with a generic conditional-GET middleware: it can only compare after the handler has already built the response body, so it saves response bytes but not the DB read. Skipping the read requires a per-route "current version" resolver — which is the known_revision check under another name, plus indirection.
Alternative way:
- Adopt the standard
ETag/If-None-Matchheader form, implemented as a small handler-level helper (the authorityrevisionas a strong ETag, early 304 before the state query) rather than a blanket middleware.- It keeps standard HTTP semantics and lets future snapshot-style endpoints reuse the helper, without pretending the version check can be route-agnostic. The router-side client change is a one-line header swap, which we've confirmed is acceptable.
Checks on ecosystem compatibility:
- The
ETagheader design is compatible with any client. None of the public LLM APIs (OpenAI, OpenRouter, Anthropic) use HTTP conditional caching on their data planes (they cache by request-body keys such as prompt caching, OpenRouter'sX-OpenRouter-Cache-*) and our ETag use would sit only on the private coordinator↔worker control-plane endpoint, which the end-user clients never touch.
| publication binds **a primary model name (plus optional aliases)** to **one | ||
| or more deployment endpoints** (each with a split `ratio`) on **one router | ||
| authority**, and carries a `control_mode`. It is the object users | ||
| create/edit/delete; it is stored Manager-side (source of truth) and mirrored |
There was a problem hiding this comment.
Will the same table be added to the manager database with the same schema as well?
There was a problem hiding this comment.
Yes on persistence, no on "same schema".
The difference is intentional (see the sibling JSONB thread for the coordinator half). The stack splits state three ways:
- The Manager owns the normalized source of truth and validation (
model_publications+model_publication_nameswithUNIQUE (authority, name)as a DB constraintmodel_publication_endpointswith CASCADE-backstop FKs +model_api_keys, specified inBEP-1053/manager.mdsince 4d3f1ef). - The coordinator owns a wire-shaped denormalized mirror whose only job is entity-granular replay to stateless router nodes.
- The router holds everything in memory only (its key store is explicitly never persisted) and rebuilds from the coordinator's snapshot on (re-)registration.
Resolve all four open questions and fill the design gaps surfaced in a design-review grilling session: - Endpoint placement via scaling-group routing; pick_worker treats ROUTER as unbounded; dedicated-coordinator topology (drop app-filters from the design). - Per-node liveness (ephemeral node_id, valkey liveness set as source of truth, derived `nodes`, dual-mode for backward compat) + REST/metrics exposure. - Visibility model: explicit-at-issuance, RBAC-validated, shrink-only; periodic Manager reconciler for the RBAC-driven shrink (no RBAC events to hook). - Two-tier revocation, documented; opt-in strict all-live-nodes confirmation (ack event carries node_id). - Hash-only key custody (plain SHA-256): token_hash replaces plaintext token. - Single per-authority revision + conditional poll (?known_revision -> 304). - Per-authority publication scope; multi-authority = caller composition. - First-class aliases (one deployment under several names, gated per key). - Atomic key rotation; overlap via two key_ids. - control_mode (manual/strategy-managed) to avoid BEP-1049 dual-writer. - rate_limit documented as per-node best-effort. - ratio = relative weight (0 = drain); Manager-side endpoint_id validation. - Authority discovery via appproxy client list_workers(). - Terminology: define publication, model name/alias, authority/node. Paired with the worker-side doc update in lablup/continuum-router#748. Claude-Session: https://claude.ai/code/session_01DRHPAAYiQxQyMRkzic4Dda
…lization Explicitly names the replica-group concept (ReplicaGroupRow) throughout — Terminology, Motivation, and a new "Replica groups and traffic weight today" subsection documenting that traffic_weight is not yet propagated to AppProxy (a known BA-6233 follow-up, not a regression). Adds a "Weight composition and normalization" subsection correcting the flatten-then- multiply ratio × traffic_ratio formula per review feedback on the paired continuum-router design (PR #748), verified still present in the shipped reconcile. Notes the BEP-1005 (Unified AppProxy) compatibility gap and records a Future Directions section exploring router-to-router chaining as a structural fix for both the normalization and propagation gaps. Claude-Session: https://claude.ai/code/session_01ULGKSxQycfWcqbNBBtYiPX
… routing Reorganize the monolithic BEP into a main overview (intent, architecture at a glance, Decision Log, Open Questions) plus four sub-documents (architecture / coordinator / manager / migration) with mermaid diagrams, per the BEP segmentation guide. Design changes from the second review round (2026-07-15): - Adopt hierarchical (chained) routing, superseding the flattened weighted pool shipped router-side; realize it inside the ROUTER worker as per-replica-group instances so continuum-router's replica-level KV machinery (prefix-aware routing, KV index, disaggregated serving) is preserved. Instance substrate (in-memory vs subprocess) recorded as an open question with a recommendation. - Propagate replica-group id/traffic_weight over the existing circuit wire (folds in the BA-6233 follow-up); rollout ramps become ordinary route updates. - Enforce fail-fast on stock/ROUTER inference-worker colocation at registration and document the migration procedure for existing AppProxy deployments. - Specify the Manager-side data model (normalized publications/names/ mappings + model_api_keys tables with CASCADE backstops). - Record coordinator-schema evolvability (BEP-1005 has no work plan), the JSONB mirror rationale, and a deterministic mixed-node_id liveness rule; replace set-operator symbols flagged in review. Claude-Session: https://claude.ai/code/session_01P38DWyutrMv8kuC8XKjyux
4d3f1ef to
910b851
Compare
Refines BEP-1053: ROUTER Frontend Mode across two design-review rounds (2026-06-21, 2026-07-15) and restructures it into the segmented BEP format so a first-time reader can follow intent → key decisions → detailed trade-offs.
Document restructure
The monolithic BEP is now a main overview + four sub-documents (per the BEP segmentation guide), with mermaid diagrams throughout:
FrontendMode.ROUTER, registration validation, per-node liveness, desired-state tables,/v2/routers/*, snapshot + events, failure model, key custodyRound 1 (2026-06-21): resolved the four original open questions
node_id, valkey liveness set, derivednodes) exposed via REST + Prometheus + fleet view.revision+ conditional snapshot polling; first-class aliases gated per key; atomic key rotation;control_modeto prevent a BEP-1049 dual-writer conflict; per-node best-effortrate_limit;ratioas relative weight (0= drain); authority discovery vialist_workers().Round 2 (2026-07-15): hierarchy, fail-fast, and the Manager side
traffic_weightpropagate over the existing circuit wire (replica_groupson the circuit payload,replica_group_idper route) — folds the BA-6233 "weight never reaches AppProxy" follow-up into this proposal; rollout ramps become ordinary route updates.model_publications/model_publication_names/model_publication_endpoints/model_api_keys, CASCADE backstops + service-layer lifecycle hooks — vs the coordinator's deliberately wire-shaped JSONB mirror (rationale recorded).node_idliveness (nodes= liveness-set count + legacy counter) so rolling upgrades of stock workers stay correct; coordinator schema recorded as freely evolvable (BEP-1005 has no work plan).Pairing / cross-repo status
The worker-side counterpart is lablup/continuum-router#748 (
docs/en/architecture/appproxy-worker.md). The shipped router implementation (epic lablup/continuum-router#804) implements the now-superseded flattened selection; its rework to the hierarchical model — and the doc re-sync — is tracked on #804.https://claude.ai/code/session_01DRHPAAYiQxQyMRkzic4Dda
https://claude.ai/code/session_01P38DWyutrMv8kuC8XKjyux