> The complete documentation index is at [llms.txt](/llms.txt).

In Bauplan, pipelines are implicitly defined by chaining models together
through their declared inputs and outputs. Pipelines take the form of
Directed Acyclic Graphs (DAGs).

When you define a model, you only declare which inputs come from which models. Bauplan
analyzes the dependency graph based on model definitions and automatically infers the
correct execution order.

To declare model dependencies (chain models into a DAG), annotate a function parameter
with a Bauplan model, using `Annotated` and `bauplan.Model`:

```python
from typing import Annotated
import bauplan

class StepOutput(bauplan.TableSchema):
    """The columns each step passes to the next."""

    col: bauplan.Int64 | None

@bauplan.model()
@bauplan.python('3.11')
def step_1(
    data: Annotated[pyarrow.Table, bauplan.Model('input_table')],
) -> Annotated[pyarrow.Table, StepOutput]:
    return data

@bauplan.model()
@bauplan.python('3.11')
def step_2(
    data: Annotated[pyarrow.Table, bauplan.Model('step_1')],
) -> Annotated[pyarrow.Table, StepOutput]:
    return data

@bauplan.model()
@bauplan.python('3.11')
def step_3(
    data: Annotated[pyarrow.Table, bauplan.Model('step_2')],
) -> Annotated[pyarrow.Table, StepOutput]:
    return data
```

## Pipeline constraints

Models can take multiple tabular inputs but must return a single tabular
output. Currently, `pyarrow.Table` is the only supported return type.

✅ This is a valid Bauplan DAG, every model has 1 output.

```text
┌────────────┐
│  Model 1   │──────────────┐
└────────────┘              │
                            ▼
┌────────────┐      ┌────────────┐      ┌────────────┐
│  Model 2   │─────►│  Model 3   │─────►│  Model 4   │
└────────────┘      └────────────┘      └────────────┘
                            ▲
┌────────────┐              │
│ Iceberg    │──────────────┘
│  Table     │
└────────────┘
```

❌ This is **NOT** a valid Bauplan DAG, Model 2 returns 2 tables.

```text
                                        ┌────────────┐
                                    ┌──►│ Iceberg    │
                                    │   │  Table A   │
                                    │   └────────────┘
┌────────────┐      ┌────────────┐  │
│  Model 1   │─────►│  Model 2   │──┤
└────────────┘      └────────────┘  │
                                    │   ┌────────────┐
                                    └──►│ Iceberg    │
                                        │  Table B   │
                                        └────────────┘
```

Write two models instead. Both can read the same upstream model.

## Best practices

To keep your project organized and make it easy to run, test, and
inspect individual pipelines, we recommend the following structure. See
[Projects](/concepts/projects) for more details.

```text
my_project/
└── pipelines/
    ├── sales_reporting/
    │   ├── models.py
    │   └── bauplan_project.yml
    ├── customer_segmentation/
    │   ├── models.py
    │   └── bauplan_project.yml
    └── churn_prediction/
        ├── models.py
        └── bauplan_project.yml
```

- Group models that form a logical pipeline into a single file:
  `models.py`.
- Place that file inside a folder named after the pipeline
  (`sales_reporting`, `churn_prediction`, etc.).
- Each pipeline folder contains its own `bauplan_project.yml` file,
  which defines environment settings and dependencies for that
  pipeline only.
- Separate function bodies into external modules and call them within
  the Bauplan models. This keeps business logic code neatly separated
  from the DAG and environment declaration, making code refactoring
  easier and future-proof.

This layout:

- Makes each pipeline self-contained and easy to run with
  `bauplan run` from inside the folder.
- Keeps models modular and promotes reuse.
- Helps enforce consistent dependency tracking and ref resolution.