For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt, and this page is available as Markdown at /dd/0011-human-web-ui-and-agent-client-access.md.

Themelio compatibility reference. This page describes the retained Themelio implementation or its historical design. Check the parallel-operation guide before applying it to the current platform.

DD-0011: Chat UI and Client Access — Implementation

Related ADR: ADR-0011: Chat UI and Client Access for Shared LLM Services

Status: Draft

Date: 2026-06-30

Authors: TBD

Implementation branches

BranchKey content
local/llm-access-openwebuiopen-webui-deploy Ansible role; llm-access-root-actor connection/group/ACL management
local/llm-access-waldur-seedLocal Waldur AI offering seed fixtures

All branches are stacked on top of earlier local/* branches and diverge from origin/main by approximately 110 commits total.

Warning

Tested on a local Minikube cluster in CPU mode only. Full SSO login against the production AAI (GRNET MyAccessID) and multi-project isolation at scale require staging/production validation.

Open WebUI deployment

Open WebUI is deployed by the open-webui-deploy Ansible role into the open-webui namespace using the official Helm chart.

AttributeValue
Helm chartopen-webui/open-webui v14.11.0
Imageghcr.io/open-webui/open-webui:0.9.6
Namespaceopen-webui
ServiceNodePort (port 80 / containerPort 8080)
Persistence5 Gi PVC (ReadWriteOnce) for conversation history (SQLite)

Disabled features (set via Helm values):

FeatureSetting
Ollama backendollama.enabled: false
Pipelinespipelines.enabled: false
Tika (file parsing)tika.enabled: false
Terminalsterminals.enabled: false
Direct connectionsENABLE_DIRECT_CONNECTIONS: false
Ollama APIENABLE_OLLAMA_API: false

Open WebUI's default OpenAI connection (openaiBaseApiUrl) is set to LiteLLM's internal Service URL with a disabled key (disabled-until-project-connection). Per-project connections are provisioned separately by llm-access-root-actor (see below).

OIDC / SSO configuration

Helm/env variableValue
sso.enabledtrue when Keycloak is present or shared_oauth2_issuer is set
sso.oidc.providerUrl<issuer>/.well-known/openid-configuration
sso.oidc.providerName"Keycloak"
sso.oidc.scopes"openid email profile"
sso.groupManagement.groupsClaim"project_ids" (local shim only — see note)
ENABLE_OAUTH_SIGNUPtrue
DEFAULT_USER_ROLE"user"
ENABLE_OAUTH_GROUP_CREATIONtrue
Note

The project_ids OIDC groups claim is a local development shim only. Keycloak is configured with a custom mapper that injects project_ids from seeded group memberships to exercise the SSO login flow end-to-end in the local cluster. In staging and production, the external Keycloak / MyAccessID realm does not carry project group data. Group membership is set exclusively by llm-access-root-actor via the membership sync loop (see DD-0020).

Local CA trust (local mode only)

In local mode, an init container merges the local SSL proxy CA into the Open WebUI Python certifi bundle so that OIDC back-channel calls succeed despite the self-signed certificate. This init container runs only when local_mode=true; it is not present in staging/production.

Per-project connection model

Open WebUI stores its OpenAI-compatible backend connections in three parallel arrays in its internal config (retrieved via GET /openai/config and updated via POST /openai/config/update):

OPENAI_API_BASE_URLS:  [url_0, url_1, ...]
OPENAI_API_KEYS:       [key_0, key_1, ...]
OPENAI_API_CONFIGS:    {"0": {config_0}, "1": {config_1}, ...}

One entry is maintained per Waldur project resource by llm-access-root-actor:

FieldValue
base_urlhttp://litellm.litellm.svc.cluster.local:4000/v1
api_keyproject-scoped LiteLLM virtual key (from Kubernetes Secret)
config.enabletrue (active resource) or false (terminated resource)
config.prefix_idresource_backend_id
config.model_idslist of allowed model names (from offering available_models)

When a resource is terminated, llm-access-root-actor sets config.enable: false and clears the api_key field; the entry is not removed (prevents index shift in the arrays).

What the prefix_id controls

Open WebUI uses prefix_id + model_ids to generate per-connection model entries. The resulting model IDs visible to users are <prefix_id>/<model_id> internally, but the root actor explicitly creates separate model records named <resource_backend_id>.<model_id> with group-scoped access grants.

Model naming and ACL

Models in Open WebUI are created by the root actor with the naming convention:

<resource_backend_id>.<model_name>

For example, project buffermusic with model qwen2.5-0.5b → model ID buffermusic.qwen2.5-0.5b.

Each model has access grants set via PUT /api/v1/models/model:

{
  "access_grants": [
    {
      "principal_type": "group",
      "principal_id": "<group_id>",
      "permission": "read"
    }
  ]
}

Where group_id is the Open WebUI group corresponding to the project (see below). Users who are not members of the group cannot see or use the model.

When a resource is terminated or its model allowlist changes:

  • Removed models have their access_grants cleared (no group restriction → effectively hidden from all non-admin users; the root actor does not delete model records).
  • New models in the allowlist get the project group ACL applied.

Group management

One Open WebUI group per Waldur project resource:

FieldValue
Group nameresource_backend_id (e.g., buffermusic)
Group descriptionWaldur project name (e.g., "Buffer Music Project")

The group is created idempotently via POST /api/v1/groups/create if it does not exist; otherwise POST /api/v1/groups/id/<id>/update updates the description if it changed.

Group membership is managed separately by the membership_sync agent (see DD-0020). The group creation and model ACL wiring happen during project_create / project_update; user additions/removals happen on every membership sync cycle (every 5 minutes).

Request routing (UI path)

When a human user chats in Open WebUI:

Browser
  → Open WebUI backend (session auth)
      → select connection where config.prefix_id matches the model's prefix
          → POST /v1/chat/completions to LiteLLM
              api_key = per-project virtual key
          → LiteLLM: check team budget, route to vLLM alias Service
          → LiteLLM: record spend in PostgreSQL

Open WebUI does not have direct access to the inference namespace or vLLM endpoints. All routing goes through LiteLLM. Budget enforcement is identical to the API path.

API client access

API clients (OpenAI SDK, Claude Code, Codex CLI) authenticate to LiteLLM directly using the per-project virtual key. The key is distributed via the Waldur offering:

  • Virtual keys do not expire automatically — there is no TTL. A key remains valid until explicitly rotated.
  • The rotate_project_key endpoint on the root actor (service.py: rotate_project_key) generates a brand-new LiteLLM virtual key via POST /key/generate, persists it to the project's Kubernetes Secret (bumping a rotation_version counter), updates the Open WebUI connection to use the new key, and then deletes the previous key from LiteLLM. Rotation is therefore immediate and destructive to the old key: any client still configured with the previous key starts failing right after rotation completes.
  • The Waldur offering exposes a "view API key" action that triggers this endpoint. Rotation is on-demand only — there is no automatic/periodic rotation schedule.
  • API clients configure their base URL to the LiteLLM external endpoint and use the key.

For Codex CLI clients requiring POST /v1/responses: a separate responses-proxy service (from local/codex-responses-api branch) translates /v1/responses to Chat Completions. This is outside the LiteLLM gateway and the Open WebUI stack.

Page source

Source snapshot · Revision f928834c8.