# PromptPack CRD

A PromptPack is a versioned collection of prompts for AI agents, sourced from a ConfigMap containing a `pack.json` file in the [PromptPack format](https://promptpack.org/docs/spec/schema-reference). The controller validates `pack.json` against the [PromptPack JSON Schema](https://promptpack.org/schema/latest/promptpack.schema.json) before activating it, using the copy built into the operator unless the Helm value `operator.promptPackSchema.source` is `network`.

A live PromptPack (`source.type: live`) points at an authored Arena project instead. A [PromptPackSource](/reference/core/promptpacksource/) of type `project` creates it, and the runtime compiles the project's current source when a session opens.

## API version

```yaml
apiVersion: omnia.altairalabs.ai/v1alpha1
kind: PromptPack
```

**Short name:** `pp` (e.g. `kubectl get pp`).

## Spec fields

A published PromptPack version is an immutable release: the **whole spec** is
frozen after create (CEL: `self == oldSelf`). To change anything, publish a new
version.

### `packName`

Logical pack identity. Required. Versions of the same pack share a
`packName`; an AgentRuntime's `spec.promptPackRef.name` references it.

```yaml
spec:
  packName: customer-service
```

### `version`

Semantic version of this pack. Required, and must match
`^(\d+)\.(\d+)\.(\d+)(-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?(\+[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*)?$`
(e.g. `1.0.0`, `2.1.0-beta.1`), or the literal `live`. A leading `v` is
rejected. The deploy API and PromptPackSource strip one from the version they
are given. `live` is required when `source.type` is `live` and rejected
otherwise (CEL-validated).

An AgentRuntime pin matches `version` as an exact string, including build
metadata, so `1.0.0+gAAAA` and `1.0.0+gBBBB` are two different packs. For
channel ordering, though, semver gives the two versions equal precedence, so
the one created later serves the channel. The other becomes `Superseded`, with
`DuplicateVersion` True. To avoid the tie, give each publish a distinct
`MAJOR.MINOR.PATCH` or prerelease.

```yaml
spec:
  version: "2.0.0"
```

### `source`

Source of the compiled PromptPack content.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `source.type` | `configmap` \| `live` | Yes | Source type |
| `source.configMapRef.name` | string | When `type` is `configmap` | Name of the ConfigMap |
| `source.live.sourceRef` | string | When `type` is `live` | Name of the PromptPackSource that created the pack, in the same namespace |
| `source.live.project.id` | string | When `type` is `live` | ID of the Arena project whose source is the content |
| `source.live.project.user` | string | When `type` is `live` | Pseudonym of the user whose working clone of the project (`arena/workspaces/<id>/<user>`) the agent mounts and compiles. A project has no shared copy, so a live pack always runs one user's clone. |

`source.live` is required for type `live`, where `configMapRef` is rejected,
and is rejected for type `configmap` (CEL-validated). The ConfigMap must
contain a `pack.json` key with valid PromptPack JSON content.

A live pack is dev-only. The controller does not resolve the project, validate a
schema, compute `contentDigest` or place the pack on a channel: it sets
`SourceValid` True with reason `LiveSource` and the phase to `Active`. An
AgentRuntime may reference it only without `version` or `track`, and never from
a rollout (see
[AgentRuntime `promptPackRef`](/reference/core/agentruntime/#promptpackref)).

```yaml
spec:
  source:
    type: configmap
    configMapRef:
      name: my-prompts  # ConfigMap must have pack.json key
```

### `skills`

Skills to expose to agents using this pack. Every entry goes through a
[SkillSource](/reference/core/skillsource/); inline skill content is not
accepted.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `source` | string | Yes | Name of a SkillSource in the pack's own namespace |
| `include` | string[] | No | Keep only skills whose `SKILL.md` frontmatter `name` matches an entry. Empty keeps every skill the source synced |
| `mountAs` | string | No | Group name the skills are exposed under (pattern `^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`). Defaults to the basename of the source's `status.artifact.contentPath`, which is the synced content version |

### `skillsConfig`

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `maxActive` | int32 (min 1) | Unlimited if omitted | Cap on concurrently active skills |
| `selector` | `model-driven` \| `tag` | `model-driven` | Which skills the model is offered in the Phase-1 index. `model-driven` offers all of them and lets the model activate what it needs. `tag` offers only skills whose `SKILL.md` metadata `tags` (comma-separated) shares an entry with `tags` |
| `tags` | []string | — | Tags the `tag` selector matches. Required when `selector` is `tag`; ignored otherwise. A skill with no `tags` metadata is never offered by the `tag` selector |

## Status fields

### `phase`

Current phase of the PromptPack.

| Value | Description |
|-------|-------------|
| `Pending` | Not validated |
| `Active` | Valid, and the highest version of its `packName` on the stable or the prerelease channel |
| `Superseded` | A newer validated version has been published on its channel, or a sibling of equal semver precedence was created later (see `DuplicateVersion`). A superseded version still serves any AgentRuntime pinned to it |
| `Failed` | Source or schema validation failed |

### `activeVersion`

The pack's `spec.version` while it is `Active`; cleared when it becomes
`Superseded`.

### `contentDigest`

`sha256:<hex>` of the resolved `pack.json`, recomputed every reconcile. The
operator stamps it onto agent pods, which record it on each session, so a stored
result names the content that produced it.

### `publishedDigest`

`sha256:<hex>` of `pack.json` when the operator first saw this version. Set
once and never changed. `ContentDrift` compares `contentDigest` against it.

### `contentMetadata`

The pack's own `metadata` block, lifted out of `pack.json` verbatim.

### `governance`

The typed reading of `metadata.governance` (see
[below](#metadatagovernance)), with a synthesised `declared` list naming the
fields the pack actually stated. `extensions` is not carried. Absent when the
pack declares no governance block.

### `skillsManifestVersion`

Hash of the resolved skill manifest — identifies the skill content in force.
Empty when the pack declares no skills.

### `lastUpdated`

Timestamp of the last status update.

### `conditions`

| Type | Description |
|------|-------------|
| `Ready` | Roll-up of `SourceValid`, `SchemaValid` and the three skill conditions; inherits the first failing one's reason and message |
| `SourceValid` | ConfigMap exists and contains `pack.json` key. For a live pack, True with reason `LiveSource` when `source.live` is set |
| `SchemaValid` | `pack.json` content conforms to the PromptPack schema |
| `SkillsResolved` | Every `spec.skills[].source` names an existing SkillSource (True with reason `NoSkills` when none are declared) |
| `SkillsValid` | No skill-name collisions across sources after `include` |
| `SkillToolsResolved` | Every resolved skill's `allowed-tools` is declared by the pack |
| `ContentDrift` | **True is the bad state.** True, with a Warning event, while `contentDigest` differs from `publishedDigest`: `pack.json` changed under a published version. Stays True until the content returns to the published digest. The pack keeps serving |
| `Superseded` | **True is the bad state.** True when a newer version has been published on this version's channel, or (reason `DuplicateVersion`) when a sibling of equal semver precedence won the publish-order tiebreak |
| `DuplicateVersion` | **True is the bad state.** Set True, with a Warning event, when a sibling whose version differs only in build metadata (equal semver precedence) was created later and serves the channel instead. Absent unless a tie has occurred; False once it is resolved |
| `AgentsNotified` | Reports how many AgentRuntimes reference this pack (reason `NoAgentsToNotify` when none) |

See [PromptPack validation](/explanation/platform/reconciliation/#validation) for how source and schema validation run, and how failures surface as Kubernetes events.

## ConfigMap format

The referenced ConfigMap must contain a `pack.json` key with a compiled PromptPack following the [PromptPack specification](https://promptpack.org/docs/spec/schema-reference):

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: my-prompts
data:
  pack.json: |
    {
      "id": "customer-service",
      "name": "Customer Service Assistant",
      "version": "1.0.0",
      "template_engine": {
        "version": "v1",
        "syntax": "{{variable}}"
      },
      "prompts": {
        "support": {
          "id": "support",
          "name": "General Support",
          "version": "1.0.0",
          "system_template": "You are a helpful customer service agent for {{company_name}}. Be professional, empathetic, and solution-oriented.",
          "variables": [
            {
              "name": "company_name",
              "type": "string",
              "required": true,
              "description": "Name of the company"
            }
          ],
          "parameters": {
            "temperature": 0.7,
            "max_tokens": 1024
          },
          "validators": {
            "banned_words": ["competitor", "lawsuit"]
          }
        }
      },
      "metadata": {
        "domain": "customer-service",
        "language": "en",
        "tags": ["support", "helpdesk"]
      }
    }
```

### PromptPack structure

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | Yes | Unique identifier (lowercase, hyphens allowed) |
| `name` | string | Yes | Human-readable name |
| `version` | string | Yes | Semantic version (MAJOR.MINOR.PATCH) |
| `template_engine` | object | Yes | Template configuration |
| `prompts` | object | Yes | Map of prompt definitions |
| `metadata` | object | No | Domain, language, tags, and the `governance` block (below) |
| `tools` | object | No | Tool definitions for function calling |
| `fragments` | object | No | Reusable template fragments |

### `metadata.governance`

Records what the agent is for and how far it may act alone
(promptpack-spec RFC 0013). Values are **asserted by the pack's author**:
readers may use them to tighten a policy, never to relax one.

Every field is optional; an omitted field means **undeclared**, not a safe
default. RFC 0013 forbids inferring one from absence.

| Field | Type | Description |
|-------|------|-------------|
| `intended_purpose` | string | What the agent is for |
| `foreseeable_misuse` | string[] | Ways it could be misused |
| `autonomy_level` | string | How far it acts without a human |
| `accountable_owner` | string | Who answers for it |
| `operator_role` | string | Provider or deployer |
| `risk_classification` | string | The author's risk position — a legal judgement, never derived |
| `intended_deployment_contexts` | string[] | Where it is meant to run |
| `capabilities` | string[] | What it can do |
| `approved_environments` | string[] | Where it is approved to run |
| `requires_ai_disclosure` | boolean | Whether a human counterparty must be told this is an AI |
| `vocabularies` | object | Vocabulary URIs the values are drawn from |
| `extensions` | object | Firm-specific additions |

Two of these have runtime behaviour:

- **`requires_ai_disclosure`** is delivered to clients and recorded on each
  session. See
  [Disclose That an Agent Is an AI](/how-to/privacy/disclose-ai-interaction/).
- **The whole block** is exposed to ToolPolicy CEL rules as `governance`, so a
  rule can gate on what an agent declared. `governance.declared` lists the
  fields the pack stated, distinguishing undeclared from empty.

### `evals`

A pack carries eval definitions in a pack-level `evals` array and in each prompt's `evals` array, next to `validators`. A prompt-level eval overrides a pack-level eval with the same `id`. The eval worker runs the pack-level `evals` plus those of the prompt the runtime opens the pack at: `workflow.entry`, then `agents.entry`, then the only prompt, then `default`. Realtime evals run these definitions against live sessions. See [Realtime evals](/explanation/evaluation/realtime-evals/) for how.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | string | Yes | Identifier, unique within its scope |
| `type` | string | Yes | Registered eval type, for example `llm_judge`, `contains`, `regex` or `guardrail_triggered` |
| `trigger` | string | Yes | When the eval fires (below) |
| `params` | object | No | Type-specific parameters |
| `sample_percentage` | number | No | Percentage of turns or sessions to run, for `sample_turns` and `sample_sessions`. Default 5 |
| `enabled` | boolean | No | Default `true` |

| Trigger | Fires |
|---------|-------|
| `every_turn` | After each assistant message |
| `sample_turns` | After a hash-sampled share of assistant messages |
| `on_session_complete` | When the session completes |
| `sample_sessions` | On a hash-sampled share of completed sessions |

`on_conversation_complete` and `on_workflow_step` are valid values that realtime evals do not fire.

| Eval type | Parameters | Cost |
|-----------|------------|------|
| `llm_judge` | `criteria` (required), `rubric`, `model`, `provider` (name of a `spec.providers` entry) | One LLM call |
| `contains` | `patterns` (all must appear, case-insensitive). `content_includes` is an alias | None |
| `regex` | `pattern`, `expect_match` | None |
| `guardrail_triggered` | `validator`, `should_trigger` | None |

For the complete specification, see [promptpack.org](https://promptpack.org/docs/spec/schema-reference).

## Example

```yaml
apiVersion: omnia.altairalabs.ai/v1alpha1
kind: PromptPack
metadata:
  name: customer-service
  namespace: agents
spec:
  packName: customer-service
  version: "2.0.0"
  source:
    type: configmap
    configMapRef:
      name: cs-prompts-v2
```

Status after deployment:

```yaml
status:
  phase: Active
  activeVersion: "2.0.0"
  conditions:
    - type: SourceValid
      status: "True"
      reason: SourceValid
      message: "Source configuration is valid"
    - type: SchemaValid
      status: "True"
      reason: SchemaValid
      message: "pack.json content is valid"
    - type: AgentsNotified
      status: "True"
      reason: AgentsNotified
      message: "Notified 3 AgentRuntime(s)"
```

See [Author and compile a PromptPack](/how-to/agents/author-promptpacks/) for
writing PromptPacks in YAML and compiling with `packc`.