# Profiles

> How profiles declare the executors, tool vocabularies, and registry extensions a workflow may use, keeping domain and vendor detail out of the core specification.

The core specification deliberately says nothing about what tools exist. `task.tool` is a free string, and `billing.tier` and `billing.unit` carry no vocabulary. Profiles are where concrete detail lives, so the core can stay stable while domains and vendors evolve at their own pace.

A document declares the profiles it uses:

```json
{
  "owg_version": "0.92",
  "id": "episodic-dailies",
  "profiles": ["core", "cloud-services"]
}
```

`core` is implicit and may be listed for clarity. Every other entry names a profile whose vocabulary the document draws on.

## What a profile declares

A profile is a registry, published separately from the core schema. It declares:

| Element | Purpose |
|---|---|
| `profile_id`, `profile_name`, `version` | Identity |
| `conformance` | The level this profile targets — see below |
| `executor_types` | Which core executor types the profile covers |
| `tools` | Named tools, each with its inputs, outputs, and billing shape |

Each tool entry names its required and optional inputs, what it produces, and how it bills:

```json
{
  "profile_id": "cloud-services",
  "profile_name": "Cloud Media Services (illustrative)",
  "version": "1.0",
  "conformance": "L2",
  "executor_types": ["service"],
  "tools": {
    "transcode": {
      "description": "Transcode a source file to a delivery format",
      "executor_type": "service",
      "inputs": {
        "required": ["source_url", "output_format"],
        "properties": {
          "source_url":    { "type": "string" },
          "output_format": { "type": "string" },
          "resolution":    { "type": "string" }
        }
      },
      "outputs": { "produces": ["output_key", "content_type",
                                "duration_seconds", "file_size_bytes"] },
      "billing": { "tier": "standard", "unit": "per_job" }
    }
  }
}
```

The profile above is illustrative — it shows the shape, not a published vocabulary.

## Profile kinds

Profiles fall into two kinds, and the distinction matters for who should write them.

**Domain profiles** describe a category of work rather than a product, and are the natural candidates for community ownership:

| Domain | Executor types | What it covers |
|---|---|---|
| Generative AI | `comfyui_graph`, `comfyui_node`, `agent`, `saas_api` | Generative pipelines, autonomous agents, model-backed services |
| VFX and post | `local_app`, `human`, `render_farm` | Desktop applications and human review gates |
| Physical production | `human`, `local_app` | On-set capture and the ingest boundary |

**Vendor profiles** describe one provider's tools. A vendor publishes and versions its own; the specification neither blesses nor enumerates them.

### Two constraints worth knowing

Both come from domain profiles, and both affect how you author a document:

- **Generative AI: `operated_by` is required** on any participant acting as an `agent` executor. An autonomous agent with no accountable principal is not usable under that profile — see [Participants and authority](/participants-and-authority#agent-authority). The same profile marks content credentials on generated assets as **recommended**.
- **Physical production: provenance defaults to `asserted`.** Offline and physical steps are assertions, not attestations — a manually logged camera card is somebody's word. The **ingest station is the boundary** where physical provenance becomes digital, and where an assertion can be upgraded to an attestation by a signed checksum. This is the origin of the [`assurance` vocabulary](/relationships#the-three-assurance-levels).

### Dispatch versus execution

One semantic that catches people out, from the VFX and post domain: for `local_app` executors, **OWG dispatches and awaits status — it does not drive the application in-process.** Status arrives in the graph by ingestion from whatever system tracks that work.

So a task with a `local_app` executor is a request and a record, not a remote-control instruction. Model it accordingly.

## Registry extension profiles

Domain and vendor profiles (above) extend what `task.tool` can name. A third kind, added in v0.92, extends the registries themselves. `context`, `asset`, and `relationships[]` each carry an optional `profiles` object, keyed by profile id, reserved for exactly this:

```json
{
  "contexts": [{
    "id": "story-0472",
    "profiles": {
      "broadcast-newsroom": { "lifecycle": { "phase": "BREAKING" } }
    }
  }]
}
```

Core reserves the key and does not interpret what is inside it. A profile declaring a `registry_extensions` object states the shape of its own key on `context`, `asset`, and/or `relationships[]`:

```json
{
  "profile_id": "broadcast-newsroom",
  "registry_extensions": {
    "context": {
      "type": "object",
      "properties": { "lifecycle": { "type": "object" } }
    }
  }
}
```

**Why not just widen core?** The same reason a billing tier or a device type does not belong in core: a field meaningful to one domain and meaningless to every other implementer is domain vocabulary, not a shared primitive. `context.work_type: "factual-news"` and `asset.state: "growing"` earned core placement because they generalize past the standard that motivated them, see [Changelog → v0.92](/changelog#v092) for the test applied to a concrete case. Story lifecycle phases, evidential sourcing tiers, and compliance-gate status did not pass that test, which is why they live in the `broadcast-newsroom` profile instead.

**Namespacing is what makes two profiles safe on one document.** Nothing stops a second profile from also wanting something phase-and-priority-shaped on `context` — a live-sports profile, say, with its own notion of a running clock. Because each profile owns only its own key under `.profiles`, two such profiles compose on the same context with no collision, by construction rather than by convention.

**Not yet validator-enforced.** A document's `context[].profiles.<id>` validates today as "any object" under core, exactly like `metadata`. A profile's `registry_extensions` schema documents the intended shape; nothing currently loads and checks it automatically at validation time. Treat a document using this mechanism as L1-conformant only until that lands, this is new spec surface, not yet a promise the tooling keeps.

## Conformance levels

Three levels, each testable given the right inputs.

### L1 — the document is valid

All four [validation passes](/validation) succeed. Testable from the document alone.

### L2 — the document is executable on its profiles

Given the document **and** its declared profiles resolved, all of the following hold for every task:

1. If the task has a `tool`, some active profile declares it.
2. The declaring profile lists the task's `executor.type` in its `executor_types`.
3. Every input the profile marks `required` is present in the task's `inputs`.
4. Every input present satisfies the profile's declared type and enum constraints.
5. Every key in the task's `outputs` appears in the profile's `produces` list for that tool, unless it is a `metadata` or `x-` key.

A task with no `tool` is L2-conformant by default — it names no profile contract to satisfy.

### L3 — the run captured full provenance

Given a [run document](/runs), for every attempt with `status: "succeeded"`:

1. `performed_by` is present and resolves to a participant in the definition.
2. `ran_on` is present and resolves to an infrastructure entry.
3. Every asset in the definition's `produced[]` for that task appears in the attempt's `produced`.
4. Every produced asset carries an `identity` and an `assurance`.
5. Any participant acting as an `agent` has an `operated_by` chain terminating at a `human` or `organization`.

L3 is a property of an execution, not of a definition. A definition cannot be L3-conformant; only a run can.

> **State which level you mean.** "OWG-conformant" alone is ambiguous — L1 is about a file, L2 about a file plus its profiles, L3 about an execution. See [Versioning and stability](/versioning-and-stability#conformance).

## Practical guidance

- **Always declare `profiles`.** It states which vocabulary a document draws on, and it is what an L2 conformance check reads.
- **Resolve profiles at validation time if you want L2.** The core schema treats `tool` as a free string by design — checking a tool name against its profile requires the profile, so that check belongs to whatever loads them.
- **Do not put domain vocabulary in the core.** If you find yourself wanting a new core enum value for a tool, a billing tier, or a device type, that belongs in a profile. The core stays small on purpose.
- **Version profiles independently.** A profile can iterate without a core release, which is the entire reason the split exists.
