Feature 7: Execution Model
← Success Criteria and Actions | Next: Extension Profiles →
UWS defines a real orchestrator/runtime split. This is what distinguishes UWS from a document-only format: the specification defines portable execution semantics, not just document shape. This guide describes current UWS 1.12 behavior, with version gates preserved for older documents.
The Split
UWS core (the Orchestrator) owns:
- Document validation before execution
- Indexing operations, workflows, steps, trigger targets, and
parallelGroupmembers - Dependency execution across operations, workflows, and steps
- Evaluation and enforcement of
dependsOn,when,forEach,items,batchSize,wait - Execution of all six structural constructs
- Processing of success and failure actions (
retry,goto,end) - Output resolution and structural result shaping
- Trigger route resolution and routed execution
The bound runtime owns:
- Leaf execution of a single Operation (one HTTP call, or one extension-owned action)
- Expression evaluation against the current execution context
- Resolving the item list for
forEachandloop
This line is what allows UWS to define portable orchestration semantics without standardizing HTTP clients, secret handling, storage engines, or product-specific runtime hooks.
The Runtime Interface
Three methods are all the orchestrator needs from a concrete runtime:
type Runtime interface {
ExecuteLeaf(ctx context.Context, op *Operation) error
EvaluateExpression(ctx context.Context, expr string) (any, error)
ResolveItems(ctx context.Context, itemsExpr string) ([]any, error)
}
A runtime may also implement the additive uws1.RuntimeWithResult interface:
type RuntimeWithResult interface {
ExecuteLeafWithResult(ctx context.Context, op *Operation) (any, error)
}
When present, the orchestrator stores the returned JSON-compatible leaf result
on the operation execution record before evaluating success criteria and
outputs. This makes $response available to those expressions. For a step
that directly invokes an operation, the same result is copied to the step
record so step outputs can read it. Existing runtimes that implement only
Runtime remain valid and continue using ExecuteLeaf; the core receives no
return value from that method.
The mockruntime package implements both interfaces. Its
response record shape follows the operation profile and is not a UWS wire
contract.
Everything above this line — dependency resolution, parallel scheduling, retry counting, switch evaluation, loop batching — is owned by UWS core.
Example 1: Minimal Runtime Implementation
A mock runtime that records calls, useful for testing:
type MockRuntime struct {
Calls []string
Values map[string]any
}
func (r *MockRuntime) ExecuteLeaf(ctx context.Context, op *uws1.Operation) error {
r.Calls = append(r.Calls, op.OperationID)
return nil
}
func (r *MockRuntime) EvaluateExpression(ctx context.Context, expr string) (any, error) {
if v, ok := r.Values[expr]; ok {
return v, nil
}
return nil, nil
}
func (r *MockRuntime) ResolveItems(ctx context.Context, itemsExpr string) ([]any, error) {
v, err := r.EvaluateExpression(ctx, itemsExpr)
if v == nil {
return nil, err
}
items, ok := v.([]any)
if !ok {
return nil, fmt.Errorf("items expression did not resolve to array")
}
return items, nil
}
Example 2: Binding and Executing a Document
package main
import (
"context"
"log"
"os"
"github.com/OpenUdon/uws/convert"
"github.com/OpenUdon/uws/uws1"
)
func main() {
data, err := os.ReadFile("workflow.uws.yaml")
if err != nil {
log.Fatal(err)
}
var doc uws1.Document
if err := convert.UnmarshalYAML(data, &doc); err != nil {
log.Fatal(err)
}
rt := &MyHTTPRuntime{ /* ... */ }
doc.SetRuntime(rt)
ctx := context.Background()
if err := doc.Execute(ctx); err != nil {
log.Fatalf("execution failed: %v", err)
}
// Inspect what ran
for key, record := range doc.ExecutionRecords() {
log.Printf("%s: status=%s", key, record.Status)
}
}
doc.Execute(ctx) runs three checks automatically before handing off to the orchestrator: Validate(), ValidateExecutable(), and ValidateExecutionEntrypoint().
UWS 1.9.1 and later content-trust analysis is intentionally not a fourth automatic check. Applications invoke contenttrust.Analyze explicitly before or after validation when they want advisory provenance findings. The analyzer never changes execution records, runtime inputs, orchestration, authorization, or executor results. See the UWS 1.12 contract and content-trust guide.
Example 3: Trigger Dispatch
Accepting an inbound event and routing it through UWS core:
// Receive a webhook payload (e.g. from an HTTP handler)
func handleWebhook(w http.ResponseWriter, r *http.Request) {
var payload map[string]any
json.NewDecoder(r.Body).Decode(&payload)
// Determine which output fired based on payload content
outputIndex := 0 // "created"
if payload["event"] == "updated" {
outputIndex = 1
}
err := doc.DispatchTrigger(r.Context(), "order_events", outputIndex, payload)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.WriteHeader(204)
}
UWS core resolves the route, validates the targets, and executes them through the standard orchestrator. The payload becomes $trigger inside the routed workflows.
Example 4: Inspecting Execution Records
After execution completes, records identify every step that ran:
doc.SetRuntime(rt)
if err := doc.Execute(ctx); err != nil {
log.Printf("execution error: %v", err)
}
records := doc.ExecutionRecords()
for key, record := range records {
fmt.Printf("%-40s status=%-10s kind=%s\n",
key, record.Status, record.Kind)
}
Example output:
op:list_pets status=success kind=operation
op:send_report status=success kind=operation
wf:main status=success kind=workflow
The keys shown above are from this Go implementation only. UWS 1.11 established that record keys, persistence, and serialized history are implementation-defined, while defining portable loop, forEach, and merge result payload shapes; UWS 1.12 preserves those rules. See Structural Results and the UWS 1.12 execution contract.
Execution Context Available to the Runtime
During each ExecuteLeaf call, the runtime can inspect:
| Context | What it contains |
|---|---|
| Trigger context | Active trigger ID, emitted output label, and payload |
| Iteration context | Current item value, zero-based index, batch, zero-based batch index, and position in batch; $batchIndex is available only in a UWS 1.11-or-later loop |
| Input bindings | Current $inputs values, including step-local bindings when a step supplies inputs |
| Current-execution context | The operation or step being run right now |
| Execution records snapshot | All records accumulated so far |
These are passed through context.Context and are never serialized into the UWS wire format.
Validation Before Execution
The three pre-execution checks run automatically inside doc.Execute(ctx):
if err := d.Validate(); err != nil {
return err // UWS document validity
}
if err := d.ValidateExecutable(); err != nil {
return err // entry workflow, runtime binding
}
if err := d.ValidateExecutionEntrypoint(); err != nil {
return err // entrypoint requirements
}
UWS 1.12 adds declarative operation effects (read, write, or unknown;
omission means unknown) and pending step contracts for work that has not
been bound to an operation. Pending input and output declarations are recursive
object-root schemas, not runtime values. Validate() accepts a valid pending
contract, while executable validation rejects any pending step before calling
the runtime, even in a branch execution would not select. An effect label is
descriptive; it does not authorize a live call. The pending-only example shows recursive field sets. See the UWS 1.12 contract.
The AI Agent Pipeline
For AI agent use cases, UWS acts as the contract between intent extraction and execution:
natural language → structured intent → UWS document → Validate() → optional Analyze() → Execute()
// Agent proposes a UWS document
proposedYAML := agent.ProduceWorkflow(userIntent)
var doc uws1.Document
convert.UnmarshalYAML([]byte(proposedYAML), &doc)
// Catch errors before execution
result := doc.ValidateResult()
if !result.Valid() {
// Feed structured errors back to the model for correction
correction := agent.CorrectWorkflow(proposedYAML, result.Errors)
// ... retry
return
}
// Only execute a validated document
doc.SetRuntime(rt)
doc.Execute(ctx)
ValidateResult() returns path-tagged errors that drop straight into a corrective prompt. Two fields, two exact paths — enough for the model to fix in one pass rather than guessing from prose.
From The Big Fixture
The large fixture is built from Go structs, validated, serialized, and round-tripped across JSON, YAML, and HCL:
doc := BuildDocument()
if err := doc.Validate(); err != nil {
return fmt.Errorf("validate source document: %w", err)
}
jsonData, err := convert.MarshalJSONIndent(doc, "", " ")
hclData, err := convert.MarshalHCL(doc)
yamlData, err := convert.MarshalYAML(doc)
if err := verifyRoundTrips(jsonData, hclData, yamlData); err != nil {
return err
}
Full context: testdata/big/main.go.