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-profileMUST contain at least one non-whitespace character.sourceDescription,sourceOperationId,sourceOperationRef,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 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_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.