Source of truth: kei-openwebui docs/setup.md. This page is a copy; changes land there first. The kei-openwebui repository is private today, so its links open only for accounts Haikei has given access.
This guide takes you from nothing to an Open WebUI where every model call and every governed tool call is decided and audited by Kei. It uses the published openwebui-kei image. For how the connector works inside, see design.md. For how Haikei cuts releases, see release.md.
The examples use a neutral customer, Acme, with a tool pack acme_orders. Replace the names, IDs and registry with your own.
Kei (control plane) your platform
┌─────────────────────────────┐ ┌──────────────── openwebui-kei container ───────────────┐
│ runtime installation │◄─ token ──┤ kei-proxy: bootstrap, serve, heartbeat │
│ platform openwebui │ │ boot: tool sync (PUT /api/v1/runtime/tools), seeding │
│ agent + model profile │── bundle ►│ Open WebUI + Kei Pipe + kei_guard + your tool pack │
│ policies, users, groups │◄─ audit ──┤ │
└─────────────────────────────┘ └─────────────────────────────────────────────────────────┘Before you start#
- A Kei organization and workspace, and an owner or admin who can run
kei login. - The
keiCLI.kei directory-syncneeds a release newer than kei-cli v0.1.29. - A secret manager your container platform can read (Azure Key Vault, AWS Secrets Manager, GCP Secret Manager, a Kubernetes Secret).
- A container platform that runs
linux/amd64images and lets you set the container user. - An OpenAI-compatible model endpoint for the model profile.
1. Kei side#
Everything in this section is admin work with the kei CLI (or the Kei console). Run kei login first. Each command below is in kei --help. Use the CLI’s --help usage, not this page, if the two ever disagree.
1.1 The agent and the installation#
The installation is the runtime’s identity in Kei. Its platform must be openwebui: that is what makes Open WebUI users resolve as (openwebui, <Open WebUI user id>) identities.
- Create the chat agent in the Kei console (the CLI has no agent create command) and note its ID.
- Create the installation, bound to that agent:
kei bot init --platform openwebui --name acme-chat --agent <agent-id> --workspace <workspace>
kei bot list # note the installation ID
kei bot agents list --installation <installation-id>1.2 The runtime credential#
The runtime credential is KEI_RUNTIME_TOKEN. Kei shows it once. kei bot credential refuses to write it to a terminal, so pipe it straight into your secret manager:
kei bot credential --installation <installation-id> --workspace <workspace> \
| <your secret-manager import command>Never print it, paste it, or put it in a file in the repository. To rotate it later, use kei bot credential --rotate the same way, then restart the container: the old token stops working immediately.
You also need KEI_RUNTIME_CONTROL_PLANE_URL, the Kei control plane’s base URL (no /api/v1 suffix). The console shows it next to the credential.
1.3 The model profile#
The Kei Pipe is Open WebUI’s only model provider. It lists the models the agent’s model profile gives it, so with no profile Open WebUI shows no models.
kei model-profiles create --workspace <workspace> --display-name acme-chat-model \
--endpoint https://models.acme.example/v1 --auth-type none \
--default-model <model-id> --agent <agent-id>
kei model-profiles assignment --workspace <workspace> --agent <agent-id>
kei model-profiles readiness acme-chat-model --workspace <workspace>--endpointmust be anhttps://URL with no credentials, query or fragment.--auth-type api_keyreads the key from stdin and seals it to a runtime that can receive it. The CLI refuses (no runtime in this scope can receive the API key; set up a runtime with a credential-sync key first) when there is none. Credential sync is covered by thekei-runtime-setupskill in HaikeiLabs/skills.- A profile that already exists is assigned with
kei model-profiles assign <profile> --workspace <workspace> --agent <agent-id>.
1.4 Policies#
Two kinds of permit make a working chat. Both are evaluated by the kei-proxy daemon in the container, against the user the agent session resolved to.
| Governs | --dst-pattern |
Example --src-pattern |
|---|---|---|
| Model calls through the Kei Pipe | model:invoke |
group:chat-users |
| One governed tool | tool:<name>, the tool’s declared name exactly |
group:chat-users |
kei policies create --workspace <workspace> --name chat-users-models \
--src-pattern group:chat-users --dst-pattern model:invoke --effect permit --agent-id <agent-id>
kei policies create --workspace <workspace> --name chat-users-list-orders \
--src-pattern group:chat-users --dst-pattern tool:acme.orders.list_orders --effect permit \
--agent-id <agent-id>
kei policies list --workspace <workspace>--src-patternnames who:user:<id>,group:<name>, or*.- The
kei-proxyin current images evaluates v1 policy bundles, wheremodel:invokepermits decide model calls. In v2 bundles the agent’s model profile assignment decides them instead (HAI-459), so keep the assignment from step 1.3 either way. - Bind the permits to the chat agent with
--agent-id. kei-cli v0.1.28 and later accept it; on older versions set the agent on the policy in the console. - Kei policy alone decides. There is no confirmation step: a write tool runs as soon as a permit allows it.
- A policy only applies to a tool that is registered for the installation. The image registers its pack’s tools at boot (section 3). Until then a call is refused
tool_not_registeredwhatever the policies say. - Read/write isn’t part of the request, so a policy that separates reads from writes names the read and write tools themselves.
1.5 Linking users#
Each Open WebUI user is identified by Open WebUI’s own user ID, whatever SSO you configured. Kei must link that ID to an email-keyed Kei user before the user’s calls are allowed. Until then the user is unlinked and every governed call is refused.
- Claim link (any SSO). The first time an unlinked user triggers a governed tool, the connector shows them a one-time claim link as an Open WebUI notification, to that user only. They open it, sign in to Kei, and ask again. The link is never in the chat, the model’s context, or a log.
- Entra directory sync. With Microsoft Entra, Kei can create your users and groups from the Entra app’s role assignments, one Kei group per app role value:
kei directory-sync set --tenant-id <tenant-guid> --client-id <sync-app-guid> --app-id <app-guid> \
--dry-run=true --create-users=true
kei directory-sync enable
kei directory-sync report # read the dry-run report before going live
kei directory-sync set --dry-run=falseSynced users are Kei users and group members, so group:<role value> permits apply to them. Each user still links their Open WebUI identity with the claim link once. The Azure side (app roles, the sync app’s Graph permissions, the federated credential) is in the kei-directory-sync skill in HaikeiLabs/skills.
Check a user’s links with kei users resolve --email <email> and kei users remote-identities list <user>.
2. The image#
2.1 Pull or mirror by digest#
openwebui-kei is on ECR Public and needs no credentials: public.ecr.aws/o1a1k4g0/openwebui-kei. Each GitHub release of HaikeiLabs/kei-openwebui has an IMAGE_DIGEST.txt. Deploy its image= line, the digest, never the tag. The repository is private today: if you can’t open its releases, ask Haikei for the release’s digest.
docker pull public.ecr.aws/o1a1k4g0/openwebui-kei@sha256:<digest>To mirror into your own registry, copy by digest so the digest doesn’t change. Azure Container Registry:
az acr import --name <your-acr> \
--source public.ecr.aws/o1a1k4g0/openwebui-kei@sha256:<digest> \
--image openwebui-kei:<tag>Then deploy <your-acr>.azurecr.io/openwebui-kei@sha256:<digest>. Other registries: any tool that copies a manifest by digest (for example crane copy or skopeo copy) works the same way.
Plain-data tool declarations (below) need v0.1.2 or later. The boot tool sync needs v0.1.1 or later.
2.2 Run as UID/GID 10001#
The image runs as user kei, UID 10001, GID 10001. Set your platform’s container user, and any runAsUser, runAsGroup, fsGroup, and the owner of mounted volumes, to 10001. A template that defaults to 1000 makes the supervisor refuse to start with KEI_RUNTIME_STATE_DIR (...) is not a writable directory; refusing to start.
2.3 Environment#
| Variable | Required | What it is |
|---|---|---|
KEI_RUNTIME_TOKEN |
yes | The runtime credential, from your secret manager |
KEI_RUNTIME_CONTROL_PLANE_URL |
yes | The Kei control plane base URL |
WEBUI_SECRET_KEY |
yes | Open WebUI’s secret key, from your secret manager. Without it the supervisor refuses to start. Replicas must share it |
KEI_OPENWEBUI_TOOL_PACKS |
with a pack | Comma-separated pack modules, e.g. acme_orders. Usually set in your layer’s Dockerfile |
KEI_PROXY_EGRESS_PROXY |
no | An HTTP proxy for kei-proxy’s calls to Kei, e.g. http://localhost:1055. Given to kei-proxy and the boot tool sync only, never to Open WebUI |
KEI_PROXY_NO_PROXY |
no | Hosts that bypass that proxy, e.g. 169.254.169.254, plus the Kei control plane host if it is public |
KEI_PROXY_AUDIT |
recommended | kei-proxy’s audit spool path. Put it on a mounted volume, so a killed container doesn’t lose unshipped records |
KEI_PROXY_HEARTBEAT_INTERVAL |
no | Heartbeat interval (default 1m) |
KEI_PROXY_READY_TIMEOUT |
no | Seconds to wait for kei-proxy’s /readyz (default 60) |
The image already sets ENABLE_OPENAI_API=false, ENABLE_OLLAMA_API=false and ENABLE_DIRECT_CONNECTIONS=false, so the Kei Pipe is the only model provider. Don’t override them. Open WebUI’s own settings (database, SSO, WEBUI_URL) are Open WebUI’s; see its documentation.
On a fresh database there is no Open WebUI admin yet. The container starts, waits for the first admin to sign up, then installs the Kei functions. Until then nobody can chat, because there is no model provider.
3. Tool packs#
Your own tools ship as a Python package, a tool pack, layered on the image at build time. Nothing installs at runtime.
FROM public.ecr.aws/o1a1k4g0/openwebui-kei@sha256:<digest>
USER root
RUN pip install --no-cache-dir acme-orders==<version>
USER kei
ENV KEI_OPENWEBUI_TOOL_PACKS=acme_ordersBuild it for linux/amd64 and deploy your layer by its own digest.
3.1 Declare the tools as plain data#
The pack’s top-level module declares TOOLKITS. Each toolkit’s tools list declares the governed tools as plain data. The pack never imports kei_openwebui.
# acme_orders/__init__.py
TOOLKITS = [
{
"id": "acme_orders",
"name": "Acme: orders",
"module": "acme_orders.tools",
"description": "Lists and cancels Acme orders.",
"source": "acme",
"tools": [
{"name": "acme.orders.list_orders", "access": "read", "required_capabilities": ["orders.read"]},
{"name": "acme.orders.cancel_order", "access": "write", "required_capabilities": ["orders.write"]},
],
},
]nameandaccess(readorwrite) are required. The field rules are in release.md.- The name is the action. Kei is asked about
tool:<name>, and your policy’s--dst-patternmust use the same name. TheToolsmethod must pass that same name toGovernor.run. Name each tool<method>or<prefix>.<method>after its method (acme.orders.list_orders→list_orders). From v0.1.3, a declaration whose method has a different name says so inmethod({"name": "acme.orders.get_order", "access": "read", "method": "orders_get"}). - A malformed declaration, or a name declared twice, stops the container and names the pack and entry. A pack that doesn’t import stops it too.
3.2 The boot tool sync#
Kei’s policy bundle only lists tools registered for the installation. At every boot, once kei-proxy is ready and before Open WebUI serves, the image sends the packs’ manifest to PUT $KEI_RUNTIME_CONTROL_PLANE_URL/api/v1/runtime/tools with the runtime token. You configure nothing else.
- The PUT replaces the installation’s registered tools, so a restart converges Kei on the current packs and a dropped tool is unregistered.
- No packs configured: the sync is skipped (
tool sync skipped: no tool packs configured). - Packs configured but no tools exported: the container refuses to start rather than send an empty list, which would unregister every tool (
tool sync refused: ... its manifest has no tools). - Kei refuses, or the control plane is unreachable: the sync retries 5 times over about 15 s, then the container exits with
registering the tool packs' tools with Kei failed; refusing to start Open WebUI.
It fails closed: Open WebUI never serves tools Kei can’t govern. The tools reach kei-proxy on its next bundle poll, within about a minute.
4. Verify#
Get a shell in the running container (docker exec -it <container> bash, kubectl exec -it <pod> -- bash, or az containerapp exec ... --command bash). Never print KEI_RUNTIME_TOKEN; the commands below use it in place.
- The boot log shows the sync:
kei-seed: tool sync: registered 2 tool(s) with Kei at /api/v1/runtime/tools (added=2, removed=0, unchanged=0, updated=0)followed by kei-seed: Kei functions seeded (...).
- The export lists your tools under the names you’ll write policies for:
python -m kei_openwebui.boot --export-manifest- The bundle holds them, about a minute after boot:
"$KEI_PROXY_PATH" policy showtool_count includes your tools. state, policy_revision and last_success show the bundle is current.
- The models the agent’s profile gives the Pipe:
printf 'header = "Authorization: Bearer %s"\n' "$KEI_RUNTIME_TOKEN" | \
curl --config - -sS --unix-socket "$KEI_RUNTIME_SOCKET_PATH" http://localhost/v1/models; echo-
One allowed call. Sign in to Open WebUI as a linked user in
group:chat-users, pick a Kei model, enable the Acme toolkit, and ask something that lists orders. The answer has data. -
One denied call. Ask for a tool with no permit, for example cancelling an order. The model answers with a refusal:
Refused (kei.denied): .... The tool body never runs. -
The audit. The Kei console’s audit view shows both decisions under the user’s Kei user, with the tool and
allowordeny. Before the collector ships them, they’re in the spool at$KEI_PROXY_AUDIT.
kei harness sync --harness openwebui --url <open-webui-url> (admin token in OPENWEBUI_ADMIN_TOKEN) checks from outside that the Kei Pipe and kei_guard are installed and active. It is read-only and exits 1 on drift.
5. Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
A tool call is refused tool is not registered in the policy bundle (tool_not_registered) |
Kei hasn’t got the tool: an image older than v0.1.1, no KEI_OPENWEBUI_TOOL_PACKS, a name that differs from what the method sends, or the bundle hasn’t refreshed yet |
Check the boot log for tool sync: registered N tool(s), compare --export-manifest names with the refused name, wait one bundle poll and check policy show. A permit doesn’t help until the tool is registered. See release.md |
The container won’t start: tool sync refused: KEI_OPENWEBUI_TOOL_PACKS=... is set but its manifest has no tools |
The pack declares no governed tools (no tools list), or the image is older than v0.1.2 and doesn’t read plain-data tools |
Add a tools list to each toolkit, and use v0.1.2 or later |
The container won’t start: tool sync with Kei failed: PUT /api/v1/runtime/tools failed after 5 attempt(s): HTTP 401 ... |
The runtime token is wrong, or was rotated | Put the current credential in the secret manager and restart |
The same, with HTTP 404 |
KEI_RUNTIME_CONTROL_PLANE_URL is wrong (for example it ends in /api/v1), or the control plane doesn’t serve the tool sync route |
Use the base URL with no path. If it is right, ask Haikei |
The same, with HTTP 409 |
A tool name is already registered in the workspace by an admin or another installation | Rename the tool, or remove the other registration in the console |
| The same, with a network error | Egress | Set KEI_PROXY_EGRESS_PROXY, or add the control plane host to KEI_PROXY_NO_PROXY if it is public |
The container won’t start: KEI_RUNTIME_STATE_DIR (...) is not a writable directory |
The container runs as a UID other than 10001 | Run as 10001:10001 and give mounted volumes that owner |
The container won’t start: WEBUI_SECRET_KEY is not set |
No Open WebUI secret key | Set WEBUI_SECRET_KEY from the secret manager |
The container won’t start: kei-proxy runtime bootstrap failed |
The token or control plane URL is wrong, the installation was deleted, or egress is blocked | Check the token and URL; kei bot status --installation <id> |
A user is told their account isn’t linked (Refused (identity.unlinked)) |
Their Open WebUI user isn’t linked to a Kei user | They open the claim link Open WebUI showed them and sign in to Kei. With no link, an admin checks kei users remote-identities list <user> |
| The audit shows a user’s calls as the installation’s owner, marked via installation, and they’re denied | The call had no session user: the Open WebUI user is unlinked, so the row falls back to the installation subject (who set up the runtime) | Link the user (claim link). Their next calls show under their own Kei user |
| A call is denied although a permit exists | The user isn’t in the permit’s group, the permit isn’t bound to the chat agent, or the bundle hasn’t refreshed | Check the user’s groups and the permit’s agent; wait one poll and check policy_revision with policy show |
| No models in Open WebUI’s model list | kei-proxy isn’t ready, the agent has no model profile, or nobody holds a model:invoke permit |
kei model-profiles assignment --workspace <ws> --agent <id>; add a model:invoke permit; check /v1/models (Verify, step 4) |
A chat turn fails with Kei governance is unavailable, so this request was not sent |
The kei_guard filter found kei-proxy not ready |
Check the container log for kei-proxy errors; the container exits if kei-proxy dies |
The boot log warns tool sync: <toolkit>.<method> is left out of the manifest: ... |
No declaration is matched to that Tools method. Either it really is ungoverned, or its tool name doesn’t follow <prefix>.<method> and names no method |
From v0.1.3, set the declaration’s method to the method that runs it. Before v0.1.3, the warning is diagnostic only if the registered count is right and each method sends its declared name; otherwise declare the tool or rename it after its method |
Related#
- release.md: releases,
IMAGE_DIGEST.txt, the tool-pack contract, registering tools by hand on v0.1.0. - design.md: the gate, agent sessions, the supervisor, known gaps.
- e2e.md: the end-to-end harness against a local control plane.
- HaikeiLabs/skills:
kei-openwebui,kei-cli,kei-runtime-setup,kei-credential-rotation,kei-directory-sync.