Skip to content

Helm Values Reference

This page documents the configuration values for the Omnia Helm chart. See Install with Helm for installation steps.

replicaCount: 3 # 3+ recommended for HA with leader election
image:
repository: ghcr.io/altairalabs/omnia-operator
pullPolicy: IfNotPresent
tag: "" # Defaults to Chart appVersion
imagePullSecrets: [] # this chart's own pods only; see global.imagePullSecrets
nameOverride: ""
fullnameOverride: ""
global:
imageRegistry: "" # e.g. registry.internal.example.com
imagePullSecrets: [] # e.g. [{name: mirror-pull}] or [mirror-pull]
ValueDefaultDescription
global.imageRegistry""Registry every Omnia image is pulled from. The registry host of each reference is replaced and the path kept: ghcr.io/altairalabs/omnia-operator:<tag> becomes <imageRegistry>/altairalabs/omnia-operator:<tag>. A reference with no registry host (a Docker Hub name such as busybox:1.36) is prefixed whole. A reference already under the registry is unchanged. A value with a path, such as registry.example.com/omnia, prefixes that path. No scheme.
global.imagePullSecrets[]Secret names, as {name: <secret>} or a bare string. Added first to every pod the chart deploys, ahead of imagePullSecrets and each component’s podOverrides.imagePullSecrets, each name once.

global.imageRegistry applies to every image the chart renders: the operator, dashboard, management-api, doctor, Arena controller, PromptKit LSP, compaction CronJob and the helm test pods. It also applies to every image the chart passes the operator and the Arena controller as a flag, which are the images of the pods they create:

Operator flagPods
--facade-imageAgent facade container
--framework-imageAgent runtime container, one per framework type
--session-api-image, --memory-api-image, --privacy-api-imagePer-workspace services
--eval-worker-imageEval workers
--policy-broker-imagePolicy broker sidecar (Enterprise)
--egress-proxy-imageSandbox egress proxy, and the init containers that deliver a browser session or sign-in grant to a sandbox
--arena-worker-image (Arena controller)Arena workers (Enterprise)

The sandbox classes the operator installs (sandbox.image, sandbox.promptarenaClass.image, sandbox.browserClass.image) follow it too. An image set on a custom resource (spec.framework.image, spec.facades[].image, or the image of a SandboxClass you create) is used as written.

The chart passes global.imagePullSecrets to the operator and the Arena controller as --image-pull-secrets. They add the names first to every pod they create: agents, per-workspace services, eval workers, sandboxes, egress proxies and Arena workers. A Secret must exist in the namespace of the pod that uses it, so create it in every workspace namespace as well as the release namespace.

Helm passes global to every subchart. Each observability subchart reads its own registry setting:

SubchartRegistry setting
grafanaglobal.imageRegistry, global.imagePullSecrets
lokiglobal.imageRegistry; pull secrets at loki.imagePullSecrets
tempoglobal.imageRegistry; pull secrets at tempo.tempo.pullSecrets
alloyglobal.image.registry, global.image.pullSecrets
kedaglobal.image.registry; pull secrets at keda.imagePullSecrets
prometheusnone: set each prometheus.*.image.repository to the full mirrored path; pull secrets at prometheus.imagePullSecrets
nfscsi (csi-driver-nfs)nfscsi.image.baseRepo and nfscsi.image.nfs.repository; pull secrets at nfscsi.imagePullSecrets

Render the chart with your values and check every image: line before you install a subchart against a mirror:

Terminal window
helm template omnia oci://ghcr.io/altairalabs/charts/omnia --devel \
-f values.yaml | grep 'image:' | sort -u

Install in an air-gapped cluster builds a bundle of every image and pushes it to these paths.

serviceAccount:
create: true
annotations: {}
name: "" # Defaults to the release fullname when create is true
podAnnotations: {}
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
openshift:
enabled: false
ValueDefaultDescription
openshift.enabledfalseRuns every Omnia pod under OpenShift’s restricted-v2 SCC. No chart pod sets runAsUser, runAsGroup or fsGroup, including any set in podSecurityContext or dashboard.podSecurityContext. The operator and arena controller get --omit-pod-ids, so the agent, sandbox, egress-proxy, enrolment and arena worker pods they build leave them unset too. The chart refuses to render with nfs.csiDriver.enabled or dev.enabled. When false, every pod runs as 65532 (the dashboard as 101).

See Install on OpenShift.

resources:
limits:
cpu: "1"
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
nodeSelector: {}
tolerations: []
affinity: {}
leaderElection:
enabled: true
probes:
port: 8081
liveness:
initialDelaySeconds: 15
periodSeconds: 20
readiness:
initialDelaySeconds: 5
periodSeconds: 10
metrics:
enabled: true
port: 8443
secure: true
webhook:
enabled: false
port: 9443
rbac:
create: true
crdPreflight:
enabled: true

crdPreflight checks at render time that the omnia-crds release is installed and new enough — it does not install CRDs itself. CRDs install from the separate omnia-crds chart; see Install with Helm.

operator:
promptPackSchema:
source: embedded
ValueBehavior
embedded (default)The operator validates every pack against the schema built into its binary and makes no outbound call.
networkThe operator fetches the version a pack’s $schema names from promptpack.org, and uses the built-in schema if the fetch fails. A pack without $schema still uses the built-in schema.
airGapped: false
ValueBehavior
false (default)Each outbound connection follows its own value.
trueTurns off install telemetry, licence activation and heartbeat, and the GitHub community templates source, and runs the operator with --air-gapped. The render fails if operator.promptPackSchema.source is network, or if controlPlane.url, awsMarketplace.productSku, gcpMarketplace.reportingSecret, grafana.plugins with grafana.enabled, or dashboard.avatars.gravatar is set.

Image references do not change. See Outbound connections for every destination, its default and the value that turns it off.

controlPlane:
url: "" # Empty disables the integration
grantSecretName: omnia-installation-grant
displayName: ""
environment: production

The integration is off unless url is set. grantSecretName names the Secret, in the release namespace, that holds the bootstrap grant. The chart never creates it. displayName is the name this installation appears under in the control-plane console, and environment is your own label for it.

Once registered, the operator installs the licence the control plane issues into the arena-license Secret, the same place an uploaded licence goes. It verifies each document against the embedded public key first, and never replaces the installed licence with one issued earlier, so an outage or a bad document leaves the last good licence in force. license.key cannot be set together with controlPlane.url: every upgrade would write the key back over the licence the control plane issued.

facade:
image:
repository: ghcr.io/altairalabs/omnia-facade
tag: "" # Defaults to Chart appVersion

The facade provides media storage for file uploads, configured through spec.facades[].extraEnv on the AgentRuntime:

- name: OMNIA_MEDIA_STORAGE_TYPE
value: local
- name: OMNIA_MEDIA_STORAGE_PATH
value: /var/lib/omnia/media

OMNIA_MEDIA_STORAGE_TYPE accepts none, local, s3, gcs, or azure. See Configure Media Storage for the S3, GCS, and Azure backends.

The framework image is used for the agent runtime container. This naming aligns with the CRD’s spec.framework field.

framework:
image:
repository: ghcr.io/altairalabs/omnia-runtime
tag: "" # Defaults to Chart appVersion
enterprise:
enabled: false # Enable enterprise features (Arena Fleet, etc.)

enterprise.enabled turns on the Arena controllers, workspace shared-filesystem features, and the Project Editor’s LSP validation (gated separately by promptkitLsp.enabled). It does not install the Arena CRDs — those come from the separate omnia-crds chart, gated by that chart’s own enterprise.enabled value (default true):

The eval worker runs realtime evals for non-PromptKit agents (Pattern A). It is an enterprise feature.

workspaceServices:
evalWorker:
image:
repository: ghcr.io/altairalabs/omnia-eval-worker
tag: "" # Defaults to Chart appVersion
pullPolicy: IfNotPresent

Whether an eval-worker Deployment exists is derived per Workspace service group (the spec.services[].evalWorker.enabled field on the Workspace CRD), not a chart toggle. Replica count is hardcoded to 1, and the container has no configurable resource limits or requests.

The eval worker is namespace-scoped: one Deployment per namespace per service group, with a namespaced Role/RoleBinding. A ClusterRoleBinding to a separately provisioned workspace-reader ClusterRole is added only to read cluster-scoped Workspace CRDs, gated on rbac.create (default true).

Arena Fleet provides distributed testing for PromptKit bundles. This is an enterprise feature.

enterprise:
enabled: true # Required for Arena Fleet
arena:
worker:
image:
repository: ghcr.io/altairalabs/omnia-arena-worker
tag: "" # Defaults to Chart appVersion
pullPolicy: IfNotPresent
serviceAccountName: "" # Empty = a per-job arena-worker SA with no cloud identity
podLabels: {} # e.g. {azure.workload.identity/use: "true"}
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 100m
memory: 128Mi

Set serviceAccountName to the workspace runtime ServiceAccount when evaluations call a provider with auth.type: workloadIdentity. The worker then inherits that ServiceAccount’s cloud identity. Left empty, the controller creates a per-job ServiceAccount with no cloud identity, and keyless providers fail. podLabels adds labels to the worker pod template, for example to opt into a cloud-identity webhook.

Source polling interval and timeout are ArenaProject CRD fields, not chart values: spec.interval (required, no default) and spec.timeout (default 60s). See ArenaProject.

See the Arena Fleet documentation for architecture details.

Arena Fleet uses a work queue for distributing tasks to workers: in-memory for development, Redis for production.

enterprise:
arena:
queue:
type: redis # memory (dev) or redis (production, the default)

memory runs in the arena-controller process: use a single controller replica, and queued jobs are lost on restart. redis requires a resolvable Redis URL, or the render fails.

The queue resolves its Redis target from enterprise.arena.queue.redis, then falls back to the cluster-wide redis.default. The first non-empty form wins, in this order: existingSecret (a Secret holding the full URL), url, then the decomposed host, port, db and user. Both blocks take the same fields.

redis:
default:
existingSecret:
name: redis-credentials
key: redis-url
# or: url: "rediss://my-redis.example.com:6379"
# or: host: my-redis.example.com
# port: 6379
enterprise:
arena:
queue:
type: redis
redis: # optional per-queue override of redis.default
url: ""
host: ""
port: 6379
db: 0
user: ""
tls:
caExistingSecret:
name: ""
key: ""

This is the operator-wide default. A Workspace service group that sets its own redis puts the runs in that group on it instead, controller and workers both; see services[].redis. No chart value is needed for that. The arena-controller’s ClusterRole already lets it read Workspaces and read and write Secrets in every namespace, which it uses to write each such run’s <run>-queue-redis Secret.

An empty host in both blocks leaves the queue without a Redis target. The decomposed form is cleartext only; a TLS connection needs the URL form. tls.caExistingSecret names a Secret holding the CA bundle for a Redis with a self-signed certificate.

Give the queue’s Redis a maxmemory-policy of noeviction (or a volatile-* policy). The queue’s keys are work, not cache: under allkeys-lru or allkeys-lfu Redis evicts a running run’s pending list or item records when memory runs short, and the run loses work without an error. Size maxmemory for about 1.1 KB per work item of the runs in flight; a finished run’s keys expire an hour after it ends, and a deleted run’s keys are deleted with it.

There is no Redis in the omnia chart. The omnia-dev subchart deploys a dev-only in-cluster Redis: a single-replica StatefulSet with no auth and no persistence, noeviction, maxmemory 512mb under a 768Mi limit. Its Service is named <fullname>-redis-master, where <fullname> is the release name when it contains omnia and <release>-omnia otherwise.

dev:
enabled: true
redis:
enabled: true

When dev.grafanaDashboards.redis.enabled is true, the omnia-dev subchart ships a ConfigMap labelled grafana_dashboard: "1" holding a Redis Overview dashboard (used memory, total keys, uptime, commands per second, memory usage and the Arena queue). The dashboard reads a Redis datasource that you provision in Grafana.

The Arena controller manages ArenaProject, ArenaJob, ArenaRun and ArenaTemplateSource resources.

enterprise:
arena:
controller:
replicaCount: 1
image:
repository: ghcr.io/altairalabs/omnia-arena-controller
tag: "" # Defaults to Chart appVersion
pullPolicy: IfNotPresent
resources: {}
workspaceIdleAfter: 336h
serviceAccount:
create: true
annotations: {}
name: "" # Empty means <release>-arena-controller

workspaceIdleAfter is how long a user’s working clone of an ArenaProject may go unused before the controller judges it. A clone with nothing unpushed is deleted, and opening the project clones it again. A clone with unsaved work is flagged in the project’s status.workspaces and never deleted. The value is a Go duration.

enterprise:
communityTemplates:
enabled: true # airGapped: true turns off the GitHub default
name: community-templates
url: https://github.com/AltairaLabs/promptkit-templates.git
branch: main
templatesPath: "" # Path within fetched content where templates live; empty = root
syncInterval: 1h
timeout: 2m
trust: community # official | partner | community; empty leaves it out

The source is created in the default workspace’s namespace (defaultWorkspace.namespace.name) and only when defaultWorkspace.enabled is true, because a template source must belong to a workspace. Other workspaces add their own. See ArenaTemplateSource CRD for details on template sources.

With airGapped: true, the source is not created while url is the GitHub default. Point url at an in-cluster mirror to keep it.

trust is the source’s tier in the gallery, community by default. The repository is AltairaLabs’ own, so official is an opt-in for an installer allowed the curate verb on arenatemplatesources (cluster-admin is). Admission refuses a tier above community from any other identity, a scoped Argo CD or Flux ServiceAccount included, and on helm upgrade --atomic that rolls the whole release back.

The PromptKit LSP server provides real-time YAML validation and code intelligence for the Project Editor.

enterprise:
promptkitLsp:
enabled: true # On by default; has no effect unless enterprise.enabled is also true
replicaCount: 1
image:
repository: ghcr.io/altairalabs/omnia-promptkit-lsp
tag: "" # Defaults to Chart appVersion
pullPolicy: IfNotPresent
service:
port: 8080
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 512Mi

Provides real-time YAML and PromptKit schema validation, semantic token highlighting, and diagnostics in the Project Editor. See the PromptKit documentation for the configuration format.

The Omnia Dashboard provides a web UI for monitoring and managing agents.

The agent and sandbox the Arena builder (“Generate with AI”) talks to in a workspace, as the install’s defaults. A Workspace overrides each in spec.builder. The defaults match the omnia-arena-builder chart; set them when that chart is installed under another agent.name, or for a builder of your own.

ValueDefaultDescription
dashboard.builder.agentarena-builderThe builder’s AgentRuntime in the workspace namespace
dashboard.builder.sandboxsandboxThe sandbox, in that agent’s ToolRegistry, a builder session declares its project mount on
dashboard.builder.projectPath/workspaceWhere that sandbox mounts the project, as the builder agent is told it
dashboard.builder.mountRoleprojectThe sandbox mount role a builder session declares the project on: one of operator.sandboxAPI.mountRoles, or one the Workspace configures. Reaches management-api as OMNIA_BUILDER_MOUNT_ROLE

An empty value leaves the builder unconfigured for every workspace that does not set it, and the dashboard says so.

dashboard:
enabled: true
image:
repository: ghcr.io/altairalabs/omnia-dashboard
pullPolicy: IfNotPresent
tag: "" # Defaults to Chart appVersion
replicaCount: 1
service:
type: ClusterIP
port: 3000
resources:
limits:
cpu: 500m
memory: 128Mi
requests:
cpu: 25m
memory: 32Mi

The dashboard pod is nginx. It serves the dashboard’s static files and passes /api/ to management-api, which does the work, so it needs little CPU or memory, and it keeps no state, so it scales freely. Sign-in sessions are management-api’s: more than one management-api replica needs a Redis session store (redis.default or dashboard.session.redis).

dashboard:
staticAssets:
keepBuilds: 3
persistence:
enabled: false
storageClassName: ""
accessModes: [ReadWriteMany]
size: 1Gi
existingClaim: ""
resources:
limits: { cpu: 200m, memory: 64Mi }
requests: { cpu: 10m, memory: 16Mi }
updateStrategy: {}

Each build of the dashboard names its JavaScript and CSS files by their content, under /_next/static/. A browser tab opened before an upgrade asks for the files of the build it loaded. A pod of the new build does not have them, so the tab’s next navigation can fail. With the store off, the dashboard reloads the page once when that happens, and shows a notice when it sees that a newer build is running, so the user can reload on their own terms.

staticAssets.persistence.enabled: true keeps earlier builds’ files instead. Every dashboard pod runs an init container, from the dashboard image, that copies the pod’s /_next/static/ into one shared volume and deletes the files of all but the newest keepBuilds builds. nginx serves a file from the pod’s own image first and from the volume second, so an open tab finds its files on any replica, during the rollout and after it.

The store keeps files, not page data. When an open tab moves to a page whose data it has not fetched yet, Next.js fetches that data from the running build, sees a different build, and loads the page in full, as a link to it would. That is a page load, not an error, and the tab’s state on the page it left is lost.

ValueDefaultDescription
staticAssets.persistence.enabledfalseTurn the store on
staticAssets.persistence.accessModes[ReadWriteMany]Access modes of the claim: ReadWriteMany, ReadWriteOnce or ReadWriteOncePod. More than one replica needs ReadWriteMany, and the chart refuses to render without it. A claim’s access modes cannot change once it exists, so changing this value on an install fails the upgrade: switch the store off, which deletes the claim, then on again with the new modes
staticAssets.persistence.storageClassName""Storage class; empty uses the cluster default
staticAssets.persistence.size1GiClaim size. A build’s files are tens of MiB. Each pod prunes the volume to keepBuilds - 1 other builds before it copies its own, so the volume holds at most keepBuilds builds
staticAssets.persistence.existingClaim""Use this claim instead of creating one. Set accessModes to its access modes, which the chart reads to choose the update strategy
staticAssets.keepBuilds3Builds kept, newest first. The pod’s own build is always kept, and a build a pod starts again, as in a rollback, counts as the newest
staticAssets.resourcessee aboveResources of the init container
updateStrategy{}The Deployment’s strategy. Empty lets the chart choose (see below)

Access modes and the update strategy:

  • ReadWriteMany, any number of replicas: the Deployment keeps the default RollingUpdate. Old and new pods mount the volume at once, on any node.
  • ReadWriteOnce, one replica: the chart sets strategy: Recreate. A ReadWriteOnce volume attaches to one node, so a rolling update’s new pod, scheduled on another node, would wait for the volume while the old pod waits for the new one to be ready. Recreate stops the old pod first. The dashboard is unavailable until the new pod is ready, and open tabs keep their files once it is, because the old build is already in the volume. Where the volume can attach to both pods at once, such as a single-node cluster, set updateStrategy to RollingUpdate yourself.
  • ReadWriteOnce, more than one replica: refused at render time.

The init container runs as the pod does: as uid 101, with a read-only root filesystem and no capabilities. It writes the volume through the pod’s fsGroup, 101 by default, so the storage class must honour fsGroup or give uid 101 write access. A dashboard.podSecurityContext you set replaces the chart’s whole pod security context, so it must keep fsGroup (101, or a group that can write the volume). Under openshift.enabled the platform assigns the fsGroup.

The store is a cache, so the init container fails open. When the volume is full, cannot be written, or another pod holds the lock past the wait limit, it logs a WARNING and exits successfully, and the pod starts and serves its own build. It also stops after 300 seconds in all, so a hung volume cannot keep the pod initialising. Only the open tabs of earlier builds lose anything: they recover by reloading, as with the store off. This matters most under Recreate, where no old pod is left serving.

The store covers files a build already had in the volume, so it protects upgrades from the first release that ran with it on, not the upgrade that turns it on. Every file is written under a temporary name and renamed into place, so nginx never serves a partial file. Pods that start at the same time take turns through a lock directory on the volume. The pod holding it updates a heartbeat in it every 15 seconds; a waiting pod breaks the lock only after watching that heartbeat stand still for 60 seconds of its own time, and gives up waiting after 180 seconds. No two pods’ clocks are compared, so clock skew between nodes does not matter. A pod frozen for longer than 60 seconds while holding the lock, as a node freeze can do, loses it, and on waking it notices at its next step and registers nothing. NFS offers no fencing, so a check and the step after it are never one: if the freeze falls exactly between the check and a delete, that one delete still happens, and another build can be left without one of its files. A tab that asks for it gets a 404, which the dashboard handles by reloading the page, and the file stays missing until a pod of that build starts again and copies it back. nginx never serves a partial file.

dashboard:
avatars:
gravatar: false
ValueBehavior
false (default)The user menu shows the signed-in user’s initials. The browser makes no avatar request.
trueThe user menu loads the user’s picture from www.gravatar.com. The user’s browser makes the request and sends a SHA-256 hash of the user’s email. The initials show if the image does not load.

airGapped: true fails the render when gravatar is true. Earlier releases loaded Gravatar avatars with no setting to turn them off.

dashboard:
ingress:
enabled: false
className: "" # Empty uses the cluster's default IngressClass
annotations: {}
host: dashboard.example.com
tls:
- secretName: dashboard-tls
hosts:
- dashboard.example.com

The Ingress, and the HTTPRoute the chart creates for the dashboard on the external or internal Gateway, send three path prefixes to management-api’s Service (managementApi.service.port, 8080) and everything else to the dashboard: /api/v1/agents/ (agent consoles), /api/v1/lsp (the Arena editor’s language server) and /api/v1/sandboxes/ (a browser sign-in’s live view). These are the browser’s WebSockets. management-api serves them and authenticates each from the browser’s own session cookie, proxy headers or API key. The dashboard has no WebSocket port.

A proxy in front of these routes must pass the browser’s Origin and Host through, or name the host as X-Forwarded-Host. management-api refuses an upgrade whose Origin is not dashboard.baseUrl, or, with that unset, not on the request’s host name.

dashboard:
wsProxy:
url: "" # Base URL the browser opens these WebSockets against

Leave dashboard.wsProxy.url empty unless the WebSockets are served at a different origin from the pages. The empty default uses the page’s own origin, which the routes above send to management-api. Local development sets ws://localhost:3002, which Tilt forwards to management-api.

dashboard:
auth:
# Mode: anonymous, proxy, oauth, or builtin. No default: the chart
# fails to render until you set one.
mode: oauth
# Required to render with mode: anonymous; the chart fails without it.
allowAnonymous: false
# Session configuration. Both are optional: left empty, the chart
# generates a secret on first install and keeps it across upgrades.
sessionSecret: "" # A literal. Visible in `helm get values` — avoid in production.
existingSessionSecret: "" # Name of a Secret with the key OMNIA_SESSION_SECRET. Preferred.
cookieName: omnia_session
ttl: 86400 # 24 hours
cliTokenTtlSeconds: 3600 # TTL of tokens minted for the deploy CLI login
# Role mapping
anonymousRole: viewer
roleMapping:
adminGroups: []
editorGroups: []

For use with external authentication proxies (e.g., oauth2-proxy):

dashboard:
auth:
mode: proxy
proxy:
headerUser: X-Forwarded-User
headerEmail: X-Forwarded-Email
headerGroups: X-Forwarded-Groups
headerDisplayName: X-Forwarded-Preferred-Username

For direct OAuth/OIDC integration:

dashboard:
auth:
mode: oauth
oauth:
provider: generic # generic, google, azure or okta
issuerUrl: https://your-tenant.auth0.com/ # Required for generic
clientId: your-client-id
clientSecret: your-client-secret # Or use existingSecret
existingSecret: "" # Secret with OMNIA_OAUTH_CLIENT_ID and OMNIA_OAUTH_CLIENT_SECRET
scopes: "" # Comma-separated; empty uses the provider's preset
azureTenantId: "" # azure provider
oktaDomain: "" # okta provider
claims: # Each empty field uses the provider's preset
username: ""
email: ""
displayName: ""
groups: ""

API keys authenticate programmatic clients (CI, the deploy adapter, scripts). The store backend is selected with dashboard.apiKeys.store. See Durable API keys for a full walkthrough of the three stores.

dashboard:
apiKeys:
enabled: true # Enable API key authentication
# Store backend:
# memory — ephemeral, dev only (keys lost on restart)
# file — read-only Kubernetes Secret, GitOps-friendly
# postgres — durable, keys (bcrypt-hashed) live in Postgres
store: memory
# File store (store: file)
filePath: /etc/omnia/api-keys/keys.json # Where the keys Secret is mounted
keysSecret: "" # Name of an existing Secret holding keys.json
# Postgres store (store: postgres)
postgresUrl: "" # Connection URL; falls back to builtin.postgresUrl when empty
existingPostgresSecret: "" # Existing Secret with an OMNIA_AUTH_API_KEYS_POSTGRES_URL key
maxPerUser: 10 # Maximum keys per user
defaultExpiration: 90 # Default expiration in days (0 = never)

A store left at memory is auto-promoted to postgres when a postgresUrl or existingPostgresSecret is set, so a wired durable backend is never silently ignored.

management-api is the Go service that serves the dashboard’s whole API. It serves every sign-in route and decides who each request is from. It reads the dashboard’s auth values above (dashboard.auth, dashboard.oauth, dashboard.proxy, dashboard.session, dashboard.apiKeys and dashboard.builtin), under the same variable names, so there is nothing to configure twice and nothing to switch on.

managementApi:
servesManagementPlaneKeys: true
acceptLegacyIdentityTokens: false
database:
existingSecret: "" # Secret with a POSTGRES_CONN key: management-api's own database
ValueDefaultDescription
managementApi.servesManagementPlaneKeystruePoint the operator’s --mgmt-plane-jwks-url (and so every facade) at management-api’s /api/v1/auth/jwks, and Doctor’s and arena-controller’s service-token mint at its /api/v1/auth/service-token. Must stay true with the dashboard enabled, which serves no keys: the chart refuses false. Ignored when management-api is not deployed
managementApi.acceptLegacyIdentityTokensfalseAccept an identity token minted by a dashboard pod from before management-api took over authentication. Off, a request carrying one is resolved from its cookie, proxy headers or API key alone, and the token is ignored. Set true only for an upgrade from a build whose dashboard still mints these tokens (a main build from before the cut-over; no release is one), so dashboard pods that have not rolled yet keep working, then set it back to false. Those tokens verify against the dashboard’s old keypair, which the chart does not trust by itself, so list <release>-dashboard-signing-keypair in managementApi.signingKey.additionalCertSecrets for the same upgrade
managementApi.database.existingSecret""management-api’s own database, holding builtin users and, when dashboard.apiKeys names no database, API keys

The chart refuses to render when:

  • managementApi.replicaCount is above 1 and no Redis session store resolves (redis.default or dashboard.session.redis). management-api writes the sessions and reads them back, and the session cookie names a record in the store, so every replica must read the same one. One replica keeps them in memory.
  • dashboard.auth.mode is builtin and managementApi.database.existingSecret is empty, because management-api keeps builtin users in its own database.

The dashboard keeps no user store and signs nothing, so these values are gone or going. Each still renders when an upgrade’s values file sets it, and the install notes name it:

ValueStatus
dashboard.builtin.storeType, dashboard.builtin.sqlitePathRemoved. They chose the dashboard’s user store
dashboard.persistenceDeprecated, and mounts nothing. For one release the chart still renders <release>-dashboard-data when enabled is true and existingClaim is empty, now annotated helm.sh/resource-policy: keep: v0.9.0-beta.20 created the claim without that annotation, and Helm reads it from the live object, so dropping the claim outright would delete the old users. Delete the claim yourself once you will not roll back. A claim named by existingClaim was never the chart’s, and the chart leaves it alone. An install of v0.9.0-beta.20 with enabled: true must upgrade through this release, not past it: the release after drops the claim, and Helm deletes a claim that never got the annotation
dashboard.signingKeyRemoved. The chart neither renders nor trusts the dashboard’s keypair. The <release>-dashboard-signing-keypair Secret an earlier release created is kept, so delete it yourself, or list it in managementApi.signingKey.additionalCertSecrets to keep trusting tokens it signed through a rollout

management-api holds the only management-plane signing key. Its JWKS publishes that key and any managementApi.signingKey.additionalCertSecrets. dashboard.jwksUrl, dashboard.serviceTokenUrl and doctor.mgmtPlaneToken.url still win when set.

White-label branding is an enterprise feature. The chart emits NEXT_PUBLIC_BRAND_* environment variables in the dashboard ConfigMap, which management-api reads, when enterprise.enabled is true and dashboard.branding.productName is set. Helm cannot evaluate a runtime license, so the license’s whiteLabel entitlement is enforced afterward, at runtime, by management-api before /api/v1/config hands the brand to the browser. Leave productName empty to keep the default Omnia brand.

dashboard:
branding:
productName: "" # Product name in the sidebar, page title and auth screens
logoDarkUrl: "" # Logo URL (sidebar and link-preview card)
faviconUrl: "" # Favicon URL
colors: {} # Colour map, both themes (primary, accent, sidebar, ...)
colorsDark: {} # Colour map overrides for the dark theme
fonts:
family: "" # Font family name applied to --font-sans
mono: "" # Monospace family applied to --font-mono
url: "" # Optional web-font stylesheet URL to load the family
links:
sales: "" # Sales contact (email or URL)
upgradeUrl: "" # Upgrade/enterprise URL shown on license gates
customCss: "" # Token-scoped custom CSS appended to :root only

For the accepted colour keys, precedence, content-security-policy rules and asset serving, see Dashboard branding reference. For the steps, see White-label the dashboard.

All observability components are optional and disabled by default.

prometheus:
enabled: false
server:
persistentVolume:
enabled: false # Enable for production
prefixURL: /prometheus
baseURL: /prometheus
alertmanager:
enabled: false
prometheus-pushgateway:
enabled: false
prometheus-node-exporter:
enabled: false
kube-state-metrics:
enabled: false
grafana:
enabled: false
adminPassword: admin # Change in production
grafana.ini:
server:
root_url: "%(protocol)s://%(domain)s:%(http_port)s/grafana/"
serve_from_sub_path: true
sidecar:
dashboards:
enabled: true
label: grafana_dashboard
searchNamespace: ALL
datasources:
enabled: true
label: grafana_datasource
searchNamespace: ALL
service:
type: ClusterIP
loki:
enabled: false
deploymentMode: SingleBinary
loki:
auth_enabled: false
useTestSchema: true
storage:
type: filesystem
commonConfig:
replication_factor: 1
singleBinary:
replicas: 1
persistence:
enabled: true
size: 10Gi
backend:
replicas: 0
read:
replicas: 0
write:
replicas: 0
gateway:
enabled: false
chunksCache:
enabled: false
resultsCache:
enabled: false
ruler:
enabled: false

Enable the ruler for log-based alerting with a writable emptyDir volume:

loki:
ruler:
enabled: true
singleBinary:
extraVolumes:
- name: rules
emptyDir: {}
extraVolumeMounts:
- name: rules
mountPath: /var/loki/rules
alloy:
enabled: false
alloy:
configMap:
content: |
// Kubernetes pod discovery and log collection
// See values.yaml for full configuration
tempo:
enabled: false
tempo:
reportingEnabled: false
tempoQuery:
enabled: false
persistence:
enabled: true
size: 5Gi

For exposing agents externally:

gateway:
enabled: false
name: agents
className: istio
listeners:
http:
port: 80
protocol: HTTP
https:
enabled: false
port: 443
protocol: HTTPS
tlsSecretName: ""
annotations: {} # On the Gateway resource only
infrastructureAnnotations: {} # spec.infrastructure.annotations

annotations land on the Gateway resource. Cloud load-balancer annotations (static IP, resource group) belong in infrastructureAnnotations: the Gateway API controller (Gateway API v1.1+) forwards them to the Service it provisions.

defaultExposure has the operator create an HTTPRoute for each agent that sets facades[].expose.enabled. The chart cannot template these routes, because agents are created at runtime. Exposure adds no authentication; spec.externalAuth still decides who may connect.

defaultExposure:
enabled: false
baseDomain: "" # Required when enabled; hosts are {name}.{namespace}.{baseDomain}
gateway:
name: "" # Required when enabled; e.g. the chart's {fullname}-agents
namespace: "" # Empty = the agent's namespace
sectionName: "" # Optional listener name

A Gateway in another namespace needs a ReferenceGrant, which the operator does not create. See Expose agents.

For observability tools:

internalGateway:
enabled: false
name: internal
className: istio
port: 8080
grafana:
enabled: true
path: /grafana
prometheus:
enabled: true
path: /prometheus
istio:
enabled: false
tempoService: omnia-tempo.omnia-system.svc.cluster.local
tempoPort: 4317

authentication.enabled renders an Istio RequestAuthentication and two AuthorizationPolicy resources that require a valid JWT on agent pods. It requires istio.enabled: true.

These policies cover agent pods in the release namespace only. The chart renders them in the namespace you install Omnia into, and Istio applies a selector-based policy only to pods in its own namespace. Agents in workspace namespaces are not covered, and neither is the edge-trust header strip under authentication.edgeTrust. To require a JWT on an agent in any namespace, set spec.externalAuth.oidc on the AgentRuntime. See Configure authentication.

authentication:
enabled: false
jwt:
issuer: "" # e.g., https://your-tenant.auth0.com/
jwksUri: "" # Defaults to {issuer}/.well-known/jwks.json
audiences: []
forwardOriginalToken: true
outputClaimToHeaders: []
# - header: x-user-id
# claim: sub
authorization:
requiredClaims: []
# - claim: "scope"
# values: ["agents:access"]
excludePaths:
- /healthz
- /readyz

Advanced autoscaling with scale-to-zero support.

keda:
enabled: false # Set to true only if KEDA is not already installed
operator:
watchNamespace: "" # Empty = all namespaces
prometheus:
serverAddress: "http://omnia-prometheus-server.omnia-system.svc.cluster.local"

Agent runtimes get their sandboxes from the operator’s Sandbox API. The operator creates every SandboxClaim an agent uses. The API runs on every operator replica and is exposed as the sandbox-api port of the <release>-operator Service. The chart passes that Service’s URL to the operator, which sets it as OMNIA_SANDBOX_API_URL on the runtime container of every AgentRuntime with spec.sandbox.enabled.

operator:
sandboxAPI:
port: 8087
claimPolicyGrace: 60s
mountRoles:
- name: project
path: "arena/workspaces/{id}/{user}"
mustExist: true
minRole: editor

operator.sandboxAPI.claimPolicyGrace is the time after the chart’s claim admission policy is created during which the Sandbox API holds back a sandbox that mounts a project, binds a Connection or syncs back.

operator.sandboxAPI.mountRoles are the mount roles a session may declare on its sandbox, beside the built-in scratch and session. The chart renders them as the operator’s --sandbox-mount-roles flag, and the operator refuses to start when one is invalid. A Workspace replaces a role by name, or adds one, in spec.sandboxMountRoles. The default is one role, project, on which the Arena builder declares its project mount.

FieldDefaultDescription
name—The role a declaration and a class’s mounts name. A DNS label, not scratch or session
path—The directory a mount of this role binds, relative to the workspace’s content. Exactly one whole segment is {id}, the id the declaration names. Not absolute, no .., never reaching or containing sandboxes/, sessions/, arena/sources/, arena/snapshots/, arena/deployed/, arena/template-sources/, arena/template-indexes/, skills/ or manifests/, and not overlapping another role’s path
mustExisttrueRefuse a declaration whose directory is not there (MountNotFound). false has the operator create it as the claim binds
minRoleeditorThe least workspace role (viewer, editor, owner) a verified user needs to declare the mount

See SandboxClaim mount roles.

The operator knows nothing about any sandbox image, so each class the chart ships carries its image’s own shape in values: args, env, ports, filesystem (and readiness, connections). These are the codegen class’s:

sandbox:
handlerPort: mcp # the class port the reference registries dial
handlerPath: /sse # the path on it
codegenClass:
args: ["-addr=:8080", "-workspace=/workspace"]
env:
- {name: HOME, value: /sandbox-cache}
- {name: GOPATH, value: /sandbox-cache/go}
- {name: GOCACHE, value: /sandbox-cache/go-build}
- {name: GOMODCACHE, value: /sandbox-cache/go/pkg/mod}
ports:
- {name: mcp, port: 8080}
filesystem:
workspace: {mountPath: /workspace}
scratch:
- {name: cache, mountPath: /sandbox-cache, sizeLimit: 4Gi}

promptarenaClass has the same shape (its image is built on codegen-sandbox), and browserClass its own, below. A class with no ports fails to render. See SandboxClass for each field.

An opt-in, second ClusterSandboxClass carrying the promptarena/packc CLIs, built on the codegen-sandbox base, for authoring against a mounted PromptArena project instead of scratch space. Off by default; a claim naming this class gets a project mount bound read-write to /workspace, and does not also get scratch space. See SandboxClaim.

sandbox:
promptarenaClass:
enabled: false
name: promptarena
image:
repository: ghcr.io/altairalabs/omnia-promptarena-sandbox
tag: ""
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "2"
memory: 2Gi

image.repository is required at render time when enabled is true; an empty image.tag means the chart’s appVersion. image is an object: the schema refuses a plain string. resources.limits for cpu and memory are mandatory: the SandboxClass CRD rejects an untrusted class without both.

The image builds from Dockerfile.promptarena-sandbox in the Omnia repository and is published with every release as ghcr.io/altairalabs/omnia-promptarena-sandbox. Tilt runs with ENABLE_SANDBOXES=true build it locally.

An opt-in ClusterSandboxClass for a browser: structured browser control over MCP (agent-browser behind an allowlisting bridge) on a headless Chromium, with no shell. Its values give it a read-only root, no workspace, and an in-memory /tmp for the browser profile. It has no egress, so it reaches nothing until Connections bind hosts to a claim.

sandbox:
browserClass:
enabled: false
name: browser
image:
repository: ghcr.io/altairalabs/omnia-browser-sandbox
tag: "" # empty = the chart's appVersion
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
runtimeClassName: "" # gVisor or Kata, where the nodes support one
args: ["-addr=:8080"]
env:
- {name: HOME, value: /tmp/home}
ports:
- {name: mcp, port: 8080}
filesystem:
readOnlyRoot: true
scratch:
- {name: tmp, mountPath: /tmp, sizeLimit: 512Mi, medium: Memory}
connections: # roles; a role taking a browserSession declares `session`
- role: site
session:
stateFile: /tmp/state/state.json
args:
- "-connection={{.Connection}}"
- "-state-required"
- "{{if .SignedOutPrefixes}}-signed-out-prefixes={{.SignedOutPrefixes}}{{end}}"
- "{{if .StateFile}}-state-file={{.StateFile}}{{end}}"
toolRegistry:
enabled: false
name: browser-sandbox
allowedTools: [agent_browser_open, agent_browser_read, ...] # all 33 by default

connections declares the site role. An AgentRuntime binds a Connection to it in spec.sandbox.connections; any credential kind binds, and a browserSession is delivered as session says. Unbound, the browser is anonymous and reaches nothing.

toolRegistry.enabled installs a ToolRegistry named toolRegistry.name in sandbox.toolRegistry.namespace. Its one sandbox, browser, names this class and binds no Connection. Its one mcp handler dials the class’s mcp port over SSE, with toolRegistry.allowedTools as its allowlist. The default list is every tool the browser bridge serves. The chart fails to render the registry without browserClass.enabled, a name, or at least one tool. See Give an agent a browser.

Chrome runs with --no-sandbox, because restricted Pod Security denies it the user namespaces its own sandbox needs. The pod is the containment, and a sandboxed RuntimeClass is the recommended next rung.

With the browser class on, the chart also installs browser-enrol, the class a person signs in to a browserSession Connection in (an enrolment). It runs the browser class’s image, tag, resources and RuntimeClass, with its own port, because a class’s ports cannot change. With it installed, the operator serves its enrolment endpoint and admits management-api’s pods, and nothing else, to an enrolment’s view port.

sandbox:
browserClass:
enrolment:
enabled: true
name: browser-enrol
maxPerWorkspace: 5 # live enrolments per workspace
maxDuration: "" # 1m-20m; empty is 15m
args: ["-addr=:8080"]
env:
- {name: HOME, value: /tmp/home}
ports:
- {name: view, port: 8080}
view: {port: view}
readiness: {port: view, httpGet: {path: /healthz}}
filesystem:
readOnlyRoot: true
scratch:
- {name: tmp, mountPath: /tmp, sizeLimit: 512Mi, medium: Memory}
connections:
- {role: site, session: {}}
enrolmentArgs: [...] # the bridge's interactive flags, templated

enrolmentArgs becomes the class’s spec.enrolment.args; see Enrolment classes for what they may use. The chart fails to render a class without them.

The dashboard offers Sign in with a browser only when this class is installed under Enterprise and the install names the KMS key browser Connections seal their sessions with. management-api writes that key into each browser Connection created in the dashboard, so nobody is asked about KMS.

connections:
browserSession:
encryption:
kmsProvider: aws-kms # aws-kms, azure-keyvault, gcp-kms, vault or local
keyID: alias/omnia-browser-sessions
secretRef: {name: ""} # a Secret in each workspace namespace; omit for workload identity

local keeps the key in a Kubernetes Secret and is for development only (hack/dev-kms-secret.sh); the chart fails to render local without secretRef.name.

Every sandbox whose Connections name hosts gets its own egress proxy, which dials public addresses only. The one setting here is for end-to-end test clusters, which need a sandbox’s browser to sign in to an in-cluster TLS login fixture. Never set it on an install that serves real users.

sandbox:
egressProxy:
insecureTestHosts: [] # [{host, namespace, service}], TEST ONLY
acknowledgeInsecureTestOnly: false # must be true, or rendering fails
insecureTestSPKI: "" # base64 SHA-256 of the fixture cert's SPKI

Each entry names a fixture host, a lowercase DNS name, and the Service serving it on port 443; namespace and service are DNS-1123 labels, and the chart fails to render anything else. The proxy dials the Service’s cluster IP and never resolves the host, so no DNS rewrite is needed for it. The chart passes them to the operator as --sandbox-egress-insecure-test-hosts, and prints a warning in its notes. See Test-only: in-cluster fixture hosts for what the switch does and does not open.

The omnia chart installs no demo agents. The public demos ship as the omnia-demo chart, at charts/omnia-demo in the AltairaLabs/omnia-demo repository. Its values are documented there.

See Configure Helm for common scenarios for minimal, production, and observability-only value sets.