PipelineBuilder solves the glue problem between processing steps. Declare your steps, register handler functions, wire the connections, and hand control to ExecutionEngine — it handles topological ordering, passes output between steps, retries on failure with configurable backoff, and returns a structured ExecutionResult you can log or alert on.

Why Use Pipelines?

Pipelines solve coordination problems in multi-step data processing. Instead of writing monolithic scripts where each function calls the next, you define independent steps and declare their dependencies. The pipeline engine figures out the execution order, runs independent steps in parallel, and passes data between steps automatically. This becomes essential when you have:
  • Multiple data sources that need different processing
  • Steps that can run concurrently
  • Complex retry or error recovery requirements
  • Workflows that change frequently
  • Team environments where different people own different steps
A five-step pipeline with two parallel branches executes faster than a linear script, and adding a sixth step requires only declaring where it fits in the DAG.

When To Use / When Not To Use

Use pipelines for:
  • Multi-step ETL workflows with 3+ processing stages
  • Scheduled production jobs that run daily or hourly
  • Workflows where some steps can run in parallel
  • Complex data transformations that benefit from modular design
  • Team environments where different people maintain different steps
Don’t use pipelines for:
  • One-off scripts or prototypes (use simple functions instead)
  • Linear workflows with only 1-2 steps
  • Exploratory data analysis in Jupyter notebooks
  • Cases where the overhead of defining steps exceeds the workflow complexity

Typical Pipeline Workflow

Most data pipelines follow a four-stage pattern regardless of domain:
  1. Ingest — Pull data from files, APIs, databases, or streams
  2. Transform — Clean, validate, normalize, and enrich the raw data
  3. Extract — Run entity recognition, relationship extraction, or other NLP tasks
  4. Store — Write results to vector stores, knowledge graphs, or output files
Each stage can have multiple parallel steps. For example, the Ingest stage might fetch from three different REST APIs simultaneously, while Transform applies different cleaning rules to each source.
PipelineBuilder and ExecutionEngine are in semantica.pipeline. Failure handling, retry policies, and parallelism management are separate classes you can import individually for fine-grained control. Custom step handlers are plain Python functions — no subclassing required.

Your First Pipeline

The minimum viable pipeline has three steps: ingest, extract, store. Define them, connect them, build, execute. Every step is a function that takes data as the first positional argument and receives step configuration as keyword arguments. The pipeline engine calls your step handler with the upstream data (from dependencies) and passes any step configuration as **kwargs:
Root steps (no dependencies) receive None as their data parameter — they generate data from scratch rather than transforming upstream outputs. Pass the handler function directly to add_step() as the handler keyword argument, alongside any step configuration:
ExecutionEngine performs a topological sort of the step graph before executing, so even if you declare steps in the wrong order the execution sequence is always correct. Each step receives the previous step’s return value as its data argument.

Reading the ExecutionResult

Every engine.execute_pipeline() call returns an ExecutionResult dataclass. Check it before assuming success:
result.errors is a List[str] — one entry per failed step, each containing the exception message. A pipeline with retry_on_failure=True attempts each failed step up to max_retries times (default: 3) before recording it as a failure and moving on.

Handling Failures and Configuring Retry Policy

By default, ExecutionEngine(retry_on_failure=True) uses an exponential backoff policy: three retries, starting at 1 second, doubling each time, capped at 60 seconds. For steps that call external APIs or databases — where transient failures are expected — you can set per-step-type policies via FailureHandler:
handler.classify_error() distinguishes ValidationError (low severity, usually don’t retry), ProcessingError (high severity), and timeout/connection errors (medium severity, always retry). You can inspect the classification:

Running Steps in Parallel

When two steps don’t depend on each other — for example, NER extraction and triplet extraction both reading from the same ingest output — declare them as parallel branches by connecting both to the same upstream step:
set_parallelism(n) tells the engine how many steps it may run simultaneously. The topological sort guarantees that only steps whose dependencies are all completed are eligible for concurrent execution — you cannot accidentally run a step before its inputs are ready.

Common Pitfalls

Forgetting to return data. If a step handler doesn’t return anything, downstream steps receive None as their data parameter. This usually causes crashes or silent failures. Every non-terminal step should return data for the next stage. Excessive parallelism. Setting max_workers=50 on an 8-core machine creates more overhead than speedup. Start with max_workers=4 and increase gradually while monitoring resource usage. Most I/O-bound steps work well with moderate parallelism. Stateful handlers. Step handlers should be pure functions — given the same input data and config, they should produce the same output. Avoid global variables, file handles, or database connections that persist between calls. Each step execution should be independent. Debugging large pipelines. When a 10-step pipeline fails on step 7, don’t re-run the entire pipeline to debug. Extract the failing step into a standalone script, use the actual intermediate data as input, and fix it in isolation.

Development and Debugging

Start small and validate each step individually before building the full pipeline:
For complex pipelines, add checkpoints to save intermediate outputs:

Delta / Incremental Processing

Delta pipelines process only changes between two versions of your data, rather than reprocessing everything from scratch. This is essential for large datasets where full reprocessing is too slow or expensive. Your STIX bundle directory grows by 20–30 new files each night. Re-processing all 4,000 historical files every morning wastes time and compute. delta_mode=True on the ingest step tells the pipeline to process only files that have changed since the last version snapshot:
The base_version_id and target_version_id are stored on the PipelineStep dataclass and passed through to your handler via config — your handler is responsible for using them to filter its input. A typical pattern is to check file modification timestamps against the base version date.

Building from a Config Dict

For pipelines defined in config files — useful when different environments (dev, staging, prod) run the same pipeline with different paths and thresholds — pass a dict to build_pipeline() instead of calling add_step() manually:
Note that build_pipeline() reads step connections from the "dependencies" key inside each step’s config dict (not from connect_steps() calls). Add dependencies explicitly if you use this path:

Monitoring Progress

ExecutionEngine integrates with Semantica’s progress tracker automatically — every step start, update, and completion is recorded. To observe progress during a long-running pipeline, inspect step status on the Pipeline object after execution:
result.metrics gives the aggregate view:

Domain Examples

A SOC threat intelligence team needs an end-to-end pipeline that ingests STIX bundles from a classified directory, runs entity extraction with custom threat-actor labels, and builds a ContextGraph ready for analyst queries. The pipeline runs every six hours; failed steps retry automatically so a transient filesystem error doesn’t drop an ingestion cycle.
  • Ingest — all source types for the ingest step: PDFs, APIs, databases, RSS feeds, STIX directories, and streams
  • Semantic Extraction — NER, relation extraction, triplet extraction, and event detection for the extract step
  • Context Graphs — building and querying the ContextGraph that the store step populates
  • Provenance — tracking the origin document, confidence score, and pipeline run ID for every extracted entity