Skip to content

Feature 8: Extension Profiles

Execution Model | Next: Validation →


Not every step in a workflow is an HTTP call. Formatting a message, invoking a function, running a local command, calling a language model, reading a file, or executing a SQL query are common needs. UWS keeps these out of the core and uses extension profiles instead.

Extension-Owned Operations

An operation without an OpenAPI binding is extension-owned. It MUST include x-uws-operation-profile naming the profile that can execute it:

  • x-uws-operation-profile MUST contain at least one non-whitespace character.
  • sourceDescription, openapiOperationId, and openapiOperationRef MUST NOT be set.
  • Additional x-* fields carry profile-specific configuration and are not interpreted by UWS core.

The validator accepts extension-owned operations as intentionally runtime-owned — it does not flag the absent OpenAPI binding as an error.

Public Runtime Supplement

UWS also publishes the optional uws.runtime.1.0 supplement for common non-HTTP runtime selectors. It defines one UWS-owned operation extension, x-uws-runtime, whose payload is intentionally small:

Field Meaning
type REQUIRED non-HTTP runtime selector.
command Command text for command-like runtimes.
workingDir Working directory for command-like runtimes.
function Function name for function runtimes.
workflow Nested workflow reference.
arguments Runtime-owned argument values.

Valid type values are ssh, cmd, fnct, fileio, sql, s3, smtp, dns, ldaps, scp, sftp, and llm. HTTP is intentionally absent: HTTP/OpenAPI calls use sourceDescription plus openapiOperationId or openapiOperationRef.

The supplement does not standardize credentials, clients, hosts, provider selection, process management, or result schemas. Those remain runtime-private configuration or product-owned extension fields.

Example 1: Function Call

Invoke a local or serverless function within the workflow:

operationId: format_report
x-uws-operation-profile: uws.runtime.1.0
x-uws-runtime:
  type: fnct
  function: render_markdown
  arguments:
    - template: daily_report
      data:
        summary:    $steps.get_weather.outputs.summary
        date:       $variables.report_date
        recipient:  $steps.get_user.outputs.email
dependsOn: [get_weather, get_user]
outputs:
  html: $response.body.rendered

The bound runtime resolves render_markdown locally. UWS core sees only dependsOn, outputs, and that the profile is uws.runtime.1.0.

Example 2: Language Model Call

Run a prompt through an LLM as part of the workflow:

operationId: summarize_feedback
x-uws-operation-profile: uws.runtime.1.0
x-uws-runtime:
  type: llm
  arguments:
    - model: gpt-4o
      prompt: |
        Summarize the following customer feedback in one sentence:
        {{ $steps.fetch_feedback.outputs.text }}
      temperature: 0.3
dependsOn: [fetch_feedback]
outputs:
  summary: $response.body.content

The LLM call is runtime-owned. The rest of the workflow — how fetch_feedback runs, what summary feeds into downstream — is orchestrated by UWS core.

Example 3: SQL Query

Execute a database query as a workflow step:

operationId: load_pending_orders
x-uws-operation-profile: uws.runtime.1.0
x-uws-runtime:
  type: sql
  command: |
    SELECT id, total, customer_id
    FROM orders
    WHERE status = 'pending'
      AND created_at > $variables.cutoff_date
outputs:
  rows:  $response.body.rows
  count: $response.body.count

The database connection, credentials, and driver settings are runtime-private configuration. They are not public UWS runtime metadata.

Example 4: SSH / Shell Command

Run a remote command over SSH:

operationId: deploy_artifact
x-uws-operation-profile: uws.runtime.1.0
x-uws-runtime:
  type: ssh
  command: |
    cd /opt/app && \
    ./deploy.sh {{ $steps.build.outputs.artifact_path }}
dependsOn: [build]
outputs:
  exit_code: $response.body.exitCode
  logs:      $response.body.stdout

The SSH host, identity, timeout, and client behavior are selected by the bound runtime or a product-owned profile. The public x-uws-runtime payload only selects the non-HTTP invocation surface.

Example 5: Mixing Core and Extension Operations

A single document with three operation kinds — OpenAPI-bound, function call, and OpenAPI-bound:

sourceDescriptions:
  - name: weather_api
    url: ./weather.openapi.yaml
    type: openapi
  - name: gmail_api
    url: ./gmail.openapi.yaml
    type: openapi

operations:
  # Shape 1: OpenAPI-bound
  - operationId: get_weather
    sourceDescription: weather_api
    openapiOperationId: getCurrentWeather
    request:
      query:
        q: Los Angeles
    outputs:
      summary: $response.body.summary
      temp_f:  $response.body.main.temp

  # Shape 3: Extension-owned (function call)
  - operationId: build_email
    x-uws-operation-profile: uws.runtime.1.0
    x-uws-runtime:
      type: fnct
      function: mail_raw
      arguments:
        - subject: "Weather: {{ $steps.fetch.outputs.temp_f }}°F in Los Angeles"
          body:    $steps.fetch.outputs.summary
    dependsOn: [get_weather]
    outputs:
      raw: $response.body.raw

  # Shape 1: OpenAPI-bound
  - operationId: send_report
    sourceDescription: gmail_api
    openapiOperationId: sendMessage
    dependsOn: [build_email]
    request:
      body:
        userId: me
        raw:    $steps.compose.outputs.raw

Weather and Gmail stay fully OpenAPI-bound. Email formatting is runtime-owned. The document validates before any execution begins.

Specification Extensions on Non-Operation Objects

x-* fields are not limited to extension-owned operations. Any UWS object can carry them as metadata:

{
  "workflowId": "main",
  "type": "sequence",
  "x-owner": "payments-team",
  "x-sla-ms": 5000,
  "steps": [...]
}

Conforming tooling MUST preserve these fields on round-trip and MUST NOT interpret or modify them.

Reserved Prefix

Prefix Usage
x-uws- Reserved — UWS-owned fields and supplements, including x-uws-operation-profile and x-uws-runtime.
x-udon- udon runtime implementation
x-<vendor>- Vendor-specific
x-<product>- Product-specific

Third-party tooling MUST NOT introduce fields under x-uws-. All other x-* prefixes are governed entirely by the implementation that defines them.

What Happens When a Profile Is Unknown

The UWS validator accepts extension-owned operations regardless of whether the named profile is supported by the current runtime. Profile resolution is a runtime concern, not a schema concern:

operationId: do_something
x-uws-operation-profile: my_custom_profile
x-my-custom-profile:
  action: whatever
  • Validator: accepts the document — my_custom_profile is a valid non-empty string. ✓
  • Runtime: if the runtime does not implement my_custom_profile, it returns an error at execution time.
  • HCL conversion: preserves extension fields inside extensions { ... } blocks and flattens them back to x-* fields when converting to JSON or YAML.

From The Big Fixture

The large fixture includes every runtime supplement selector. This excerpt shows one llm operation with all supplement fields present:

operation "run_llm_primary" {
  dependsOn = ["fetch_ticket", "load_customer"]
  outputs = {
    audit  = "$response.body.auditId"
    result = "$response.body.result"
  }
  extensions {
    x-uws-operation-profile = "uws.runtime.1.0"
    x-uws-runtime {
      type       = "llm"
      command    = "llm task primary"
      function   = "summarize_incident_primary"
      workflow   = "runtime/llm-primary.uws.hcl"
      workingDir = "/srv/incident-response/llm"
      arguments = [{ incidentId = "$inputs.incidentId" }]
    }
  }
}

Full context: testdata/big/big.hcl.


Execution Model | Next: Validation →