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.
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)
}
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 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.
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 keying scheme is implementation-defined. UWS 1.0 does not standardize a serialized record store.
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, index, batch number, position in batch |
| 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
}
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.