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 a source 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, sourceOperationId, sourceOperationRef, 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 source 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/API/event calls use sourceDescription plus sourceOperationId or sourceOperationRef. OpenAPI sources may also use legacy 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 native source-bound:

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

operations:
  # Shape 1: OpenAPI-bound with the legacy selector
  - 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.get_weather.outputs.temp_f }}°F in Los Angeles"
          body:    $steps.get_weather.outputs.summary
    dependsOn: [get_weather]
    outputs:
      raw: $response.body.raw

  # Shape 2: source-bound with the generic selector
  - operationId: send_report
    sourceDescription: gmail_api
    sourceOperationId: gmail_users_messages_send
    dependsOn: [build_email]
    request:
      body:
        userId: me
        raw:    $steps.build_email.outputs.raw

workflows:
  - workflowId: main
    type: sequence
    steps:
      - stepId: get_weather
        operationRef: get_weather
      - stepId: build_email
        operationRef: build_email
      - stepId: send_report
        operationRef: send_report

Weather and Gmail stay source-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 →