Open Workflow GraphPre-release
Specification

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.

View as markdownMachine-readable source for agents and scripted implementers

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:

{
  "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:

ElementPurpose
profile_id, profile_name, versionIdentity
conformanceThe level this profile targets — see below
executor_typesWhich core executor types the profile covers
toolsNamed 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:

{
  "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:

DomainExecutor typesWhat it covers
Generative AIcomfyui_graph, comfyui_node, agent, saas_apiGenerative pipelines, autonomous agents, model-backed services
VFX and postlocal_app, human, render_farmDesktop applications and human review gates
Physical productionhuman, local_appOn-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. 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.

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:

{
  "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[]:

{
  "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 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 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, 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.

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.