Helm Values Reference
This page documents the configuration values for the Omnia Helm chart. See Install with Helm for installation steps.
Operator Configuration
Section titled “Operator Configuration”Basic Settings
Section titled “Basic Settings”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.imagePullSecretsnameOverride: ""fullnameOverride: ""Private registry
Section titled “Private registry”global: imageRegistry: "" # e.g. registry.internal.example.com imagePullSecrets: [] # e.g. [{name: mirror-pull}] or [mirror-pull]| Value | Default | Description |
|---|---|---|
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 flag | Pods |
|---|---|
--facade-image | Agent facade container |
--framework-image | Agent runtime container, one per framework type |
--session-api-image, --memory-api-image, --privacy-api-image | Per-workspace services |
--eval-worker-image | Eval workers |
--policy-broker-image | Policy broker sidecar (Enterprise) |
--egress-proxy-image | Sandbox 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:
| Subchart | Registry setting |
|---|---|
grafana | global.imageRegistry, global.imagePullSecrets |
loki | global.imageRegistry; pull secrets at loki.imagePullSecrets |
tempo | global.imageRegistry; pull secrets at tempo.tempo.pullSecrets |
alloy | global.image.registry, global.image.pullSecrets |
keda | global.image.registry; pull secrets at keda.imagePullSecrets |
prometheus | none: 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:
helm template omnia oci://ghcr.io/altairalabs/charts/omnia --devel \ -f values.yaml | grep 'image:' | sort -uInstall in an air-gapped cluster builds a bundle of every image and pushes it to these paths.
Service Account
Section titled “Service Account”serviceAccount: create: true annotations: {} name: "" # Defaults to the release fullname when create is truePod Configuration
Section titled “Pod Configuration”podAnnotations: {}
podSecurityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault
securityContext: readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: - ALLOpenShift
Section titled “OpenShift”openshift: enabled: false| Value | Default | Description |
|---|---|---|
openshift.enabled | false | Runs 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
Section titled “Resources”resources: limits: cpu: "1" memory: 512Mi requests: cpu: 100m memory: 256MiScheduling
Section titled “Scheduling”nodeSelector: {}tolerations: []affinity: {}Leader Election
Section titled “Leader Election”leaderElection: enabled: trueHealth Probes
Section titled “Health Probes”probes: port: 8081 liveness: initialDelaySeconds: 15 periodSeconds: 20 readiness: initialDelaySeconds: 5 periodSeconds: 10Metrics
Section titled “Metrics”metrics: enabled: true port: 8443 secure: trueWebhooks
Section titled “Webhooks”webhook: enabled: false port: 9443rbac: create: trueCRD preflight check
Section titled “CRD preflight check”crdPreflight: enabled: truecrdPreflight 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.
PromptPack schema source
Section titled “PromptPack schema source”operator: promptPackSchema: source: embedded| Value | Behavior |
|---|---|
embedded (default) | The operator validates every pack against the schema built into its binary and makes no outbound call. |
network | The 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. |
Air-gapped install
Section titled “Air-gapped install”airGapped: false| Value | Behavior |
|---|---|
false (default) | Each outbound connection follows its own value. |
true | Turns 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.
Commercial control plane
Section titled “Commercial control plane”controlPlane: url: "" # Empty disables the integration grantSecretName: omnia-installation-grant displayName: "" environment: productionThe 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 Configuration
Section titled “Facade Configuration”facade: image: repository: ghcr.io/altairalabs/omnia-facade tag: "" # Defaults to Chart appVersionMedia Storage
Section titled “Media Storage”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/mediaOMNIA_MEDIA_STORAGE_TYPE accepts none, local, s3, gcs, or azure.
See Configure Media Storage for
the S3, GCS, and Azure backends.
Framework Configuration
Section titled “Framework Configuration”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 appVersionEnterprise Features
Section titled “Enterprise Features”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):
- ArenaProject — PromptKit bundle sources
- ArenaJob — test execution
- ArenaTemplateSource — project templates
Eval Worker Configuration
Section titled “Eval Worker Configuration”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: IfNotPresentWhether 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 Configuration
Section titled “Arena Fleet Configuration”Arena Fleet provides distributed testing for PromptKit bundles. This is an enterprise feature.
Basic Settings
Section titled “Basic Settings”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: 128MiSet 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 Configuration
Section titled “Source Configuration”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 Queue (Redis)
Section titled “Arena Queue (Redis)”Arena Fleet uses a work queue for distributing tasks to workers: in-memory for development, Redis for production.
Queue Type
Section titled “Queue Type”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.
Redis target
Section titled “Redis target”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.
In-cluster dev Redis
Section titled “In-cluster dev Redis”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: trueRedis dashboard for Grafana
Section titled “Redis dashboard for Grafana”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.
Arena Controller
Section titled “Arena Controller”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-controllerworkspaceIdleAfter 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.
Community Templates
Section titled “Community Templates”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 outThe 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.
PromptKit LSP
Section titled “PromptKit LSP”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: 512MiProvides real-time YAML and PromptKit schema validation, semantic token highlighting, and diagnostics in the Project Editor. See the PromptKit documentation for the configuration format.
Dashboard Configuration
Section titled “Dashboard Configuration”The Omnia Dashboard provides a web UI for monitoring and managing agents.
Arena builder
Section titled “Arena builder”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.
| Value | Default | Description |
|---|---|---|
dashboard.builder.agent | arena-builder | The builder’s AgentRuntime in the workspace namespace |
dashboard.builder.sandbox | sandbox | The sandbox, in that agent’s ToolRegistry, a builder session declares its project mount on |
dashboard.builder.projectPath | /workspace | Where that sandbox mounts the project, as the builder agent is told it |
dashboard.builder.mountRole | project | The 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.
Basic Settings
Section titled “Basic Settings”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: 32MiThe 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).
Open tabs across upgrades
Section titled “Open tabs across upgrades”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.
| Value | Default | Description |
|---|---|---|
staticAssets.persistence.enabled | false | Turn 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.size | 1Gi | Claim 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.keepBuilds | 3 | Builds 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.resources | see above | Resources 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 defaultRollingUpdate. Old and new pods mount the volume at once, on any node.ReadWriteOnce, one replica: the chart setsstrategy: Recreate. AReadWriteOncevolume 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.Recreatestops 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, setupdateStrategytoRollingUpdateyourself.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.
Avatars
Section titled “Avatars”dashboard: avatars: gravatar: false| Value | Behavior |
|---|---|
false (default) | The user menu shows the signed-in user’s initials. The browser makes no avatar request. |
true | The 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.
Ingress
Section titled “Ingress”dashboard: ingress: enabled: false className: "" # Empty uses the cluster's default IngressClass annotations: {} host: dashboard.example.com tls: - secretName: dashboard-tls hosts: - dashboard.example.comThe 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 againstLeave 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.
Authentication
Section titled “Authentication”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: []Proxy Authentication
Section titled “Proxy Authentication”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-UsernameOAuth Authentication
Section titled “OAuth Authentication”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
Section titled “API Keys”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 sign-in and keys
Section titled “Management API sign-in and keys”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| Value | Default | Description |
|---|---|---|
managementApi.servesManagementPlaneKeys | true | Point 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.acceptLegacyIdentityTokens | false | Accept 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.replicaCountis above 1 and no Redis session store resolves (redis.defaultordashboard.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.modeisbuiltinandmanagementApi.database.existingSecretis 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:
| Value | Status |
|---|---|
dashboard.builtin.storeType, dashboard.builtin.sqlitePath | Removed. They chose the dashboard’s user store |
dashboard.persistence | Deprecated, 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.signingKey | Removed. 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.
Branding (White-label)
Section titled “Branding (White-label)”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 onlyFor the accepted colour keys, precedence, content-security-policy rules and asset serving, see Dashboard branding reference. For the steps, see White-label the dashboard.
Observability Stack
Section titled “Observability Stack”All observability components are optional and disabled by default.
Prometheus
Section titled “Prometheus”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: falseGrafana
Section titled “Grafana”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: ClusterIPloki: 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: falseEnable 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/rulesalloy: enabled: false alloy: configMap: content: | // Kubernetes pod discovery and log collection // See values.yaml for full configurationtempo: enabled: false tempo: reportingEnabled: false tempoQuery: enabled: false persistence: enabled: true size: 5GiGateway API
Section titled “Gateway API”External Gateway
Section titled “External Gateway”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.annotationsannotations 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.
Per-agent exposure
Section titled “Per-agent exposure”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 nameA Gateway in another namespace needs a ReferenceGrant, which the operator does not create. See Expose agents.
Internal Gateway
Section titled “Internal Gateway”For observability tools:
internalGateway: enabled: false name: internal className: istio port: 8080 grafana: enabled: true path: /grafana prometheus: enabled: true path: /prometheusIstio Integration
Section titled “Istio Integration”istio: enabled: false tempoService: omnia-tempo.omnia-system.svc.cluster.local tempoPort: 4317Authentication
Section titled “Authentication”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 - /readyzAdvanced 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"Sandbox Configuration
Section titled “Sandbox Configuration”Sandbox API
Section titled “Sandbox API”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: editoroperator.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.
| Field | Default | Description |
|---|---|---|
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 |
mustExist | true | Refuse a declaration whose directory is not there (MountNotFound). false has the operator create it as the claim binds |
minRole | editor | The least workspace role (viewer, editor, owner) a verified user needs to declare the mount |
Sandbox class shapes
Section titled “Sandbox class shapes”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.
PromptArena Sandbox Class
Section titled “PromptArena Sandbox Class”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: 2Giimage.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.
Browser Sandbox Class
Section titled “Browser Sandbox Class”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 defaultconnections 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.
Browser sign-in class
Section titled “Browser sign-in class”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, templatedenrolmentArgs becomes the class’s spec.enrolment.args; see
Enrolment classes for what
they may use. The chart fails to render a class without them.
Connections: the browser-session key
Section titled “Connections: the browser-session key”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 identitylocal 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.
Sandbox egress proxy (test-only switch)
Section titled “Sandbox egress proxy (test-only switch)”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 SPKIEach 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.
Demo content
Section titled “Demo content”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.
Example Configurations
Section titled “Example Configurations”See Configure Helm for common scenarios for minimal, production, and observability-only value sets.