﻿# FlowMarkup Examples and Patterns

**Version:** 0.9.0
**Date:** 2026-03-28
**Designed by:** Łukasz Nawojczyk
**Copyright:** © 2026 Progralink Łukasz Nawojczyk. All rights reserved.
**License:** Open Web Foundation Agreement 1.0 (OWFa 1.0)

Companion document to the [FlowMarkup Specification](FLOWMARKUP-SPECIFICATION.md). For validation rules, see [Validation Reference](FLOWMARKUP-VALIDATION.md).

---

## Example Flows

The `spec/examples/` directory contains validated flows organized by category.

### Getting Started

| File | Description |
|---|---|
| `ai-comparison.flowmarkup.yaml` | PARALLEL branches with COMPLETE fail policy to collect all results — a good starting point. |
| `order-fulfillment.flowmarkup.yaml` | Full showcase: typed contracts, waitFor approval, saga, carrier race, mail, events. |

### Actions

| File | Description |
|---|---|
| `http-request.flowmarkup.yaml` | Full request action: auth, headers, query params, JSON/form bodies, error handling. |
| `exec-commands.flowmarkup.yaml` | System command execution: args, env, working dir, parseAs, stdin, async. |
| `email-notification.flowmarkup.yaml` | Mail action with HTML/text body, SMTP config, and attachments. |
| `storage-operations.flowmarkup.yaml` | File storage: S3 get/put, SFTP upload, cross-storage transfer, parseAs for JSON, RESOURCES upload. |
| `ssh-commands.flowmarkup.yaml` | Remote SSH command execution: stdout parsing (JSON/TEXT), env vars, multi-server orchestration. |
| `cross-storage-etl.flowmarkup.yaml` | Full ETL pipeline: download CSV from S3, parse, transform in CEL, upload to SFTP, email notification. |
| `host-app-services.flowmarkup.yaml` | Engine-provisioned vs flow-defined services, service metadata, alias remapping. |

### Error Handling

| File | Description |
|---|---|
| `rollback-saga.flowmarkup.yaml` | Transaction groups with rollback handlers, RolledBackError vs RollbackFailedError. |

### Parallelism and DAGs

| File | Description |
|---|---|
| `dag-pipeline.flowmarkup.yaml` | Diamond DAG with `dependsOn` on a PARALLEL group. |
| `ai-comparison.flowmarkup.yaml` | PARALLEL branches with COMPLETE fail policy to collect all results. |

### Streaming

| File | Description |
|---|---|
| `streaming-llm.flowmarkup.yaml` | LLM token streaming with `yield`/`onYield`, caller chooses streaming vs `$yields`. |

### Resilience

| File | Description |
|---|---|
| `circuit-breaker.flowmarkup.yaml` | Circuit breaker patterns: global scope, dynamic names, fallback on CircuitOpenError. |
| `rate-limited-flow.flowmarkup.yaml` | Flow-level and step-level rate limiting with WAIT/REJECT strategies. |
| `paginated-api.flowmarkup.yaml` | Paginated API fetch with while loop and retry. |

### Security and Sub-flows

| File | Description |
|---|---|
| `secure-subflow.flowmarkup.yaml` | Capability restriction patterns on run steps: explicit `cap:` allowlists, `handle:`/`source:`/`cancel` orchestration. |
| `async-orchestration.flowmarkup.yaml` | Async sub-flows with `handle:`, selective `waitFor` with `source:`, cancellation via `cancel:`, and RACE patterns. |
| `secrets-usage.flowmarkup.yaml` | SECRET scope: bearer/basic auth, exec env injection, inline `$secret`, conditional access. |
| `private-git-flows.flowmarkup.yaml` | Git-backed flow references: github://, gitlab://, bitbucket://, HTTPS `.git//`. |

### Events and Triggers

| File | Description |
|---|---|
| `event-signaling.flowmarkup.yaml` | emit/waitFor patterns: parallel signaling, context propagation, cross-workflow correlation. |
| `multi-tier-approval.flowmarkup.yaml` | Amount-based routing with escalation: emit/waitFor condition filter, timeout escalation, multi-stage approval. |
| `triggered-flow.flowmarkup.yaml` | Flow triggers: event-driven, cron, and human-readable schedules. |

### Data and Functions

| File | Description |
|---|---|
| `csv-processing.flowmarkup.yaml` | CSV/TSV processing: inline data, exec stdout, RESOURCES, filtering, encoding, mail attachment. |
| `lock-and-decode.flowmarkup.yaml` | Lock directive, decode/parse CEL functions, repeat, waitUntil. |

### Advanced

| File | Description |
|---|---|
| `order-fulfillment.flowmarkup.yaml` | Full showcase: typed contracts, waitFor approval, saga, carrier race, mail, events. |
| `batch-processing.flowmarkup.yaml` | forEach with per-item error accumulation, post-loop assertion, partial failure reporting. |
| `idempotency-key.flowmarkup.yaml` | `idempotencyKey:` for deduplication, composite key construction, DuplicateInvocationError. |
| `version-migration.flowmarkup.yaml` | `onVersionChange` handler for checkpoint resume after flow content changes. |

---

## Patterns

### Basic Flow Structure

```yaml
flowmarkup:
  title: Hello World
  input:
    name: STRING
  output:
    greeting: TEXT
  requires: {}
  do:
    return: {greeting: "Hello, {{name}}!"}
```

### Input/Output Contract Forms

**Flat form** (preferred):
```yaml
input:
  order_id: STRING                                    # required (no $default)
  priority: {$default: standard}                       # optional — STRING inferred
output:
  result: JSON
```

**Structured output** (when output has optional fields):
```yaml
output:
  required:
    best_result: TEXT
  optional:
    comparison_data: JSON
```

**Single-value output**:
```yaml
output:
  $kind: BINARY
  $type: image/png
do:
- call: {service: renderer, operation: render, result: image}
- return: =image
```

### Error Handling Patterns

**try/catch with map form**:
```yaml
- try:
    do:
      call: {service: api, operation: fetch, result: {data: =RESULT.body}}
    catch:
      TimeoutError:
        log: "Timed out: {{ERROR.MESSAGE}}"
      default:
        log: "Unexpected: {{ERROR.TYPE}}"
```

**Error hierarchy** -- most-specific first to avoid SA-ERR-3:
```yaml
throws:
- NetworkError
- $kind: ConnectionRefusedError
  $parent: NetworkError
catch:
  ConnectionRefusedError:          # child first
    log: Connection refused
  NetworkError:                    # parent catches remaining subtypes
    log: "Network error: {{ERROR.TYPE}}"
```

### Parallel Execution

**PARALLEL with COMPLETE** (wait for all, then compare):
```yaml
- parallel:
    failPolicy: COMPLETE
    fast_api:
      call: {service: fast, operation: query, result: {fast_result: =RESULT.data}}
    slow_api:
      call: {service: slow, operation: query, result: {slow_result: =RESULT.data}}
- set: {best: "=fast_result.score > slow_result.score ? fast_result : slow_result"}
```

**RACE** (first to finish wins):
```yaml
- race:
    timeout: 10s
    primary:
      call: {service: primary, operation: fetch, result: {answer: =RESULT.data}}
    fallback:
    - wait: 3s
    - call: {service: fallback, operation: fetch, result: {answer: =RESULT.data}}
```

### Streaming (yields)

**Producer flow**:
```yaml
flowmarkup:
  title: Token Streamer
  yields:
    $kind: TEXT
  requires: {}
  do:
  - call:
      service: llm
      operation: generate
      params: {prompt: =prompt, stream: true}
      onYield:
        as: token
        do:
          yield: =token
```

**Consumer -- streaming**:
```yaml
- run:
    flow: token-streamer.flowmarkup.yaml
    params: {prompt: =user_prompt}
    onYield: FORWARD               # re-yield to caller
```

**Consumer -- materialized list**:
```yaml
- run:
    flow: token-streamer.flowmarkup.yaml
    params: {prompt: =user_prompt}
    result:
      all_tokens: =$yields         # collected list
```

### Transaction and Rollback (Saga)

When the entire flow is one transaction, use root-level `transaction:` — the `do:` list becomes an implicit group:

```yaml
flowmarkup:
  transaction: true
  onRollbackError: CONTINUE
  do:
  - call:
      service: payment
      operation: charge
      params: {amount: =total}
      result: {tx_id: =RESULT.tx_id}
      rollback:
        call:
          service: payment
          operation: refund
          params: {tx_id: =tx_id}
  - call:
      service: inventory
      operation: reserve
      params: {sku: =item.sku}
      rollback:
        call: {service: inventory, operation: release, params: {sku: =item.sku}}
```

When only part of the flow is transactional, use `group:` inline:

```yaml
- group:
    transaction: true
    onRollbackError: CONTINUE
    do:
    - call:
        service: payment
        operation: charge
        params: {amount: =total}
        result: {tx_id: =RESULT.tx_id}
        rollback:
          call:
            service: payment
            operation: refund
            params: {tx_id: =tx_id}
    - call:
        service: inventory
        operation: reserve
        params: {sku: =item.sku}
        rollback:
          call: {service: inventory, operation: release, params: {sku: =item.sku}}
```

On failure: rollback handlers fire in reverse order, then variable writes auto-revert.

### Sub-flow Security

**cap (allowlist)** -- only stated capabilities granted:
```yaml
- run:
    flow: https://partner.com/analyze.flowmarkup.yaml
    integrity: sha256-abc123
    cap:
      SECRET: [api_key]
      REQUEST: ["partner.com"]
```

**Minimal capabilities** -- grant only what the sub-flow needs:
```yaml
- run:
    flow: reporting/generate.flowmarkup.yaml
    cap:
      SECRET: [report_token]
```

### Rate Limiting and Circuit Breaking

```yaml
- call:
    service: api
    operation: query
    circuitBreaker:
      name: api.query
      threshold: 5
      window: 1m
      resetTimeout: 30s
      scope: GLOBAL
    rateLimit:
      invocations: 100
      per: 1m
      strategy: WAIT
    retry:
      maxAttempts: 3
      delay: 2s
      backoff: EXPONENTIAL
      nonRetryable: [CircuitOpenError]
    timeout: 10s
```

Execution order: `circuitBreaker -> rateLimit -> retry -> timeout -> action`.

**Circuit breaker shorthand forms:**
```yaml
# String shorthand — threshold/name
- call:
    service: api
    operation: query
    circuitBreaker: "5/com.example.api.query"
    retry: 3/2s/EXPONENTIAL
    timeout: 10s

# Integer shorthand — threshold only, name derived from _id_
- call:
    _id_: api_query
    service: api
    operation: query
    circuitBreaker: 5
    timeout: 10s
```

Advanced options (`window`, `resetTimeout`, `halfOpenAttempts`, `scope`, `errors`, `nonCountable`) require object form.

### Event-Driven Patterns

**emit/waitFor with correlation**:
```yaml
- emit:
    event: order_placed
    scope: GLOBAL
    data: {order_id: =order.id, total: =total}
- waitFor:
    event: payment_confirmed
    scope: GLOBAL
    condition: "=EVENT.DATA.order_id == order.id"
    timeout: 30m
    capture: [tx_id]
```

**Event contracts**:
```yaml
events:
  order_placed:
    _notes_: Emitted when a new order is submitted
    data:
      order_id: STRING
      total: NUMBER
```

### User-Defined Functions

```yaml
functions:
  normalize:
    params: [value]
    body: "=value.trim().lowerAscii().replace('-', '_')"
  clamp:
    params:
      val: {$kind: NUMBER}
      lo: {$kind: NUMBER}
      hi: {$kind: NUMBER}
    body: "=val < lo ? lo : (val > hi ? hi : val)"
do:
- set:
    clean_name: =normalize(raw_name)
    safe_score: =clamp(raw_score, 0, 100)
```

### Literate Format

```markdown
---
flowmarkup:
  title: Order Processor
  version: 2
  input:
    order_id: TEXT
  requires: {}
  do:
  - assert: =order_id != null
  - log: "Processing {{order_id}}"
---
# Order Processor

This flow validates and processes incoming orders.
If `documentation:` is omitted from frontmatter, this Markdown body becomes it.
```

---

## Common Mistakes

### 1. Missing `=` on CEL expressions

```yaml
# WRONG                              # CORRECT
condition: score > 0.8               condition: =score > 0.8
```

### 2. `=` inside quotes instead of outside

```yaml
# WRONG                              # CORRECT
value: ="order.id"                   value: "=order.id"
```

### 3. Template interpolation on `=`-prefixed fields (SA-INTERP-1)

```yaml
# WRONG -- {{ }} prohibited on CEL   # CORRECT
condition: "=score > {{threshold}}"  condition: =score > threshold
```

### 4. Using `${}` instead of `{{ }}`

```yaml
# WRONG                              # CORRECT
log: "Order ${order.id} done"        log: "Order {{order.id}} done"
```

### 5. Hyphens in variable names

```yaml
# WRONG -- hyphen is subtraction     # CORRECT
vars:                                vars:
  order-total: 0                       order_total: 0
```

### 6. Lowercase scope prefixes

```yaml
# WRONG                              # CORRECT
value: =context.correlation_id       value: =CONTEXT.correlation_id
```

### 7. Missing `=` in result object form

```yaml
# WRONG -- literal string            # CORRECT
result:                              result:
  tx_id: RESULT.tx_id                  tx_id: =RESULT.tx_id
```

### 8. Secret opacity violations (SA-SECRET-3)

```yaml
# WRONG -- secrets cannot be interpolated or concatenated
log: "Token: {{SECRET.api_key}}"
header: "='Bearer ' + SECRET.api_key"

# CORRECT -- pass secrets only to action boundaries
auth:
  bearer: =SECRET.api_key
```

### 9. Writing to readonly data element (SA-CONST-1)

```yaml
const:
  max_retries: 3
do:
# WRONG -- readonly data elements are immutable
- set: {max_retries: 5}
```

```yaml
# WRONG -- $readonly vars are also immutable
vars:
  api_base:
    $readonly: true
    $kind: STRING
    $value: =ENV.API_HOST
do:
- set: {api_base: "https://other.com"}  # SA-CONST-1 error
```

### 10. forEach concurrent data races

```yaml
# WRONG -- shared variable `total` has last-writer-wins race
- forEach:
    items: =orders
    as: order
    concurrent: true
    do:
      set: {total: =total + order.amount}

# CORRECT -- compute after the loop
- set: {total: =orders.map(o, o.amount).sum()}
```

### 11. Catching parent before child (SA-ERR-3)

```yaml
# WRONG -- NetworkError catches everything, ConnectionRefusedError unreachable
catch:
  NetworkError:
    log: Network error
  ConnectionRefusedError:          # unreachable!
    log: Connection refused

# CORRECT -- most specific first
catch:
  ConnectionRefusedError:
    log: Connection refused
  NetworkError:
    log: Network error
```

### 12. async: true with result or retry

```yaml
# WRONG -- async fire-and-forget cannot capture result or retry
- call:
    service: api
    operation: notify
    async: true
    result: {resp: =RESULT.data}   # incompatible
    retry: 3                       # incompatible

# CORRECT -- async has no result or retry
- call: {service: api, operation: notify, async: true}
```

### 13. `=` prefix on switch match keys

```yaml
# WRONG -- match keys are literal, never CEL
- switch:
    value: =status
    match:
      =ACTIVE:                     # wrong!
        log: Active

# CORRECT
- switch:
    value: =status
    match:
      active:
        log: Active
```

### 14. Single-value output with multi-value return

```yaml
# WRONG -- single-value output needs a bare return expression
output:
  $kind: MAP
  $type: application/json
do:
- return: {data: =result}         # this is multi-value!

# CORRECT
- return: =result
```

### 15. waitFor without timeout

```yaml
# WRONG -- can block forever         # CORRECT -- always set a timeout
- waitFor:                           - waitFor:
    event: payment_confirmed             event: payment_confirmed
    scope: GLOBAL                        scope: GLOBAL
                                         timeout: 30m
```

### 16. lock without timeout

```yaml
# WRONG -- can deadlock              # CORRECT
- lock:                              - lock:
    name: resource_x                     name: resource_x
    scope: GLOBAL                        scope: GLOBAL
    do:                                  timeout: 10s
    - set: {counter: =counter + 1}       do:
                                         - set: {counter: =counter + 1}
```

### 17. RESULT outside result: context

```yaml
# WRONG -- RESULT is only available inside result: mapping
- call: {service: api, operation: fetch}
- set: {data: =RESULT.body}       # RESULT is not in scope here

# CORRECT
- call:
    service: api
    operation: fetch
    result: {data: =RESULT.body}   # capture inside result:
```

### 18. `=` prefix on forEach.as

```yaml
# WRONG -- binding name, not expr    # CORRECT
- forEach:                           - forEach:
    items: =orders                       items: =orders
    as: =order                           as: order
```

### 19. yield inside lock or RACE (SA-YIELD-10, SA-YIELD-20)

```yaml
# WRONG -- yield forbidden in RACE branches
- race:
    fast:
      yield: =partial_result    # forbidden! Use SEQUENCE or PARALLEL.
```

### 20. return inside finally

```yaml
# WRONG -- return in finally is a static analysis error
- try:
    do:
      call: {service: api, operation: fetch}
    finally:
      return: {status: done}      # forbidden! finally is for cleanup only.
```

### 21. camelCase variable names

```yaml
# WRONG                              # CORRECT
vars:                                vars:
  orderTotal: 0                        order_total: 0
  userName: ""                         user_name: ""
```

### 22. `length()` instead of `size()`

```yaml
# WRONG                              # CORRECT
condition: =items.length() > 0      condition: =items.size() > 0
```

### 23. Missing operation: on call (SA-CALL-1)

```yaml
# WRONG                              # CORRECT
- call:                              - call:
    service: payment                     service: payment
    params: {amount: =total}             operation: charge
                                         params: {amount: =total}
```

### 24. Lowercase $kind values

```yaml
# WRONG                              # CORRECT
input:                               input:
  name: string                         name: STRING
  count: number                        count: NUMBER
```

### 25. Writing to GLOBAL without lock

```yaml
# WRONG -- read-modify-write on GLOBAL is not atomic (last-writer-wins)
- set: {GLOBAL.counter: =GLOBAL.counter + 1}

# CORRECT -- use lock for read-modify-write
- lock:
    name: global_counter
    scope: GLOBAL
    timeout: 5s
    do:
      set: {GLOBAL.counter: =GLOBAL.counter + 1}
```