Skip to content

Write a deploy adapter

Deploy a pack from your own tooling (a CI step, a plugin, a CLI in another repository) by posting a deploy-intent document to the dashboard.

To publish a pack with the existing promptarena-deploy-omnia adapter, follow Deploy a PromptPack to Omnia. For why the API takes an intent instead of CRDs, see The PromptPack Deploy Program.

  • A workspace, and an omnia_sk_ API key with the editor role on it. See API keys.
  • The dashboard’s external URL, as OMNIA_ENDPOINT.
  • A pack to deploy: the pack.json content, a name and a version.

The commands below use OMNIA_TOKEN for the key, OMNIA_ENDPOINT for the URL and WORKSPACE for the workspace name.

Fetch the profile before every deploy. It needs the viewer role.

Terminal window
curl -sS \
-H "Authorization: Bearer $OMNIA_TOKEN" \
"$OMNIA_ENDPOINT/api/workspaces/$WORKSPACE/deploy-profile"

The response looks like this (illustrative values):

{
"api_endpoint": "https://omnia.example.com",
"workspace": "acme",
"providers": [
{ "name": "openai-gpt4", "role": "llm", "type": "openai", "model": "gpt-4o" }
],
"skills": [
{ "name": "runbooks", "type": "git" }
],
"supportedDeployIntentVersions": ["deploy.omnia.altairalabs.ai/v1"]
}

Use it as follows:

  1. Check that supportedDeployIntentVersions contains the apiVersion you send. An unknown apiVersion returns 400.
  2. Set each provider binding’s ref to a providers[].name value. These are Provider names. A deploy never creates a Provider. The profile lists only Ready Providers with the llm role.
  3. Use skills[].name for any pack skills you reference.

The profile never contains a secret.

A minimal single-agent deploy:

{
"apiVersion": "deploy.omnia.altairalabs.ai/v1",
"pack": {
"name": "support-pack",
"version": "1.4.0",
"content": "{\"prompts\": [...]}"
},
"agents": [
{
"name": "support-bot",
"promptName": "triage",
"providers": [
{ "name": "default", "ref": "openai-gpt4", "role": "llm" }
]
}
]
}

pack.content is the raw pack.json as a string. The operator stores it as opaque bytes in a ConfigMap.

For a pack with three prompts and an agent per prompt, send three entries in agents[], each with its own promptName and its own DNS-safe name.

Every field is listed in api/openapi/openapi.yaml.

To use a registry that already exists, reference it:

{ "tools": { "ref": "shared-http-tools" } }

To have the deploy create a registry, send handlers:

{
"tools": {
"handlers": [
{ "name": "search", "type": "http", "httpConfig": { "url": "https://..." } }
]
}
}

ref and handlers are mutually exclusive, and sending both returns 400. Set useTools: true on each agent that should get the registry.

If your handlers need secrets, send the values, keyed by the environment variable names the pack references:

{
"credentials": {
"data": { "GITHUB_TOKEN": "ghp_..." }
}
}

The operator writes a Secret it owns and repoints your handlers at it. credentials requires tools.handlers. With tools.ref the request returns 400.

Read the credentials section of the reference before shipping this. A registry that already exists keeps its old Secret references.

Save the document as intent.json and post it:

Terminal window
curl -sS -X POST \
-H "Authorization: Bearer $OMNIA_TOKEN" \
-H "Content-Type: application/json" \
--data @intent.json \
"$OMNIA_ENDPOINT/api/workspaces/$WORKSPACE/deployments"

A 200 and a 207 both carry a results array. A 207 means some resources failed while others applied. Read the body before you read the status code:

body = post_deployment(intent).json() # your HTTP call; 200 or 207
for r in body["results"]:
if r["action"] == "failed":
log.error("%s/%s: %s", r["kind"], r["name"], r["error"])
elif r.get("error"):
# Applied, with a caveat worth showing to the user.
log.warning("%s/%s: %s", r["kind"], r["name"], r["error"])
if not body["succeeded"]:
raise DeployFailed(...)
  • succeeded is the verdict. A result can carry an error and still have applied, for example an ownership warning or credentials skipped on an existing registry. Do not roll back on error alone.
  • action: "unchanged" is a success. Packs and their content are create-only, so redeploying the same version changes nothing. Publish a new version to change content.
  • Show url only when it is present. The dashboard sets it on AgentRuntime results as a console link. When it is absent, show no link.

A 409 or 503 also carries results. A 409 means the deploy refused to take over an existing object, such as an agent with create: true whose name is taken. A 503 includes a Retry-After header. See Reading the response.

Posting the same intent again is safe. A redeploy follows these rules:

  • Packs and their content are never rewritten. Bump pack.version to publish new content.
  • AgentRuntimes are field-merged. Fields the intent cannot express keep the values whoever set them left, such as node selectors, tolerations, pod overrides, service group, facade ports and async settings.
  • The intent owns the fields it can express. Omitting memory, evals or externalAuth turns them off. Set preserveUnset: true on the agent to keep the live values instead.
  • For agents under a version-triggered rollout, read Rollout interaction. Whether your deploy takes the pack pin back depends on whether it leaves the agent in trigger mode.

Check that every entry in results has an action of created, updated or unchanged and that succeeded is true. Then check the agent:

Terminal window
kubectl get agentruntime support-bot -n <workspace-namespace>

The Phase column reaches Running once the agent is serving.

The body names the failing rule, for example an unsupported apiVersion, a missing pack.name, an empty providers list, or ref together with handlers. See Rules that live only in validation.

The agent is Failed with reason ProviderNotFound

Section titled “The agent is Failed with reason ProviderNotFound”

The Provider named by ref does not exist. Pick a name from the deploy profile.

The Provider named by ref exists but is not Ready. Check its status with kubectl get provider.