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-profileMUST contain at least one non-whitespace character.sourceDescription,openapiOperationId, andopenapiOperationRefMUST 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_profileis 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 tox-*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.