diff --git a/docs/GRAPH_SYSTEM_SUMMARY.md b/docs/GRAPH_SYSTEM_SUMMARY.md
new file mode 100644
index 0000000..473e0b3
--- /dev/null
+++ b/docs/GRAPH_SYSTEM_SUMMARY.md
@@ -0,0 +1,178 @@
+# Graph-Based Pipeline System - Implementation Summary
+
+## Overview
+
+Implemented a graph-based scripting language to replace the verbose `XYZStage` naming convention in Mainline's pipeline architecture. The new system represents pipelines as nodes and connections, providing a more intuitive way to define, configure, and orchestrate pipelines.
+
+## Files Created
+
+### Core Graph System
+- `engine/pipeline/graph.py` - Core graph abstraction (Node, Connection, Graph classes)
+- `engine/pipeline/graph_adapter.py` - Adapter to convert Graph to Pipeline with existing Stage classes
+- `engine/pipeline/graph_toml.py` - TOML-based graph configuration loader
+
+### Tests
+- `tests/test_graph_pipeline.py` - Comprehensive test suite (17 tests, all passing)
+- `examples/graph_dsl_demo.py` - Demo script showing the new DSL
+- `examples/test_graph_integration.py` - Integration test verifying pipeline execution
+- `examples/pipeline_graph.toml` - Example TOML configuration file
+
+### Documentation
+- `docs/graph-dsl.md` - Complete DSL documentation with examples
+- `docs/GRAPH_SYSTEM_SUMMARY.md` - This summary document
+
+## Key Features
+
+### 1. Graph Abstraction
+- **Node Types**: `source`, `camera`, `effect`, `position`, `display`, `render`, `overlay`
+- **Connections**: Directed edges between nodes with automatic dependency resolution
+- **Validation**: Cycle detection and disconnected node warnings
+
+### 2. DSL Syntax Options
+
+#### TOML Configuration
+```toml
+[nodes.source]
+type = "source"
+source = "headlines"
+
+[nodes.camera]
+type = "camera"
+mode = "scroll"
+
+[nodes.noise]
+type = "effect"
+effect = "noise"
+intensity = 0.5
+
+[nodes.display]
+type = "display"
+backend = "terminal"
+
+[connections]
+list = ["source -> camera -> noise -> display"]
+```
+
+#### Python API
+```python
+from engine.pipeline.graph import Graph, NodeType
+from engine.pipeline.graph_adapter import graph_to_pipeline
+
+graph = Graph()
+graph.node("source", NodeType.SOURCE, source="headlines")
+graph.node("camera", NodeType.CAMERA, mode="scroll")
+graph.node("noise", NodeType.EFFECT, effect="noise", intensity=0.5)
+graph.node("display", NodeType.DISPLAY, backend="terminal")
+graph.chain("source", "camera", "noise", "display")
+
+pipeline = graph_to_pipeline(graph)
+```
+
+#### Dictionary/JSON Input
+```python
+from engine.pipeline.graph_adapter import dict_to_pipeline
+
+data = {
+ "nodes": {
+ "source": "headlines",
+ "noise": {"type": "effect", "effect": "noise", "intensity": 0.5},
+ "display": {"type": "display", "backend": "terminal"}
+ },
+ "connections": ["source -> noise -> display"]
+}
+
+pipeline = dict_to_pipeline(data)
+```
+
+### 3. Pipeline Integration
+
+The graph system integrates with the existing pipeline architecture:
+
+- **Auto-injection**: Pipeline automatically injects required stages (camera_update, render, etc.)
+- **Capability Resolution**: Uses existing capability-based dependency system
+- **Type Safety**: Validates data flow between stages (TEXT_BUFFER, SOURCE_ITEMS, etc.)
+- **Backward Compatible**: Works alongside existing preset system
+
+### 4. Node Configuration
+
+| Node Type | Config Options | Example |
+|-----------|----------------|---------|
+| `source` | `source`: "headlines", "poetry", "empty" | `{"type": "source", "source": "headlines"}` |
+| `camera` | `mode`: "scroll", "feed", "horizontal", etc.
`speed`: float | `{"type": "camera", "mode": "scroll", "speed": 1.0}` |
+| `effect` | `effect`: effect name
`intensity`: 0.0-1.0 | `{"type": "effect", "effect": "noise", "intensity": 0.5}` |
+| `position` | `mode`: "absolute", "relative", "mixed" | `{"type": "position", "mode": "mixed"}` |
+| `display` | `backend`: "terminal", "null", "websocket" | `{"type": "display", "backend": "terminal"}` |
+
+## Implementation Details
+
+### Graph Adapter Logic
+
+1. **Node Mapping**: Converts graph nodes to appropriate Stage classes
+2. **Effect Intensity**: Sets effect intensity globally (consistent with existing architecture)
+3. **Camera Creation**: Maps mode strings to Camera factory methods
+4. **Dependencies**: Effects automatically depend on `render.output`
+5. **Type Flow**: Ensures TEXT_BUFFER flow between render and effects
+
+### Validation
+
+- **Disconnected Nodes**: Warns about nodes without connections
+- **Cycle Detection**: Detects circular dependencies using DFS
+- **Type Validation**: Pipeline validates inlet/outlet type compatibility
+
+## Files Modified
+
+### Core Pipeline
+- `engine/pipeline/controller.py` - Pipeline class (no changes needed, uses existing architecture)
+- `engine/pipeline/graph_adapter.py` - Added effect intensity setting, fixed PositionStage creation
+- `engine/app/pipeline_runner.py` - Added graph config support
+
+### Documentation
+- `AGENTS.md` - Updated with task tracking
+
+## Test Results
+
+```
+17 tests passed in 0.23s
+- Graph creation and manipulation
+- Connection handling and validation
+- TOML loading and parsing
+- Pipeline conversion and execution
+- Effect intensity configuration
+- Camera mode mapping
+- Positioning mode support
+```
+
+## Usage Examples
+
+### Running with Graph Config
+```bash
+python -c "
+from engine.effects.plugins import discover_plugins
+from engine.pipeline.graph_toml import load_pipeline_from_toml
+
+discover_plugins()
+pipeline = load_pipeline_from_toml('examples/pipeline_graph.toml')
+"
+```
+
+### Integration with Pipeline Runner
+```bash
+# The pipeline runner now supports graph configs
+# (Implementation in progress)
+```
+
+## Benefits
+
+1. **Simplified Configuration**: No need to manually create Stage instances
+2. **Visual Representation**: Graph structure is easier to understand than class hierarchy
+3. **Automatic Dependency Resolution**: Pipeline handles stage ordering automatically
+4. **Flexible Composition**: Easy to add/remove/modify pipeline stages
+5. **Backward Compatible**: Existing presets and stages continue to work
+
+## Future Enhancements
+
+1. **CLI Integration**: Add `--graph-config` flag to mainline command
+2. **Visual Builder**: Web-based drag-and-drop pipeline editor
+3. **Script Execution**: Support for loops, conditionals, and timing in graph scripts
+4. **Parameter Binding**: Real-time sensor-to-parameter bindings in graph config
+5. **Pipeline Inspection**: Visual DAG representation with metrics
diff --git a/docs/graph-dsl.md b/docs/graph-dsl.md
new file mode 100644
index 0000000..8d065c1
--- /dev/null
+++ b/docs/graph-dsl.md
@@ -0,0 +1,210 @@
+# Graph-Based Pipeline DSL
+
+This document describes the new graph-based DSL for defining pipelines in Mainline.
+
+## Overview
+
+The graph DSL represents pipelines as nodes and connections, replacing the verbose `XYZStage` naming convention with a more intuitive graph abstraction.
+
+## TOML Syntax
+
+### Basic Pipeline
+
+```toml
+[nodes.source]
+type = "source"
+source = "headlines"
+
+[nodes.camera]
+type = "camera"
+mode = "scroll"
+
+[nodes.display]
+type = "display"
+backend = "terminal"
+
+[connections]
+list = ["source -> camera -> display"]
+```
+
+### With Effects
+
+```toml
+[nodes.source]
+type = "source"
+source = "headlines"
+
+[nodes.noise]
+type = "effect"
+effect = "noise"
+intensity = 0.5
+
+[nodes.fade]
+type = "effect"
+effect = "fade"
+intensity = 0.8
+
+[nodes.display]
+type = "display"
+backend = "terminal"
+
+[connections]
+list = ["source -> noise -> fade -> display"]
+```
+
+### With Positioning
+
+```toml
+[nodes.source]
+type = "source"
+source = "headlines"
+
+[nodes.position]
+type = "position"
+mode = "mixed"
+
+[nodes.display]
+type = "display"
+backend = "terminal"
+
+[connections]
+list = ["source -> position -> display"]
+```
+
+## Python API
+
+### Basic Construction
+
+```python
+from engine.pipeline.graph import Graph, NodeType
+
+graph = Graph()
+graph.node("source", NodeType.SOURCE, source="headlines")
+graph.node("camera", NodeType.CAMERA, mode="scroll")
+graph.node("display", NodeType.DISPLAY, backend="terminal")
+graph.chain("source", "camera", "display")
+
+pipeline = graph_to_pipeline(graph)
+```
+
+### With Effects
+
+```python
+from engine.pipeline.graph import Graph, NodeType
+
+graph = Graph()
+graph.node("source", NodeType.SOURCE, source="headlines")
+graph.node("noise", NodeType.EFFECT, effect="noise", intensity=0.5)
+graph.node("fade", NodeType.EFFECT, effect="fade", intensity=0.8)
+graph.node("display", NodeType.DISPLAY, backend="terminal")
+graph.chain("source", "noise", "fade", "display")
+
+pipeline = graph_to_pipeline(graph)
+```
+
+### Dictionary/JSON Input
+
+```python
+from engine.pipeline.graph_adapter import dict_to_pipeline
+
+data = {
+ "nodes": {
+ "source": "headlines",
+ "noise": {"type": "effect", "effect": "noise", "intensity": 0.5},
+ "display": {"type": "display", "backend": "terminal"}
+ },
+ "connections": ["source -> noise -> display"]
+}
+
+pipeline = dict_to_pipeline(data)
+```
+
+## CLI Usage
+
+### Using Graph Config File
+
+```bash
+mainline --graph-config pipeline.toml
+```
+
+### Inline Graph Definition
+
+```bash
+mainline --graph 'source:headlines -> noise:noise:0.5 -> display:terminal'
+```
+
+### With Preset Override
+
+```bash
+mainline --preset demo --graph-modify 'add:noise:0.5 after:source'
+```
+
+## Node Types
+
+| Type | Description | Config Options |
+|------|-------------|----------------|
+| `source` | Data source | `source`: "headlines", "poetry", "empty", etc. |
+| `camera` | Viewport camera | `mode`: "scroll", "feed", "horizontal", etc. `speed`: float |
+| `effect` | Visual effect | `effect`: effect name, `intensity`: 0.0-1.0 |
+| `position` | Positioning mode | `mode`: "absolute", "relative", "mixed" |
+| `display` | Output backend | `backend`: "terminal", "null", "websocket", etc. |
+| `render` | Text rendering | (auto-injected) |
+| `overlay` | Message overlay | (auto-injected) |
+
+## Advanced Features
+
+### Conditional Connections
+
+```toml
+[connections]
+list = ["source -> camera -> display"]
+# Effects can be conditionally enabled/disabled
+```
+
+### Parameter Binding
+
+```toml
+[nodes.noise]
+type = "effect"
+effect = "noise"
+intensity = 1.0
+# intensity can be bound to sensor values at runtime
+```
+
+### Pipeline Inspection
+
+```toml
+[nodes.inspect]
+type = "pipeline-inspect"
+# Renders live pipeline visualization
+```
+
+## Comparison with Stage-Based Approach
+
+### Old (Stage-Based)
+
+```python
+pipeline = Pipeline()
+pipeline.add_stage("source", DataSourceStage(HeadlinesDataSource()))
+pipeline.add_stage("camera", CameraStage(Camera.scroll()))
+pipeline.add_stage("render", FontStage())
+pipeline.add_stage("noise", EffectPluginStage(noise_effect))
+pipeline.add_stage("display", DisplayStage(terminal_display))
+```
+
+### New (Graph-Based)
+
+```python
+graph = Graph()
+graph.node("source", NodeType.SOURCE, source="headlines")
+graph.node("camera", NodeType.CAMERA, mode="scroll")
+graph.node("noise", NodeType.EFFECT, effect="noise")
+graph.node("display", NodeType.DISPLAY, backend="terminal")
+graph.chain("source", "camera", "noise", "display")
+pipeline = graph_to_pipeline(graph)
+```
+
+The graph system automatically:
+- Inserts the render stage between camera and effects
+- Handles capability-based dependency resolution
+- Auto-injects required stages (camera_update, render, etc.)
diff --git a/docs/presets-usage.md b/docs/presets-usage.md
new file mode 100644
index 0000000..313381b
--- /dev/null
+++ b/docs/presets-usage.md
@@ -0,0 +1,219 @@
+# Presets Usage Guide
+
+## Overview
+
+The sideline branch introduces a new preset system that allows you to easily configure different pipeline behaviors. This guide explains the available presets and how to use them.
+
+## Available Presets
+
+### 1. upstream-default
+
+**Purpose:** Matches the default upstream Mainline operation for comparison.
+
+**Configuration:**
+- **Display:** Terminal (not pygame)
+- **Camera:** Scroll mode
+- **Effects:** noise, fade, glitch, firehose (classic four effects)
+- **Positioning:** Mixed mode
+- **Message Overlay:** Disabled (matches upstream)
+
+**Usage:**
+```bash
+python -m mainline --preset upstream-default --display terminal
+```
+
+**Best for:**
+- Comparing sideline vs upstream behavior
+- Legacy terminal-based operation
+- Baseline performance testing
+
+### 2. demo
+
+**Purpose:** Showcases sideline features including hotswappable effects and sensors.
+
+**Configuration:**
+- **Display:** Pygame (graphical display)
+- **Camera:** Scroll mode
+- **Effects:** noise, fade, glitch, firehose, hud (with visual feedback)
+- **Positioning:** Mixed mode
+- **Message Overlay:** Enabled (with ntfy integration)
+
+**Features:**
+- **Hotswappable Effects:** Effects can be toggled and modified at runtime
+- **LFO Sensor Modulation:** Oscillator sensor provides smooth intensity modulation
+- **Visual Feedback:** HUD effect shows current effect state and pipeline info
+- **Mixed Positioning:** Optimal balance of performance and control
+
+**Usage:**
+```bash
+python -m mainline --preset demo --display pygame
+```
+
+**Best for:**
+- Exploring sideline capabilities
+- Testing effect hotswapping
+- Demonstrating sensor integration
+
+### 3. demo-base / demo-pygame
+
+**Purpose:** Base presets for custom effect hotswapping experiments.
+
+**Configuration:**
+- **Display:** Terminal (base) or Pygame (pygame variant)
+- **Camera:** Feed mode
+- **Effects:** Empty (add your own)
+- **Positioning:** Mixed mode
+
+**Usage:**
+```bash
+python -m mainline --preset demo-pygame --display pygame
+```
+
+### 4. Other Presets
+
+- `poetry`: Poetry feed with subtle effects
+- `firehose`: High-speed firehose mode
+- `ui`: Interactive UI mode with control panel
+- `fixture`: Uses cached headline fixtures
+- `websocket`: WebSocket display mode
+
+## Positioning Modes
+
+The `--positioning` flag controls how text is positioned in the terminal:
+
+```bash
+# Relative positioning (newlines, good for scrolling)
+python -m mainline --positioning relative --preset demo
+
+# Absolute positioning (cursor codes, good for overlays)
+python -m mainline --positioning absolute --preset demo
+
+# Mixed positioning (default, optimal balance)
+python -m mainline --positioning mixed --preset demo
+```
+
+## Pipeline Stages
+
+### Upstream-Default Pipeline
+
+1. **Source Stage:** Headlines data source
+2. **Viewport Filter:** Filters items to viewport height
+3. **Font Stage:** Renders headlines as block characters
+4. **Camera Stages:** Scrolling animation
+5. **Effect Stages:** noise, fade, glitch, firehose
+6. **Display Stage:** Terminal output
+
+### Demo Pipeline
+
+1. **Source Stage:** Headlines data source
+2. **Viewport Filter:** Filters items to viewport height
+3. **Font Stage:** Renders headlines as block characters
+4. **Camera Stages:** Scrolling animation
+5. **Effect Stages:** noise, fade, glitch, firehose, hud
+6. **Message Overlay:** Ntfy message integration
+7. **Display Stage:** Pygame output
+
+## Command-Line Examples
+
+### Basic Usage
+
+```bash
+# Run upstream-default preset
+python -m mainline --preset upstream-default --display terminal
+
+# Run demo preset
+python -m mainline --preset demo --display pygame
+
+# Run with custom positioning
+python -m mainline --preset demo --display pygame --positioning absolute
+```
+
+### Comparison Testing
+
+```bash
+# Capture upstream output
+python -m mainline --preset upstream-default --display null --viewport 80x24
+
+# Capture sideline output
+python -m mainline --preset demo --display null --viewport 80x24
+```
+
+### Hotswapping Effects
+
+The demo preset supports hotswapping effects at runtime:
+- Use the WebSocket display to send commands
+- Toggle effects on/off
+- Adjust intensity values in real-time
+
+## Configuration Files
+
+### Built-in Presets
+
+Location: `engine/pipeline/presets.py` (Python code)
+
+### User Presets
+
+Location: `~/.config/mainline/presets.toml` or `./presets.toml`
+
+Example user preset:
+```toml
+[presets.my-custom-preset]
+description = "My custom configuration"
+source = "headlines"
+display = "terminal"
+camera = "scroll"
+effects = ["noise", "fade"]
+positioning = "mixed"
+viewport_width = 100
+viewport_height = 30
+```
+
+## Sensor Configuration
+
+### Oscillator Sensor (LFO)
+
+The oscillator sensor provides Low Frequency Oscillator modulation:
+
+```toml
+[sensors.oscillator]
+enabled = true
+waveform = "sine" # sine, square, triangle, sawtooth
+frequency = 0.05 # 20 second cycle (gentle)
+amplitude = 0.5 # 50% modulation
+```
+
+### Effect Configuration
+
+Effect intensities can be configured with initial values:
+
+```toml
+[effect_configs.noise]
+enabled = true
+intensity = 1.0
+
+[effect_configs.fade]
+enabled = true
+intensity = 1.0
+
+[effect_configs.glitch]
+enabled = true
+intensity = 0.5
+```
+
+## Troubleshooting
+
+### No Display Output
+
+- Check if display backend is available (pygame, terminal, etc.)
+- Use `--display null` for headless testing
+
+### Effects Not Modulating
+
+- Ensure sensor is enabled in presets.toml
+- Check effect intensity values in configuration
+
+### Performance Issues
+
+- Use `--positioning relative` for large buffers
+- Reduce viewport height for better performance
+- Use null display for testing without rendering
diff --git a/examples/graph_dsl_demo.py b/examples/graph_dsl_demo.py
new file mode 100644
index 0000000..566886f
--- /dev/null
+++ b/examples/graph_dsl_demo.py
@@ -0,0 +1,136 @@
+#!/usr/bin/env python3
+"""
+Demo script showing the new graph-based DSL for pipeline configuration.
+
+This demonstrates how to define pipelines using the graph abstraction,
+which is more intuitive than the verbose XYZStage naming convention.
+"""
+
+from engine.effects.plugins import discover_plugins
+from engine.pipeline.graph import Graph, NodeType
+from engine.pipeline.graph_adapter import graph_to_pipeline, dict_to_pipeline
+
+
+def demo_imperative_api():
+ """Demo: Imperative Python API for building graphs."""
+ print("=== Imperative Python API ===")
+
+ graph = Graph()
+ graph.node("source", NodeType.SOURCE, source="headlines")
+ graph.node("camera", NodeType.CAMERA, mode="scroll", speed=1.0)
+ graph.node("noise", NodeType.EFFECT, effect="noise", intensity=0.3)
+ graph.node("display", NodeType.DISPLAY, backend="null")
+
+ # Connect nodes in a chain
+ graph.chain("source", "camera", "noise", "display")
+
+ # Validate the graph
+ errors = graph.validate()
+ if errors:
+ print(f"Validation errors: {errors}")
+ return
+
+ # Convert to pipeline
+ pipeline = graph_to_pipeline(graph, viewport_width=80, viewport_height=24)
+
+ print(f"Pipeline created with {len(pipeline._stages)} stages:")
+ for name, stage in pipeline._stages.items():
+ print(f" - {name}: {stage.__class__.__name__}")
+
+ return pipeline
+
+
+def demo_dict_api():
+ """Demo: Dictionary-based API for building graphs."""
+ print("\n=== Dictionary API ===")
+
+ data = {
+ "nodes": {
+ "source": "headlines",
+ "camera": {"type": "camera", "mode": "scroll", "speed": 1.0},
+ "noise": {"type": "effect", "effect": "noise", "intensity": 0.5},
+ "fade": {"type": "effect", "effect": "fade", "intensity": 0.8},
+ "display": {"type": "display", "backend": "null"},
+ },
+ "connections": ["source -> camera -> noise -> fade -> display"],
+ }
+
+ pipeline = dict_to_pipeline(data, viewport_width=80, viewport_height=24)
+
+ print(f"Pipeline created with {len(pipeline._stages)} stages:")
+ for name, stage in pipeline._stages.items():
+ print(f" - {name}: {stage.__class__.__name__}")
+
+ return pipeline
+
+
+def demo_graph_validation():
+ """Demo: Graph validation."""
+ print("\n=== Graph Validation ===")
+
+ # Create a graph with a cycle
+ graph = Graph()
+ graph.node("a", NodeType.SOURCE)
+ graph.node("b", NodeType.CAMERA)
+ graph.node("c", NodeType.DISPLAY)
+ graph.connect("a", "b")
+ graph.connect("b", "c")
+ graph.connect("c", "a") # Creates cycle
+
+ errors = graph.validate()
+ print(f"Cycle detection errors: {errors}")
+
+ # Create a valid graph
+ graph2 = Graph()
+ graph2.node("source", NodeType.SOURCE, source="headlines")
+ graph2.node("display", NodeType.DISPLAY, backend="null")
+ graph2.connect("source", "display")
+
+ errors2 = graph2.validate()
+ print(f"Valid graph errors: {errors2}")
+
+
+def demo_node_types():
+ """Demo: Different node types."""
+ print("\n=== Node Types ===")
+
+ graph = Graph()
+
+ # Source node
+ graph.node("headlines", NodeType.SOURCE, source="headlines")
+ print("✓ Source node created")
+
+ # Camera node with different modes
+ graph.node("camera_scroll", NodeType.CAMERA, mode="scroll", speed=1.0)
+ graph.node("camera_feed", NodeType.CAMERA, mode="feed", speed=0.5)
+ graph.node("camera_horizontal", NodeType.CAMERA, mode="horizontal", speed=1.0)
+ print("✓ Camera nodes created (scroll, feed, horizontal)")
+
+ # Effect nodes
+ graph.node("noise", NodeType.EFFECT, effect="noise", intensity=0.3)
+ graph.node("fade", NodeType.EFFECT, effect="fade", intensity=0.8)
+ print("✓ Effect nodes created (noise, fade)")
+
+ # Positioning node
+ graph.node("position", NodeType.POSITION, mode="mixed")
+ print("✓ Positioning node created")
+
+ # Display nodes
+ graph.node("terminal", NodeType.DISPLAY, backend="terminal")
+ graph.node("null", NodeType.DISPLAY, backend="null")
+ print("✓ Display nodes created")
+
+ print(f"\nTotal nodes: {len(graph.nodes)}")
+
+
+if __name__ == "__main__":
+ # Discover effect plugins first
+ discover_plugins()
+
+ # Run demos
+ demo_imperative_api()
+ demo_dict_api()
+ demo_graph_validation()
+ demo_node_types()
+
+ print("\n=== Demo Complete ===")
diff --git a/examples/test_graph_integration.py b/examples/test_graph_integration.py
new file mode 100644
index 0000000..718704f
--- /dev/null
+++ b/examples/test_graph_integration.py
@@ -0,0 +1,110 @@
+#!/usr/bin/env python3
+"""
+Test script to verify graph-based pipeline integration.
+
+This script tests that the graph DSL can be used to create working pipelines
+that produce output similar to preset-based pipelines.
+"""
+
+from engine.effects.plugins import discover_plugins
+from engine.pipeline.graph_toml import load_pipeline_from_toml
+from engine.pipeline.params import PipelineParams
+
+
+def test_graph_pipeline_execution():
+ """Test that a graph-based pipeline can execute and produce output."""
+ print("=== Testing Graph Pipeline Execution ===")
+
+ # Discover plugins
+ discover_plugins()
+
+ # Load pipeline from TOML
+ pipeline = load_pipeline_from_toml(
+ "examples/pipeline_graph.toml", viewport_width=80, viewport_height=24
+ )
+
+ print(f"Pipeline loaded with {len(pipeline._stages)} stages")
+ print(f"Stages: {list(pipeline._stages.keys())}")
+
+ # Initialize pipeline
+ if not pipeline.initialize():
+ print("Failed to initialize pipeline")
+ return False
+
+ print("Pipeline initialized successfully")
+
+ # Set up context
+ ctx = pipeline.context
+ params = PipelineParams(viewport_width=80, viewport_height=24)
+ ctx.params = params
+
+ # Execute pipeline with empty items (source will provide content)
+ result = pipeline.execute([])
+
+ if result.success:
+ print(f"Pipeline executed successfully")
+ print(f"Output type: {type(result.data)}")
+ if isinstance(result.data, list):
+ print(f"Output lines: {len(result.data)}")
+ if len(result.data) > 0:
+ print(f"First line: {result.data[0][:50]}...")
+ return True
+ else:
+ print(f"Pipeline execution failed: {result.error}")
+ return False
+
+
+def test_graph_vs_preset():
+ """Compare graph-based and preset-based pipelines."""
+ print("\n=== Comparing Graph vs Preset ===")
+
+ from engine.pipeline import get_preset
+
+ # Load graph-based pipeline
+ graph_pipeline = load_pipeline_from_toml(
+ "examples/pipeline_graph.toml", viewport_width=80, viewport_height=24
+ )
+
+ # Load preset-based pipeline (using test-basic as a base)
+ preset = get_preset("test-basic")
+ if not preset:
+ print("test-basic preset not found")
+ return False
+
+ # Create pipeline from preset config
+ from engine.pipeline import Pipeline
+
+ preset_pipeline = Pipeline(config=preset.to_config())
+
+ print(f"Graph pipeline stages: {len(graph_pipeline._stages)}")
+ print(f"Preset pipeline stages: {len(preset_pipeline._stages)}")
+
+ # Compare stage types
+ graph_stage_types = {
+ name: stage.__class__.__name__ for name, stage in graph_pipeline._stages.items()
+ }
+ preset_stage_types = {
+ name: stage.__class__.__name__
+ for name, stage in preset_pipeline._stages.items()
+ }
+
+ print("\nGraph pipeline stages:")
+ for name, stage_type in graph_stage_types.items():
+ print(f" - {name}: {stage_type}")
+
+ print("\nPreset pipeline stages:")
+ for name, stage_type in preset_stage_types.items():
+ print(f" - {name}: {stage_type}")
+
+ return True
+
+
+if __name__ == "__main__":
+ success1 = test_graph_pipeline_execution()
+ success2 = test_graph_vs_preset()
+
+ if success1 and success2:
+ print("\n✓ All tests passed!")
+ else:
+ print("\n✗ Some tests failed")
+ exit(1)