# Headroom Pipeline Lifecycle Explained: Setup, Post-Send, and Response Received

> Understand the Headroom pipeline lifecycle stages: Setup, Post-Send, and Response Received. Learn how the PipelineStage enum and PipelineEvent enable request observation and mutation.

- Repository: [Tejas Chopra/headroom](https://github.com/chopratejas/headroom)
- Tags: deep-dive
- Published: 2026-06-20

---

**The Headroom pipeline lifecycle consists of three canonical stages—Setup, Post-Send, and Response Received—defined by the `PipelineStage` enum in [`headroom/pipeline.py`](https://github.com/chopratejas/headroom/blob/main/headroom/pipeline.py), allowing extensions to observe and mutate requests via `PipelineEvent` emissions at each transition point.**

Headroom implements a canonical pipeline lifecycle that orchestrates every request from initialization through response delivery. This lifecycle is defined by the `PipelineStage` enum in the core module [`headroom/pipeline.py`](https://github.com/chopratejas/headroom/blob/main/headroom/pipeline.py), where each stage emits a `PipelineEvent` that extension plug-ins can observe and mutate. Understanding these three stages—Setup, Post-Send, and Response Received—is essential for building custom extensions that intercept, log, or transform LLM requests and responses.

## The Three Stages of the Headroom Pipeline Lifecycle

The Headroom pipeline lifecycle provides three distinct interception points for extensions. Each stage represents a specific phase in the request journey, from client initialization through final response delivery.

### Setup Stage: Pipeline Initialization

The **Setup** stage (`PipelineStage.SETUP`) executes immediately after a `HeadroomClient` instance is created, before any request processing begins. This stage initializes the pipeline, loads configuration, and discovers registered extensions via the entry-point group `headroom.pipeline_extension`.

During Setup, the `PipelineExtensionManager` constructs the default transform list and instantiates extensions. According to the source code in [`headroom/client.py`](https://github.com/chopratejas/headroom/blob/main/headroom/client.py), this occurs during client initialization when the manager is built and attached to the request-handling code.

Extensions can use this stage to pre-populate configuration, register custom transforms, or perform one-time warm-up work such as loading models or establishing persistent connections.

### Post-Send Stage: Outbound Request Observation

The **Post-Send** stage (`PipelineStage.POST_SEND`) triggers after the transformed request body has been sent to the upstream provider (OpenAI, Anthropic, etc.) but before the response returns to the caller. This stage emits a `PipelineEvent` containing the final request payload, tool definitions, and request metadata.

In [`headroom/proxy/handlers/openai.py`](https://github.com/chopratejas/headroom/blob/main/headroom/proxy/handlers/openai.py), lines 2153-2190 demonstrate this emission: after the body is prepared, the handler calls `pipeline_extensions.emit(PipelineStage.POST_SEND, …)`. This gives extensions a final opportunity to log, audit, or decorate the outbound request, such as adding custom headers or triggering distributed tracing.

### Response Received Stage: Inbound Response Processing

The **Response Received** stage (`PipelineStage.RESPONSE_RECEIVED`) fires immediately after the upstream provider's response has been received and decoded (JSON or streaming SSE). This stage emits an event carrying the raw response, status code, and additional metadata.

As implemented in [`headroom/proxy/handlers/openai.py`](https://github.com/chopratejas/headroom/blob/main/headroom/proxy/handlers/openai.py) (lines 2200-2208), the handler emits this event after a non-streaming call via `pipeline_extensions.emit(PipelineStage.RESPONSE_RECEIVED, …)`. Extensions can inspect, modify, or log the response before it reaches the SDK user, enabling use cases like error translation, compliance tagging, or PII redaction.

## How the Stages Connect

The Headroom pipeline lifecycle follows a deterministic sequence:

1. **Setup** – When `HeadroomClient.__init__` executes, `PipelineExtensionManager` discovers extensions via `discover_pipeline_extensions()` and attaches the manager to request-handling code.

2. **Transform Processing** – The request passes through the **transform pipeline** (`TransformPipeline.apply` in [`headroom/transforms/pipeline.py`](https://github.com/chopratejas/headroom/blob/main/headroom/transforms/pipeline.py)), which mutates messages without emitting lifecycle events.

3. **Post-Send** – After the transformed request is handed to the upstream LLM provider, the `POST_SEND` event fires, providing the last chance to observe the outbound payload.

4. **Response Handling** – When the provider's reply arrives and is parsed, the `RESPONSE_RECEIVED` event fires, allowing inspection of the inbound payload before returning to the caller.

The lifecycle is deliberately stable; external plug-ins can rely on the `PipelineStage` enum values without worrying about internal refactors.

## Implementing Pipeline Extensions

Extensions implement the `PipelineExtension` base class and override `on_pipeline_event` to handle specific stages.

### Logging the Request After Send

This extension logs model and token information during the Post-Send stage:

```python

# my_extension.py

from headroom.pipeline import PipelineStage, PipelineEvent, PipelineExtension

class LogPostSend(PipelineExtension):
    def on_pipeline_event(self, event: PipelineEvent) -> PipelineEvent | None:
        if event.stage == PipelineStage.POST_SEND:
            # `event.messages` holds the transformed request payload

            print(f"[POST_SEND] model={event.model} tokens={len(event.messages)}")
        return None

```

Register via Python entry points in [`setup.cfg`](https://github.com/chopratejas/headroom/blob/main/setup.cfg):

```ini
[options.entry_points]
headroom.pipeline_extension =
    log_post_send = my_extension:LogPostSend

```

### Injecting Custom Headers

This extension adds headers after the request is sent:

```python
class HeaderInjector(PipelineExtension):
    def on_pipeline_event(self, event: PipelineEvent) -> PipelineEvent | None:
        if event.stage == PipelineStage.POST_SEND:
            # Mutate the outbound headers in-place

            if event.headers is None:
                event.headers = {}
            event.headers["X-Request-Id"] = event.request_id
        return event

```

### Modifying the Response Body

This extension strips debug information from responses:

```python
class ResponseSanitizer(PipelineExtension):
    def on_pipeline_event(self, event: PipelineEvent) -> PipelineEvent | None:
        if event.stage == PipelineStage.RESPONSE_RECEIVED:
            # Example: strip any internal debug fields

            if isinstance(event.response, dict):
                event.response.pop("debug_info", None)
        return event

```

## Key Implementation Files

Understanding the Headroom pipeline lifecycle requires familiarity with these core files:

- **[`headroom/pipeline.py`](https://github.com/chopratejas/headroom/blob/main/headroom/pipeline.py)** – Defines the `PipelineStage` enum (including SETUP, POST_SEND, RESPONSE_RECEIVED) and the `PipelineExtensionManager` that dispatches events to registered extensions.

- **[`headroom/proxy/handlers/openai.py`](https://github.com/chopratejas/headroom/blob/main/headroom/proxy/handlers/openai.py)** – Demonstrates real-world emission of POST_SEND (lines 2153-2190) and RESPONSE_RECEIVED (lines 2200-2208) events during OpenAI request processing.

- **[`headroom/transforms/pipeline.py`](https://github.com/chopratejas/headroom/blob/main/headroom/transforms/pipeline.py)** – Implements the core `TransformPipeline` that runs between SETUP and POST_SEND, providing token-aware compression and message transformation.

- **[`headroom/client.py`](https://github.com/chopratejas/headroom/blob/main/headroom/client.py)** – Constructs the `PipelineExtensionManager` during client initialization, serving as the entry point for the SETUP stage.

- **[`headroom/transforms/base.py`](https://github.com/chopratejas/headroom/blob/main/headroom/transforms/base.py)** – Provides the base class for all transforms, essential when building custom transforms that participate in the pipeline between SETUP and POST_SEND.

## Summary

- The **Headroom pipeline lifecycle** comprises three canonical stages: **Setup**, **Post-Send**, and **Response Received**, defined in [`headroom/pipeline.py`](https://github.com/chopratejas/headroom/blob/main/headroom/pipeline.py).
- **Setup** initializes extensions and configuration when `HeadroomClient` is instantiated.
- **Post-Send** fires after the request is transmitted to the upstream provider, allowing observation and mutation of outbound payloads.
- **Response Received** triggers after the provider's response is decoded, enabling inspection and modification before returning to the caller.
- Extensions register via the `headroom.pipeline_extension` entry point and implement `on_pipeline_event` to handle specific stages.
- The lifecycle is stable across versions, with explicit emission points in [`headroom/proxy/handlers/openai.py`](https://github.com/chopratejas/headroom/blob/main/headroom/proxy/handlers/openai.py).

## Frequently Asked Questions

### When does the Setup stage run in the Headroom pipeline lifecycle?

The Setup stage runs immediately after `HeadroomClient` instantiation, before any request is processed. During this phase, the `PipelineExtensionManager` discovers extensions via `discover_pipeline_extensions()` and builds the default transform list. This occurs in [`headroom/client.py`](https://github.com/chopratejas/headroom/blob/main/headroom/client.py) during client initialization, making it the ideal time for one-time configuration and warm-up activities.

### How do I register a custom extension for the Post-Send stage?

Register your extension by defining a Python entry point in your package configuration under the group `headroom.pipeline_extension`. Your class should inherit from `PipelineExtension` and check `event.stage == PipelineStage.POST_SEND` in the `on_pipeline_event` method. The extension will automatically receive events after the request is sent to the upstream provider.

### Can I modify the response body in the Response Received stage?

Yes, the Response Received stage allows mutation of the response before it reaches the SDK user. The `PipelineEvent` passed to your extension contains the `response` attribute, which you can modify in-place or replace entirely. This enables use cases like stripping debug fields, redacting sensitive information, or translating error formats.

### What happens between the Setup and Post-Send stages?

Between Setup and Post-Send, the request passes through the **Transform Pipeline** (`TransformPipeline.apply` in [`headroom/transforms/pipeline.py`](https://github.com/chopratejas/headroom/blob/main/headroom/transforms/pipeline.py)). This phase applies token-aware compression, content routing, and message transformations. Unlike the lifecycle stages, the transform pipeline does not emit events; it simply mutates the request messages before they are forwarded to the upstream provider.