File size: 6,940 Bytes
116524e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# Pipeline Engine

A generic, composable step runner for ordered and parallel data processing.

---

## What is the Pipeline Engine?

The Pipeline Engine is a lightweight, domain-agnostic framework for composing processing steps into pipelines. It provides contract validation, immutable context passing, and built-in concurrency control β€” all in ~300 lines of pure Python with no external dependencies beyond the standard library.

Everything composes from three primitives:

**Sequential** β€” steps run one after another:

```mermaid

graph LR

    A1[Step A] --> B1[Step B] --> C1[Step C]

```

**Branch** β€” fork, run in parallel, join:

```mermaid

graph LR

    A2[Step A] --> B2[Step B] & C2[Step C] --> D2[Step D]

```

**Nesting** β€” a pipeline used as a step:

```mermaid

graph LR

    A3[Step A] --> P3[[Inner Pipeline]] --> D3[Step D]

```

Steps declare what data they read and write. The pipeline validates ordering at construction time β€” before any data flows β€” so wiring errors surface immediately, not at runtime.

---

## Core Principles

- **Three primitives** β€” Sequential steps, parallel branches, and nested pipelines cover every composition pattern
- **Contracts** β€” Steps declare `requires` and `provides` fields; the pipeline validates ordering at construction time
- **Immutable context** β€” Steps receive a frozen context and return a new one via `.replace()`, making concurrent execution safe by default
- **Declared concurrency** β€” Parallelism is configured on the step (`max_workers`, `async_boundary`), not the pipeline
- **Per-sample error isolation** β€” One failing sample never blocks others; every sample produces a result
- **Observation hooks** β€” `PipelineHook` lets external code observe step transitions without modifying data flow (progress streaming, metrics, logging)
- **Cancellation** β€” `CancellationToken` stops a running pipeline between steps; `cancel_token_var` makes the token readable inside steps for intra-step cancellation

---

## Architecture at a Glance

```mermaid

classDiagram

    class StepProtocol {

        <<protocol>>

        +requires: set[str]

        +provides: set[str]

        +__call__(ctx: StepContext) StepContext

    }



    class Pipeline {

        +then(step) Pipeline

        +branch(*pipelines) Pipeline

        +run(samples) list~SampleResult~

        +run_async(samples) list~SampleResult~

    }



    class Branch {

        +merge: MergeStrategy

    }



    class YourStep {

        +requires: set[str]

        +provides: set[str]

        +__call__(ctx) StepContext

    }



    class StepContext {

        <<frozen dataclass>>

        +sample: str

        +metadata: MappingProxyType

        +replace(**kw) StepContext

    }



    class SampleResult {

        <<dataclass>>

        +context: StepContext

        +error: Exception?

        +ok: bool

    }



    StepProtocol <|.. Pipeline : satisfies

    StepProtocol <|.. Branch : satisfies

    StepProtocol <|.. YourStep : satisfies

    Pipeline *-- "1..*" StepProtocol : contains steps

    Branch *-- "2..*" Pipeline : contains pipelines

    StepProtocol ..> StepContext : receives & returns

    Pipeline ..> SampleResult : produces

```

`Pipeline` and `Branch` both satisfy `StepProtocol` through structural typing β€” no inheritance required. This means a `Pipeline` can be used as a step inside another pipeline, and a `Branch` slots into any step position.

| Concept | What it is | Threading | Data flow |
|---------|-----------|-----------|-----------|
| **Step** | Single unit of work | Sync internally | Receives and returns `StepContext` |
| **Pipeline** | Ordered chain of steps | `workers=N` across samples | Passes `StepContext` step-to-step |
| **Branch** | Parallel fork/join | One thread per branch | Copies context in, merges outputs |
| **Nested Pipeline** | Pipeline used as a step | Inherits parent threading | Same `StepContext` flow |

---

## Async Boundary β€” Background Processing

One of the engine's key features is the **async boundary**: a way to split a pipeline into foreground (fast return) and background (fire-and-forget) stages.

```mermaid

graph LR

    S1["Step A"] --> S2["Step B"] --> AB{{"async_boundary"}} --> S3["Step C<br/><small>background</small>"] --> S4["Step D<br/><small>background</small>"]



    style S1 fill:#6366f1,stroke:#4f46e5,color:#fff

    style S2 fill:#6366f1,stroke:#4f46e5,color:#fff

    style AB fill:#f59e0b,stroke:#d97706,color:#000

    style S3 fill:#3b82f6,stroke:#2563eb,color:#fff

    style S4 fill:#3b82f6,stroke:#2563eb,color:#fff

```

Mark any step with `async_boundary = True` β€” the pipeline returns results immediately after the foreground steps, while everything from the boundary onward continues in background threads. Use `pipe.wait_for_background()` when you need the final results.

This is critical for pipelines where early steps produce user-facing output quickly but later steps (analysis, logging, scoring) are slow and don't need to block the caller. See [Execution Model](execution.md) for full details.

---

## When to Use

!!! tip "Good fit"
    - Ordered multi-step processing with explicit data dependencies
    - Parallel fork/join patterns (multiple independent operations on the same data)
    - Fire-and-forget background processing with `async_boundary`
    - Any pipeline where you want construction-time contract validation

!!! note "Not designed for"
    - DAG scheduling with complex dependency graphs
    - Distributed computing across multiple machines
    - Stream processing with backpressure
    - ETL pipelines requiring a data catalog

---

## Installation

The pipeline engine is included in the project with no extra dependencies:

```python

from pipeline import Pipeline, Branch, StepContext, MergeStrategy, PipelineHook, CancellationToken

```

!!! tip "Using the Pipeline Engine with ACE"
    If you're building ACE pipelines, see [Composing Pipelines](../guides/composing-pipelines.md)

    for ACE-specific steps and patterns. All pipeline classes are also importable

    from `ace` directly: `from ace import Pipeline, Branch, ...`


---

## What's Next

- [**Quick Start**](quick-start.md) β€” Build and run your first pipeline in under 30 lines
- [**Core Concepts**](core-concepts.md) β€” Understand Step, Context, and the contract system
- [**Execution Model**](execution.md) β€” Three types of async, workers, and background processing
- [**Branching & Parallelism**](branching.md) β€” Parallel fork/join with merge strategies
- [**Error Handling**](error-handling.md) β€” Per-sample isolation, SampleResult, and error types
- [**Building Custom Steps**](custom-steps.md) β€” Create your own steps with dependency injection
- [**API Reference**](api-reference.md) β€” Complete signatures for all public classes