Files
Mainline/AGENTS.md
David Gwilliam e0bbfea26c refactor: consolidate pipeline architecture with unified data source system
MAJOR REFACTORING: Consolidate duplicated pipeline code and standardize on
capability-based dependency resolution. This is a significant but backwards-compatible
restructuring that improves maintainability and extensibility.

## ARCHITECTURE CHANGES

### Data Sources Consolidation
- Move engine/sources_v2.py → engine/data_sources/sources.py
- Move engine/pipeline_sources/ → engine/data_sources/
- Create unified DataSource ABC with common interface:
  * fetch() - idempotent data retrieval
  * get_items() - cached access with automatic refresh
  * refresh() - force cache invalidation
  * is_dynamic - indicate streaming vs static sources
- Support for SourceItem dataclass (content, source, timestamp, metadata)

### Display Backend Improvements
- Update all 7 display backends to use new import paths
- Terminal: Improve dimension detection and handling
- WebSocket: Better error handling and client lifecycle
- Sixel: Refactor graphics rendering
- Pygame: Modernize event handling
- Kitty: Add protocol support for inline images
- Multi: Ensure proper forwarding to all backends
- Null: Maintain testing backend functionality

### Pipeline Adapter Consolidation
- Refactor adapter stages for clarity and flexibility
- RenderStage now handles both item-based and buffer-based rendering
- Add SourceItemsToBufferStage for converting data source items
- Improve DataSourceStage to work with all source types
- Add DisplayStage wrapper for display backends

### Camera & Viewport Refinements
- Update Camera class for new architecture
- Improve viewport dimension detection
- Better handling of resize events across backends

### New Effect Plugins
- border.py: Frame rendering effect with configurable style
- crop.py: Viewport clipping effect for selective display
- tint.py: Color filtering effect for atmosphere

### Tests & Quality
- Add test_border_effect.py with comprehensive border tests
- Add test_crop_effect.py with viewport clipping tests
- Add test_tint_effect.py with color filtering tests
- Update test_pipeline.py for new architecture
- Update test_pipeline_introspection.py for new data source location
- All 463 tests pass with 56% coverage
- Linting: All checks pass with ruff

### Removals (Code Cleanup)
- Delete engine/benchmark.py (deprecated performance testing)
- Delete engine/pipeline_sources/__init__.py (moved to data_sources)
- Delete engine/sources_v2.py (replaced by data_sources/sources.py)
- Update AGENTS.md to reflect new structure

### Import Path Updates
- Update engine/pipeline/controller.py::create_default_pipeline()
  * Old: from engine.sources_v2 import HeadlinesDataSource
  * New: from engine.data_sources.sources import HeadlinesDataSource
- All display backends import from new locations
- All tests import from new locations

## BACKWARDS COMPATIBILITY

This refactoring is intended to be backwards compatible:
- Pipeline execution unchanged (DAG-based with capability matching)
- Effect plugins unchanged (EffectPlugin interface same)
- Display protocol unchanged (Display duck-typing works as before)
- Config system unchanged (presets.toml format same)

## TESTING

- 463 tests pass (0 failures, 19 skipped)
- Full linting check passes
- Manual testing on demo, poetry, websocket modes
- All new effect plugins tested

## FILES CHANGED

- 24 files modified/added/deleted
- 723 insertions, 1,461 deletions (net -738 LOC - cleanup!)
- No breaking changes to public APIs
- All transitive imports updated correctly
2026-03-16 19:47:12 -07:00

10 KiB

Agent Development Guide

Development Environment

This project uses:

  • mise (mise.jdx.dev) - tool version manager and task runner
  • hk (hk.jdx.dev) - git hook manager
  • uv - fast Python package installer
  • ruff - linter and formatter
  • pytest - test runner

Setup

# Install dependencies
mise run install

# Or equivalently:
uv sync --all-extras   # includes mic, websocket, sixel support

Available Commands

mise run test           # Run tests
mise run test-v         # Run tests verbose
mise run test-cov       # Run tests with coverage report
mise run test-browser   # Run e2e browser tests (requires playwright)
mise run lint           # Run ruff linter
mise run lint-fix       # Run ruff with auto-fix
mise run format         # Run ruff formatter
mise run ci             # Full CI pipeline (topics-init + lint + test-cov)

Runtime Commands

mise run run            # Run mainline (terminal)
mise run run-poetry    # Run with poetry feed
mise run run-firehose  # Run in firehose mode
mise run run-websocket # Run with WebSocket display only
mise run run-sixel     # Run with Sixel graphics display
mise run run-both      # Run with both terminal and WebSocket
mise run run-client    # Run both + open browser
mise run cmd           # Run C&C command interface

Git Hooks

At the start of every agent session, verify hooks are installed:

ls -la .git/hooks/pre-commit

If hooks are not installed, install them with:

hk init --mise
mise run pre-commit

IMPORTANT: Always review the hk documentation before modifying hk.pkl:

The project uses hk configured in hk.pkl:

  • pre-commit: runs ruff-format and ruff (with auto-fix)
  • pre-push: runs ruff check + benchmark hook

Benchmark Runner

Benchmark tests are in tests/test_benchmark.py with @pytest.mark.benchmark.

Hook Mode (via pytest)

Run benchmarks in hook mode to catch performance regressions:

mise run test-cov  # Run with coverage

The benchmark tests will fail if performance degrades beyond the threshold.

The pre-push hook runs benchmark in hook mode to catch performance regressions before pushing.

Workflow Rules

Before Committing

  1. Always run the test suite - never commit code that fails tests:

    mise run test
    
  2. Always run the linter:

    mise run lint
    
  3. Fix any lint errors before committing (or let the pre-commit hook handle it).

  4. Review your changes using git diff to understand what will be committed.

On Failing Tests

When tests fail, determine whether it's an out-of-date test or a correctly failing test:

  • Out-of-date test: The test was written for old behavior that has legitimately changed. Update the test to match the new expected behavior.

  • Correctly failing test: The test correctly identifies a broken contract. Fix the implementation, not the test.

Never modify a test to make it pass without understanding why it failed.

Code Review

Before committing significant changes:

  • Run git diff to review all changes
  • Ensure new code follows existing patterns in the codebase
  • Check that type hints are added for new functions
  • Verify that tests exist for new functionality

Testing

Tests live in tests/ and follow the pattern test_*.py.

Run all tests:

mise run test

Run with coverage:

mise run test-cov

The project uses pytest with strict marker enforcement. Test configuration is in pyproject.toml under [tool.pytest.ini_options].

Test Coverage Strategy

Current coverage: 56% (463 tests)

Key areas with lower coverage (acceptable for now):

  • app.py (8%): Main entry point - integration heavy, requires terminal
  • scroll.py (10%): Terminal-dependent rendering logic (unused)

Key areas with good coverage:

  • display/backends/null.py (95%): Easy to test headlessly
  • display/backends/terminal.py (96%): Uses mocking
  • display/backends/multi.py (100%): Simple forwarding logic
  • effects/performance.py (99%): Pure Python logic
  • eventbus.py (96%): Simple event system
  • effects/controller.py (95%): Effects command handling

Areas needing more tests:

  • websocket.py (48%): Network I/O, hard to test in CI
  • ntfy.py (50%): Network I/O, hard to test in CI
  • mic.py (61%): Audio I/O, hard to test in CI

Note: Terminal-dependent modules (scroll, layers render) are harder to test in CI. Performance regression tests are in tests/test_benchmark.py with @pytest.mark.benchmark.

Architecture Notes

  • ntfy.py - standalone notification poller with zero internal dependencies
  • sensors/ - Sensor framework (MicSensor, OscillatorSensor) for real-time input
  • eventbus.py provides thread-safe event publishing for decoupled communication
  • effects/ - plugin architecture with performance monitoring
  • The new pipeline architecture: source → render → effects → display

Canvas & Camera

  • Canvas (engine/canvas.py): 2D rendering surface with dirty region tracking
  • Camera (engine/camera.py): Viewport controller for scrolling content

The Canvas tracks dirty regions automatically when content is written (via put_region, put_text, fill), enabling partial buffer updates for optimized effect processing.

Pipeline Architecture

The new Stage-based pipeline architecture provides capability-based dependency resolution:

  • Stage (engine/pipeline/core.py): Base class for pipeline stages
  • Pipeline (engine/pipeline/controller.py): Executes stages with capability-based dependency resolution
  • StageRegistry (engine/pipeline/registry.py): Discovers and registers stages
  • Stage Adapters (engine/pipeline/adapters.py): Wraps existing components as stages

Capability-Based Dependencies

Stages declare capabilities (what they provide) and dependencies (what they need). The Pipeline resolves dependencies using prefix matching:

  • "source" matches "source.headlines", "source.poetry", etc.
  • This allows flexible composition without hardcoding specific stage names

Sensor Framework

  • Sensor (engine/sensors/__init__.py): Base class for real-time input sensors
  • SensorRegistry: Discovers available sensors
  • SensorStage: Pipeline adapter that provides sensor values to effects
  • MicSensor (engine/sensors/mic.py): Self-contained microphone input
  • OscillatorSensor (engine/sensors/oscillator.py): Test sensor for development
  • PipelineMetricsSensor (engine/sensors/pipeline_metrics.py): Exposes pipeline metrics as sensor values

Sensors support param bindings to drive effect parameters in real-time.

Pipeline Introspection

  • PipelineIntrospectionSource (engine/data_sources/pipeline_introspection.py): Renders live ASCII visualization of pipeline DAG with metrics
  • PipelineIntrospectionDemo (engine/pipeline/pipeline_introspection_demo.py): 3-phase demo controller for effect animation

Preset: pipeline-inspect - Live pipeline introspection with DAG and performance metrics

Partial Update Support

Effect plugins can opt-in to partial buffer updates for performance optimization:

  • Set supports_partial_updates = True on the effect class
  • Implement process_partial(buf, ctx, partial) method
  • The PartialUpdate dataclass indicates which regions changed

Preset System

Presets use TOML format (no external dependencies):

  • Built-in: engine/presets.toml

  • User config: ~/.config/mainline/presets.toml

  • Local override: ./presets.toml

  • Preset loader (engine/pipeline/preset_loader.py): Loads and validates presets

  • PipelinePreset (engine/pipeline/presets.py): Dataclass for preset configuration

Functions:

  • validate_preset() - Validate preset structure
  • validate_signal_path() - Detect circular dependencies
  • generate_preset_toml() - Generate skeleton preset

Display System

  • Display abstraction (engine/display/): swap display backends via the Display protocol

    • display/backends/terminal.py - ANSI terminal output
    • display/backends/websocket.py - broadcasts to web clients via WebSocket
    • display/backends/sixel.py - renders to Sixel graphics (pure Python, no C dependency)
    • display/backends/null.py - headless display for testing
    • display/backends/multi.py - forwards to multiple displays simultaneously
    • display/__init__.py - DisplayRegistry for backend discovery
  • WebSocket display (engine/display/backends/websocket.py): real-time frame broadcasting to web browsers

    • WebSocket server on port 8765
    • HTTP server on port 8766 (serves HTML client)
    • Client at client/index.html with ANSI color parsing and fullscreen support
  • Display modes (--display flag):

    • terminal - Default ANSI terminal output
    • websocket - Web browser display (requires websockets package)
    • sixel - Sixel graphics in supported terminals (iTerm2, mintty, etc.)
    • both - Terminal + WebSocket simultaneously

Effect Plugin System

  • EffectPlugin ABC (engine/effects/types.py): abstract base class for effects

    • All effects must inherit from EffectPlugin and implement process() and configure()
    • Runtime discovery via effects_plugins/__init__.py using issubclass() checks
  • EffectRegistry (engine/effects/registry.py): manages registered effects

  • EffectChain (engine/effects/chain.py): chains effects in pipeline order

Command & Control

  • C&C uses separate ntfy topics for commands and responses
  • NTFY_CC_CMD_TOPIC - commands from cmdline.py
  • NTFY_CC_RESP_TOPIC - responses back to cmdline.py
  • Effects controller handles /effects commands (list, on/off, intensity, reorder, stats)

Pipeline Documentation

The rendering pipeline is documented in docs/PIPELINE.md using Mermaid diagrams.

IMPORTANT: When making significant architectural changes to the rendering pipeline (new layers, effects, display backends), update docs/PIPELINE.md to reflect the changes:

  1. Edit docs/PIPELINE.md with the new architecture
  2. If adding new SVG diagrams, render them manually using an external tool (e.g., Mermaid Live Editor)
  3. Commit both the markdown and any new diagram files