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: 1These 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 reservationStep 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.10Step 6: Add Confirmation Sender
Place send-confirmation before the sink.
Parameters:
channel: "email"
template: "order-confirmed"
cc:
- sales@companyStep 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
✅ CompleteClick 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 failuresStep 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-inventoryNow 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 quantityRun 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
📥 → ✓ → ⏸ → 📦 → 💰 → 📤
gateStep 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: rejectThe 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,000Run 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, financeThe 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 HITClick 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: A3Executions
| 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-onNorth
slots:
- slug: villa-villekulla-manual
context:
- Linking tutorial as child of manualProvenance
Document
Status: 🔴 Unverified
Changelog
2026-01-26: Created tutorial for Villa Villekulla pipeline IDE