--- title: "ADR-0020: LLM Access Membership Source of Truth" --- # ADR-0020: LLM Access Membership Source of Truth **Status:** Draft **Date:** 2026-06-30 **Deciders:** Themelio developers (confirmed in session, 2026-06-30) **Relates to:** - [ADR-0005 (AAI → Waldur User Synchronization)](/adr/0005-aai-waldur-user-sync) - [ADR-0010 (Project-Scoped LLM Gateway, Token Quotas, and API Access)](/adr/0010-project-scoped-llm-gateway-token-quotas-api-access) - [ADR-0011 (Human Web UI and Agent Client Access)](/adr/0011-human-web-ui-and-agent-client-access) ## Context The LLM access platform needs one authoritative system for user-to-project membership so that: - Open WebUI group membership stays in sync with who has been allocated access, - LiteLLM project routing, keys, and budget limits stay aligned with the same Waldur project allocation, - membership addition and revocation can be exercised end to end through a single control plane. Two candidate sources of truth were considered during design: Keycloak and Waldur. ### Current State The implementation state as of the decision date: - `llm-access-root-actor` is a privileged Python service that reconciles LiteLLM teams, budgets, model allowlists, project API keys, and Open WebUI groups and connections. - The root actor exposes `/waldur/add_project_members`, `/waldur/remove_project_members`, and `/waldur/list_project_members` endpoints that manage Open WebUI group membership directly. - `waldur-llm-access-site-agent` implements `add_users_to_resource`, `remove_users_from_resource`, and `list_resource_users` via those root actor endpoints. - A `membership_sync` deployment of the site agent is provisioned alongside `order_process` and `report`. - A live probe against `staging.aai.pharos.grnet.gr` (the production-like external Keycloak / Pharos AAI stack) confirmed that the external realm's group list for production users is empty — it is a brokered federation identity facade, not a local-group authorization store. - A read-only probe against the production-shape Waldur instance (`dev.waldur.pharos.grnet.gr`) confirmed that projects such as Buffermusic, DyaniaHealth, and OpenCosmos store human membership in Waldur project membership, while the project resource is keyed by `backend_id == project_slug`. ### Current Problems Before the decision was finalized, the Proposed ADR text favored Keycloak-authoritative membership. The problems with that model are: - The external production Keycloak realm is a brokered federation IdP (MyAccessID → NTUA Shibboleth → Keycloak `id` realm). Themelio does not control that shared realm and cannot write per-project groups to it. - Even if a locally controlled Keycloak realm is introduced, managing group membership in Keycloak in parallel with Waldur creates two editable sources of the same authorization decision with no reconciliation bridge. - Waldur already drives the full resource lifecycle (create, update, delete, limits, usage). Forking membership into a separate system produces a split control plane for the same resource. - The existing `waldur-k8s-site-agent` already resolves membership through site agent callbacks to a privileged root actor. The pattern is proven in the same codebase. ## Decision Drivers - Single control plane for all Waldur-allocated LLM-access resources, including who can use them. - Consistency with the existing `waldur-k8s-site-agent` pattern for membership reconciliation. - Avoidance of dependency on a shared external Keycloak realm that Themelio does not control. - Ability to prove revocation end to end in the existing local test stack without a separate Keycloak admin path. ## Considered Options ### Option A: Waldur authoritative (adopted) Waldur is the single source of truth for who has access to which LLM-access project. The `membership_sync` backend in `waldur-llm-access-site-agent` drives periodic reconciliation. Membership changes are written to Open WebUI groups via the `llm-access-root-actor`. No Keycloak membership writes are performed. **Pros:** - Single control plane for the full resource lifecycle including membership. - Consistent with the `waldur-k8s-site-agent` pattern already in the repo. - No dependency on writing to the external Keycloak / AAI realm, which we do not control. - Membership revocation is testable immediately in the local stack. **Cons:** - Open WebUI does not derive membership from OIDC token claims (`project_ids`); it derives it from group membership managed by the root actor. The local Keycloak group-to-claim path is no longer the production authorization channel. - Users not yet registered in Open WebUI (i.e., have not yet logged in) are returned as `not_found` by the root actor and retried on the next `membership_sync` cycle. ### Option B: Keycloak authoritative (rejected) Keycloak project groups drive `project_ids` claims, which Open WebUI synchronizes on login. Waldur would have remained authoritative only for resources and budgets, not membership. This option was rejected because the production external Keycloak realm is a brokered federation IdP that Themelio does not control. Writing per-project groups there is not a viable production path. ## Decision We will use **Waldur as the authoritative system for user-to-project membership** for LLM access (Option A). Specifically: - The `waldur-llm-access-site-agent` `membership_sync` deployment reconciles Waldur project membership into Open WebUI groups via the `llm-access-root-actor`. - The root actor's `/waldur/add_project_members`, `/waldur/remove_project_members`, and `/waldur/list_project_members` endpoints own the Open WebUI group member list. - The external Keycloak / AAI layer remains the **authentication** authority only. It is not written to for per-project authorization state. - The local Keycloak `project_ids` claim path remains in the local dev stack for testing the OIDC login flow, but it is not the production authorization channel and must not be treated as evidence that Waldur membership sync is working. **See also:** [DD-0020: Implementation details](/dd/0020-llm-access-membership-source-of-truth) ## Consequences **Positive:** - Membership add, update, and revocation are driven entirely from Waldur, closing the authorization lifecycle loop within the existing operational control plane. - The existing site-agent pattern (`add_users_to_resource` / `remove_users_from_resource`) works without requiring a Keycloak admin client or a shared external realm. - Local stack smoke tests can cover end-to-end membership flows in the same environment as other lifecycle operations. **Negative:** - Users must have logged in to Open WebUI at least once before the root actor can resolve their email to an Open WebUI user ID. First-login provisioning is outside the membership sync path; those users are retried on the next sync cycle. - Open WebUI group membership is now managed externally (root actor), not via Open WebUI's own admin UI. Administrators should not edit group membership directly in Open WebUI. - The `project_ids` OIDC claim path (used in the local Keycloak dev stack) does not carry membership to Open WebUI in production; only the Waldur-driven group assignment does. **Neutral:** - External AAI / Pharos Keycloak remains the authentication authority and continues to issue user identity tokens. Nothing changes on the authn side. - The local Keycloak group-to-claim path is kept for local dev smoke testing and can be removed later if it adds maintenance cost without value. - Local validation of this ADR must seed users, projects, and resources in Waldur. Keycloak-only fixtures are insufficient to prove the real membership flow. ### Migration No migration of existing membership state is required. The membership sync deployment will reconcile the current Waldur project membership into Open WebUI on its first run. Users who are already in the correct Open WebUI groups (from the local bootstrap or manual setup) will not be re-added unnecessarily. ## Failure Behavior 1. If a user's Waldur project membership is removed, the next `membership_sync` cycle removes them from the corresponding Open WebUI group. Access to project-qualified models through Open WebUI stops at the next session check. 2. Users not yet registered in Open WebUI (no login yet) are returned as `not_found` by the root actor and remain in the `not_found` list until they first log in and the next sync cycle runs. 3. LiteLLM enforcement remains independent: the project-restricted API key is revoked or blocked at the LiteLLM level even if Open WebUI state is temporarily stale. 4. If the root actor is unavailable during a membership sync, the site agent logs the error and retries on the next cycle; no membership state is corrupted. ## Tests The required proof for this ADR: 1. A user added to a Waldur project appears in the Open WebUI group after the next `membership_sync` cycle. 2. A user removed from a Waldur project is removed from the Open WebUI group after the next `membership_sync` cycle. 3. A user not yet registered in Open WebUI is listed as `not_found` and is retried on the following cycle once they log in. 4. LiteLLM still rejects cross-project API key use independently of UI membership state. ## References - External Keycloak probe findings (session 2026-06-30): brokered federation realm, empty groups list for production users, `project_ids` claim absent from default `userinfo` response — confirming that the external realm is authn-only. - Production-shape Waldur probe findings (session 2026-06-30): Buffermusic, DyaniaHealth, and OpenCosmos expose project membership and resource linkage in Waldur itself, with project resources keyed by `backend_id == project_slug`. - `services/llm-access-root-actor/llm_access_root_actor/service.py` — `add_project_members`, `remove_project_members`, `list_project_members` implementations. - `services/waldur-llm-access-site-agent/waldur_site_agent_llm_access/backend.py` — `add_users_to_resource`, `remove_users_from_resource`. - `gitops/manifests/llm-access/base/waldur-llm-access-site-agent/deployment-membership-sync.yaml`