Skip to content

The PromptPack Deploy Program

Omnia is a Kubernetes-native runtime, but pack authors work in PromptArena and do not use kubectl. The deploy program bridges the two. It lets a PromptPack author push a pack into an Omnia workspace without hand-writing CRDs, using the promptarena-deploy-omnia deploy adapter.

This page explains the moving parts: the deploy-profile export, the browser-login flow, and how a deployed pack becomes a running AgentRuntime. For the step-by-step walkthrough, see Deploy a PromptPack to Omnia.

A pack on its own is inert. To run it in Omnia you need:

  • an API endpoint and workspace to deploy into,
  • the target workspace configured with a ready service group (the session-api / memory-api backend agents persist to),
  • an API credential scoped to that workspace,
  • one or more Providers to back the LLM calls, and
  • optionally SkillSources for retrieval and tools.

Getting each of these names right by hand is error-prone. A Provider name that matches nothing in the workspace is reported by the deploy plan as provider_not_found, and a Provider that is not Ready leaves the AgentRuntime Pending with ProviderReady false. The deploy-profile export gives the author a pre-filled config block built from what the workspace has.

The adapter talks only to the dashboard’s address, where management-api serves every /api path. management-api answers discovery requests itself and forwards deploys to the operator, which owns the translation into CRDs.

flowchart LR
CLI["promptarena CLI + promptarena-deploy-omnia"]
subgraph MA["management-api, at the dashboard's address"]
PROFILE["GET /api/workspaces/{name}/deploy-profile"]
DEPLOY["POST /api/workspaces/{name}/deployments"]
LOGIN["/api/cli/authorize, grant, token"]
end
OP["Operator deploy-intent API (:8085)"]
K8S["Kubernetes API"]
CLI --> LOGIN
CLI --> PROFILE
CLI --> DEPLOY
PROFILE -->|"list Provider, SkillSource"| K8S
DEPLOY -->|"DeployIntent + identity JWT"| OP
OP -->|"ConfigMap, PromptPack, AgentRuntime"| K8S
K8S --> REC["AgentRuntimeReconciler"]
REC -->|"Deployment + Service"| K8S

The deploy profile is a discovery payload for a single workspace. The management-api serves it at GET /api/workspaces/{name}/deploy-profile. It is discovery only, and the endpoint never returns a secret. It contains:

  • api_endpoint: the dashboard’s external ingress URL, derived from the forwarded host (falling back to OMNIA_DASHBOARD_EXTERNAL_URL).
  • workspace: the target workspace name.
  • providers: the discovery menu of Providers.
  • skills: the discovery menu of SkillSources.

Two filters shape that menu.

The first filter lists only Providers and SkillSources whose status.phase is Ready are listed. A Provider in phase Error or Unavailable, a SkillSource in phase Error, and a resource that has no phase yet are all left out, because a deployment that references one does not come up.

The second filter exports only llm-role Providers. The embedding, tts, stt, image and inference roles are workspace-level services, consumed by memory-api and similar subsystems, and are not per-agent extras. The rerank role is reserved: no component consumes it, and memory retrieval does not rerank. Bundling any of them into an agent’s spec.providers breaks the pack at its first request, so the export drops them. A Provider with no explicit spec.role defaults to llm.

The raw discovery payload lists real Provider names. Two things happen on top of it when an author exports:

  • The default-LLM picker: the export UI asks the author to pick one Provider and marks it name: default in the adapter config, with ref pointing at the real Provider CRD. The runtime loads the binding named default as its primary LLM. With no binding of that name, it uses the first binding by sorted name.
  • The workspace-scoped token: the profile itself carries no secret, so the export mints an omnia_sk_ API token scoped to this workspace. The token deploys only to that workspace, and it acts with the author’s own role there. On re-export the dashboard offers to reuse the saved token or regenerate it (revoking the old key), so repeated exports do not pile up duplicate keys. If the deployment uses a read-only API-key store, the token is left as a placeholder for the author to mint by hand.

When the dashboard runs in OAuth mode, the CLI can mint the whole config through a browser-login flow (the same shape as gcloud auth login) instead of copy-paste. The Omnia side is three endpoints.

sequenceDiagram
participant CLI as promptarena CLI
participant B as Browser
participant M as management-api
CLI->>B: open /api/cli/authorize?callback=loopback&state=nonce
B->>M: GET /api/cli/authorize
M-->>B: redirect to OIDC login, then the dashboard's /cli/select
B->>M: POST /api/cli/grant (workspace)
M-->>B: 303 to loopback callback with one-time code
B->>CLI: GET callback?code=...&state=...
CLI->>M: POST /api/cli/token (code)
M-->>CLI: token + deploy profile

GET /api/cli/authorize stashes the CLI’s loopback callback and state, and routes the browser through OIDC login to a workspace picker at /cli/select. POST /api/cli/grant re-checks the author’s editor access, mints a one-time exchange code and 303-redirects the browser to the loopback callback, so no token crosses the browser. POST /api/cli/token is the only place a token is issued: a workspace-scoped token with a default TTL of 1 hour (OMNIA_AUTH_CLI_TOKEN_TTL_SECONDS), returned with the deploy profile.

authorize and token are public so an unauthenticated CLI can start and finish the flow. grant needs the browser session and is same-origin only. Token creation must be enabled for the exchange to succeed. The profile that comes back is already in the adapter’s config shape, because the CLI merges it into spec.deploy.config unchanged: the endpoint, the workspace, and every Ready llm Provider as a {name, ref, role} binding with one named default. It carries no token, which the CLI stores separately, and no skills, which stay opt-in. The precise CLI command that drives the flow lives in the promptarena deploy tooling. See Set up the deploy config without the export for the steps.

The export produces the contents of spec.deploy.config, the mapping the promptarena-deploy-omnia adapter consumes, with no config: key of its own, because promptarena deploy config import deep-merges the file into that mapping. It holds the endpoint, workspace and token, plus providers bindings (alias, Provider ref, role) and skills entries that each name a SkillSource as source. For the shape and a worked example, see Set up the deploy config without the export. The deploy-intent API that the config feeds is documented in the Deploy-intent contract.

The adapter does not write CRDs. It sends one versioned DeployIntent to POST /api/workspaces/{name}/deployments, authenticated with the workspace-scoped token. management-api forwards the document opaquely to the operator’s deploy-intent API with an identity token carrying the caller. The operator validates the intent and creates the Kubernetes objects:

  • a PromptPack and its content ConfigMap, holding the compiled pack (see the PromptPack reference). Skills attach here: the intent’s pack.skills becomes the PromptPack’s spec.skills, each entry naming a SkillSource.
  • an AgentRuntime whose promptPackRef points at the pack and whose spec.providers carries the bindings, with the default binding as the primary LLM.

The AgentRuntime exposes the runtime through spec.facades[], a list of facade entries that each serve one protocol. A pack deployed for browser chat lands as a single websocket facade on port 8080 (the default). An agent can expose more surfaces, for example an additional a2a entry, by listing more facade entries. The operator then reconciles the AgentRuntime into a Deployment and Service, and the pack is live.

The adapter config has no field for a cloud-specific ServiceAccount name, so an AgentRuntime created this way carries none of its own. It falls back to the workspace’s spec.runtime.serviceAccountName default, so a keyless provider (auth.type: workloadIdentity) still authenticates as long as the workspace configured that default.

The alternative is for each client to author AgentRuntime bodies itself. The /agents and /promptpacks routes management-api serves work that way: they apply the caller’s spec to the Kubernetes API unchanged, and the only validation is the CRD’s own OpenAPI and CEL schema. The dashboard’s deploy wizard uses them. That keeps management-api free of schema knowledge, but every client then owns a copy of the AgentRuntime schema, and a client that falls behind the CRD is rejected by the apiserver (for example 422 spec.facades: Required value).

The DeployIntent moves that ownership to the operator. The adapter sends a description of the deploy, the operator maps it onto whatever the installed CRDs look like, and an adapter built against an older cluster keeps working. The contract is versioned by apiVersion: the operator serves deploy.omnia.altairalabs.ai/v1 and answers anything else with 400. The deploy profile lists the accepted versions in supportedDeployIntentVersions, so an adapter can check before it posts. The cost is a second schema to maintain, in the operator’s internal/api/deploy package, which has to grow with the CRDs.

A breaking AgentRuntime change still has to ship with the in-app deploy wizard, because the wizard authors AgentRuntime bodies directly.

Upgrade the CRDs first, as their own release. They ship in a separate chart, omnia-crds, so helm upgrade omnia-crds can apply schema changes. The app chart’s own helm upgrade never touches them:

Terminal window
helm upgrade omnia-crds oci://ghcr.io/altairalabs/charts/omnia-crds \
--version <version> -n omnia-system

Skipping this leaves a new operator binary reconciling objects against an old CRD schema. The app chart’s preflight refuses to install or upgrade against older CRDs, so a normal helm upgrade omnia cannot skip the step unless you set crdPreflight.enabled=false.