lantern

villa-villekulla-tutorial

Villa Villekulla Tutorial

Build a real pipeline from scratch. Takes about 20 minutes.


What You'll Build

An order processing pipeline that:

  • Loads order data
  • Validates the order
  • Checks inventory
  • Calculates pricing
  • Sends confirmation

Along the way you'll learn:

  • Placing and connecting fences
  • Configuring parameters
  • Handling errors with landing pads
  • Adding approval gates
  • Debugging with the trace panel

Prerequisites

  • Villa Villekulla running (open the web UI)
  • A few sample fences available (the tutorial uses built-in ones)

Part 1: The Happy Path

First, we'll build the pipeline assuming everything works. Then we'll add error handling.

Step 1: Create a New Pipeline

  • Click New Pipeline in the header
  • Name it order-processor
  • You'll see an empty 16×16 grid

The grid starts with a source cell (left edge) and a sink cell (right edge). These are your entry and exit points.

Step 2: Configure the Source

Click the source cell. The property editor appears on the right.

Set the initial context—this is the data your pipeline will process:

order:
  id: "ORD-12345"
  customer: "Acme Corp"
  items:
    - sku: "WIDGET-A"
      quantity: 10
      price: 25.00
    - sku: "GADGET-B"
      quantity: 5
      price: 50.00
  requested_date: "2026-02-01"

This is a YAML editor with syntax highlighting. If your YAML is invalid, you'll see an error before you can save.

Step 3: Add the Validation Fence

  • In the palette, find validate-order under Transforms
  • Click it to select
  • Click an empty cell to the right of the source
  • An arrow fence automatically appears between them

Your grid now looks like:

┌────┬────┬────┬────┬─ ─ ─
│ 📥 │ →  │ ✓  │    │
│src │    │val │    │
└────┴────┴────┴────┴─ ─ ─

Click the validate-order fence. Its property editor shows:

# Parameters (from fence introspection)
required_fields:
  - customer
  - items
  - requested_date
min_items: 1

These are the defaults. You can change them or leave them.

Step 4: Add Inventory Check

  • Select check-inventory from the palette
  • Place it to the right of validate-order
  • Arrow connects automatically

The check-inventory fence has parameters:

warehouse: "main"           # Which warehouse to check
reserve: true               # Reserve items if available
reservation_ttl: "30m"      # How long to hold reservation

Step 5: Add Price Calculator

Place calculate-pricing next in the chain.

Parameters:

tax_rate: 0.08
discount_rules:
  - min_quantity: 10
    discount: 0.05
  - min_quantity: 50
    discount: 0.10

Step 6: Add Confirmation Sender

Place send-confirmation before the sink.

Parameters:

channel: "email"
template: "order-confirmed"
cc:
  - sales@company

Step 7: Connect to Sink

Draw an arrow from send-confirmation to the sink cell.

Your complete pipeline:

┌────┬────┬────┬────┬────┬────┬────┬────┬────┐
│ 📥 │ →  │ ✓  │ →  │ 📦 │ →  │ 💰 │ →  │ 📤 │
│src │    │val │    │inv │    │prc │    │sink│
└────┴────┴────┴────┴────┴────┴────┴────┴────┘

Step 8: Run It

Click Run in the header.

The trace panel shows each step executing:

▶ Step 0: source            1ms   -
▶ Step 1: validate-order    3ms   MISS
▶ Step 2: check-inventory  45ms   MISS
▶ Step 3: calculate-pricing 2ms   MISS
▶ Step 4: send-confirmation 120ms STORED
✅ Complete

Click any step to see the context at that point. Notice how each fence adds to the context:

After validate-order:

order: {...}
validation:
  status: "valid"
  checked_at: "2026-01-26T10:30:00Z"

After check-inventory:

order: {...}
validation: {...}
inventory:
  available: true
  reservation_id: "RES-98765"
  expires_at: "2026-01-26T11:00:00Z"

Step 9: Save It

Click Save. Your pipeline is now stored as a markdown node:

  • Pipeline definition (the grid layout)
  • Configuration (parameters, overrides)
  • Documentation (which you should add!)
  • Run history (accumulates over time)

Part 2: Error Handling

Real orders fail. Let's handle that.

Step 10: Add a Landing Pad

Landing pads catch errors. Place one in an empty row below your main flow:

  • Select landing-pad from the palette
  • Place it at row 3, column 1
  • Configure its catch criteria:
catches:
  type: InventoryError    # Catches inventory failures

Step 11: Add Error Flow

After the landing pad, add fences to handle the error:

  • Place log-error after the landing pad
  • Place notify-sales after that
  • Place error-sink at the end
Row 1: 📥 → ✓ → 📦 → 💰 → 📤   (happy path)
Row 3: 🪂 → 📝 → 📧 → ⚠️       (error path)

Step 12: Add Compensation

The inventory check reserves items. If later steps fail, we need to release them.

Click the check-inventory fence. In the property editor, add:

compensate: release-inventory

Now if calculate-pricing or send-confirmation fails:

  • release-inventory runs (undoes the reservation)
  • Flow jumps to the landing pad
  • Error is logged and sales is notified

Step 13: Test the Error Path

Modify your source context to trigger an error:

order:
  id: "ORD-99999"
  customer: "Test Corp"
  items:
    - sku: "NONEXISTENT"    # This SKU doesn't exist
      quantity: 1000000      # Impossible quantity

Run the pipeline. Watch the trace:

Step 0: source             1ms   -
▶ Step 1: validate-order     2ms   MISS
✗ Step 2: check-inventory   ERROR: InventoryError
  ↩ compensate: (none needed, nothing to undo yet)
  ↪ jump to: landing-pad
▶ Step 3: log-error          1ms   MISS
▶ Step 4: notify-sales      85ms   STORED
⚠️ Complete (with errors)

Part 3: Approval Gates

Large orders need manager approval.

Step 14: Add a Gate

  • Select approval-gate from Gates in the palette
  • Place it between validate-order and check-inventory
  • You'll need to rewire: delete the old arrow, add arrows around the gate
📥 → ✓ → ⏸ → 📦 → 💰 → 📤
        gate

Step 15: Configure the Gate

prompt: "Large order requires approval"
body: |
  Customer ${order.customer} placed order ${order.id}
  for ${order.items.length} items.
  
  Total: $${pricing.subtotal}
  
  Please review and approve.
show_context: true
condition: "order.items.length > 20 || pricing.subtotal > 5000"
approvers:
  - managers
  - finance
timeout: 24h
on_timeout: reject

The gate only activates if the condition is true. Small orders pass through automatically.

Step 16: Test the Gate

Update your source context with a large order:

order:
  id: "ORD-BIG-001"
  customer: "Mega Corp"
  items:
    # ... 30 items totaling $15,000

Run the pipeline. It pauses at the gate:

Step 0: source           1ms
▶ Step 1: validate-order   3ms
⏸ Step 2: approval-gate    WAITING
  Pending approval from: managers, finance

The run history shows this session as "pending". Open the Approvals panel to approve or reject.


Part 4: Debugging

Step 17: Compare Two Runs

You have several runs now. Let's compare them.

  • In the run history panel, select your successful run
  • Hold Shift and select a failed run
  • Click Compare

The diff view shows:

  • Where the runs diverged
  • What was different in context at each step
  • Which path each took

Step 18: Re-run from a Step

Say you want to retry the failed run with a fix:

  • Load the failed session
  • Click step 2 (where it failed)
  • Click Re-run from here
  • Modify the context (fix the SKU)
  • Click Run

A new session is created, starting from step 2 with your modified context.

Step 19: Inspect the Cache

Run your successful pipeline again. Notice the cache hits:

▶ Step 1: validate-order    1ms   HIT
▶ Step 2: check-inventory  45ms   MISS  (reservations aren't cached)
▶ Step 3: calculate-pricing 0ms   HIT

Click a cached step → View Cache to see:

  • Cache key (what made it unique)
  • TTL remaining
  • Cached output

Click Clear Cache to force a fresh run next time.


Part 5: Your Pipeline File

Click View Source to see your pipeline as markdown:

# Order Processor

Pipeline for processing incoming orders with validation,
inventory check, pricing, and confirmation.

## Configuration

```yaml
canvas_size: [10, 5]
overrides:
  B1: { emoji: "✓", label: "Val" }
  D1: { emoji: "📦", label: "Inv" }
  F1: { emoji: "💰", label: "Price" }

Pipeline

tiles:
  A1: { fence: "source", config: {...} }
  B1: { fence: "validate-order" }
  C1: { fence: "approval-gate", config: {...} }
  D1: { fence: "check-inventory", compensate: "release-inventory" }
  F1: { fence: "calculate-pricing" }
  H1: { fence: "send-confirmation" }
  J1: { fence: "sink" }
  # Arrows implicit between adjacent cells
  
  A3: { fence: "landing-pad", catches: { type: "InventoryError" } }
  C3: { fence: "log-error" }
  E3: { fence: "notify-sales" }
  G3: { fence: "error-sink" }

landing-pads:
  - name: inventory-errors
    catches: { type: InventoryError }
    entry: A3

Executions

Run Started Status Duration
5 10:45 180ms
4 10:30 pending
3 10:15 52ms

This file is your pipeline. Edit it directly if you prefer text to the visual editor.

---

## What You Learned

- **Placing fences**: Click palette, click grid
- **Connections**: Arrows are fences too, implicit between adjacent cells
- **Flow rules**: Straight through, arrows to turn, gates split perpendicular
- **Type safety**: Can't place incompatible fences
- **Property editor**: Configure parameters, static or dynamic
- **Error handling**: Landing pads catch, compensation undoes
- **Approval gates**: Human checkpoints with templated messages
- **Debugging**: Trace panel, step context, diffs, re-run from step
- **Caching**: Inspect, clear, understand the keys
- **Pipeline as markdown**: One file = definition + config + docs + history

---

## Next Steps

- Read the [Manual](villa-villekulla-manual) for complete reference
- Try building a pipeline for your own use case
- Explore the fence library: `pippi list --type transform`
- Create your own fences (see the fence authoring guide)

---

## Tags

```yaml
- docs:tutorial
- tool:villa-villekulla
- tool:pippi
- pattern:pipeline
- docs:hands-on

North

slots:
- slug: villa-villekulla-manual
  context:
  - Linking tutorial as child of manual

Provenance

Document

Status: 🔴 Unverified

Changelog

2026-01-26: Created tutorial for Villa Villekulla pipeline IDE