Headroom Pipeline Lifecycle Stages and How to Hook Into Them

Headroom exposes eleven canonical pipeline lifecycle stages—from setup to response_received—and lets you intercept each one by implementing the PipelineExtension protocol or passing hooks directly to HeadroomClient.

Headroom is an open-source LLM proxy that processes every request through a finite-state machine defined in headroom/pipeline.py. Understanding the Headroom pipeline lifecycle stages is essential if you want to log traffic, mutate prompts, or skip transforms. The entire flow is expressed as the PipelineStage enum and enforced by the CANONICAL_PIPELINE_STAGES sequence.

The 11 Canonical Headroom Pipeline Lifecycle Stages

The canonical pipeline is enumerated in headroom/pipeline.py as CANONICAL_PIPELINE_STAGES and covers the full lifespan of a proxy request. According to the chopratejas/headroom source code, the stages are:

  • setup – Early initialization, such as loading configuration.
  • pre_start – One-time warm-up work before the first request is accepted.
  • post_start – The proxy is actively listening for inbound connections.
  • input_received – Raw request payloads, messages, tools, and headers arrive.
  • input_cached – The request is stored in the request cache when caching is enabled.
  • input_routed – The request is routed to the designated LLM provider.
  • input_compressed – Compression transforms are applied to the payload.
  • input_remembered – The request (or its compressed form) is written to the memory store.
  • pre_send – Final checkpoint just before dispatching to the LLM provider.
  • post_send – The provider has returned a response, but post-processing has not yet run.
  • response_received – The final response (messages, tools, metadata) is ready for the client.

How to Hook Into Headroom Pipeline Stages

Headroom’s extensibility model is built on pipeline extensions. An extension is any object implementing the PipelineExtension protocol defined in headroom/pipeline.py. Each extension must expose a single method, on_pipeline_event, which receives a PipelineEvent and returns either a modified event or None.

Writing a Pipeline Extension

Below is a minimal extension that logs each stage and mutates metadata. Because the PipelineEvent dataclass is mutable, you can modify fields such as messages or metadata in place.


# my_extension.py

from headroom.pipeline import PipelineEvent, PipelineStage

class LogStageExtension:
    """Example extension that logs each stage and can mutate the event."""
    def on_pipeline_event(self, event: PipelineEvent) -> PipelineEvent | None:
        print(f"[hook] {event.stage.value} – operation={event.operation}")
        # Example mutation: add a flag to metadata

        event.metadata["hooked"] = True
        return event          # returning a new PipelineEvent also works

Registering Extensions via Python Entry Points

The recommended discovery mechanism uses Python entry points. The PipelineExtensionManager loads every distribution registered under the group headroom.pipeline_extension, which is stored in the ENTRY_POINT_GROUP constant in headroom/pipeline.py.

Add the following to your package’s setup.cfg:

[options.entry_points]
headroom.pipeline_extension =
    log_stage = my_extension:LogStageExtension

When the proxy starts, the manager automatically discovers and instantiates your extension.

Manually Attaching Extensions to the Client

If you prefer explicit registration over auto-discovery, pass an extension instance directly to HeadroomClient via the pipeline_extensions argument. As implemented in headroom/client.py, the constructor forwards your hooks to a PipelineExtensionManager created under the hood.

from headroom.client import HeadroomClient
from my_extension import LogStageExtension

client = HeadroomClient(
    # … other client arguments …

    pipeline_extensions=LogStageExtension(),   # or a list of extensions

)

The PipelineEvent Object and Mutable Payloads

Every hook receives a PipelineEvent dataclass instance defined in headroom/pipeline.py. This object carries the full context of the current request and is designed to be mutated in flight. Its key fields include:

  • stage – The current PipelineStage enum member.
  • operation – A string such as "compress" or "chat".
  • request_id, provider, model – Traceability identifiers.
  • messages, tools, headers – Mutable request payloads.
  • response – Populated only during later stages (post_send, response_received).
  • metadata – A dictionary for arbitrary flags and data shared across extensions.

Practical Pipeline Hook Examples

Simple Logger

This extension prints every stage to stdout and returns the event unchanged.


# logger_extension.py

from headroom.pipeline import PipelineEvent, PipelineStage

class SimpleLogger:
    def on_pipeline_event(self, event: PipelineEvent):
        print(f"Stage: {event.stage.value}")
        return event

Suppressing Compression for Specific Requests

Extensions can override behavior by mutating the event. Because the PipelineExtensionManager.emit method is fail-open, any unhandled exception inside a hook is logged and the pipeline continues with the current event.

class SkipCompression:
    def on_pipeline_event(self, event: PipelineEvent):
        if event.stage == PipelineStage.INPUT_COMPRESSED:
            # Pretend compression already happened

            event.messages = event.metadata.get("original_messages")
        return event

Manual Client Integration

You can attach the logger directly without entry points by passing it to the client constructor.

from headroom.client import HeadroomClient
from logger_extension import SimpleLogger

client = HeadroomClient(
    api_key="…",                     # (placeholder – not shown)

    pipeline_extensions=SimpleLogger(),
)
response = client.chat(messages=[{"role": "user", "content": "Hi"}])

Fail-Open Error Handling

The PipelineExtensionManager.emit method wraps each extension call in a try/except block. If a hook raises an exception, Headroom logs the error and continues processing the request with the existing PipelineEvent state. This fail-open design guarantees that a misbehaving extension cannot crash the proxy.

Summary

  • Headroom defines eleven canonical pipeline lifecycle stages in headroom/pipeline.py, ranging from setup through response_received.
  • You hook into them by implementing the PipelineExtension protocol and its on_pipeline_event method.
  • Extensions can be auto-discovered via the headroom.pipeline_extension Python entry-point group or attached manually through the HeadroomClient constructor.
  • Each hook receives a mutable PipelineEvent containing payloads, metadata, and stage context.
  • The pipeline is fail-open; exceptions in extensions are logged but never halt a request.

Frequently Asked Questions

What are the Headroom pipeline lifecycle stages?

The eleven stages are setup, pre_start, post_start, input_received, input_cached, input_routed, input_compressed, input_remembered, pre_send, post_send, and response_received. They represent the full journey of a request through the Headroom proxy, as defined by the PipelineStage enum in headroom/pipeline.py.

How do I register a custom Headroom pipeline extension?

Implement the PipelineExtension protocol—specifically the on_pipeline_event method—and register it under the Python entry-point group headroom.pipeline_extension in your package metadata. Headroom’s PipelineExtensionManager automatically discovers and loads it at startup. Alternatively, instantiate the extension and pass it directly to HeadroomClient via pipeline_extensions.

Can a pipeline extension modify the request before it reaches the LLM provider?

Yes. The PipelineEvent object passed to on_pipeline_event contains mutable fields such as messages, tools, headers, and metadata. You can mutate these in place and return the event; Headroom will carry your changes forward to subsequent stages, including pre_send just before the provider call.

What happens if my Headroom pipeline extension throws an exception?

The PipelineExtensionManager.emit method catches exceptions in a try/except block and logs them. Because the pipeline is designed to be fail-open, the error does not halt request processing; the proxy simply continues with the current PipelineEvent state.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →