---
url: 'https://adk.nht.io/the-loop/pipelines/what-each-pipeline-owns.md'
description: >-
  Four pipelines, two scopes, one cost model. What goes in each is convention —
  and confusing the conventions is how you end up paying for retrieval ten times
  in a ten-iteration dispatch.
---

# What each pipeline owns

## LLM summary — What each pipeline owns

* The four pipelines are **conventions**, not enforced contracts. Nothing in the runner stops you from dispatching tools in `turnInputPipeline` or persisting in `dispatchInputPipeline`. The conventions exist because following them keeps cost in the right scope and code legible.
* Order of fire on a single turn: `turnInputPipeline` → `dispatchInputPipeline` → executor → `dispatchOutputPipeline` → `turnOutputPipeline`. The middle three repeat for each iteration of the dispatch.
* **`turnInputPipeline`** (turn, once, before dispatch): assemble what the model is about to see — retrieve [`Retrievable`](https://adk.nht.io/api/@nhtio/adk/common/classes/Retrievable)s, load and score [`Memory`](https://adk.nht.io/api/@nhtio/adk/common/classes/Memory), pack history, apply budgets, enforce inbound policy, refuse the turn before the model sees it.
* **`dispatchInputPipeline`** (dispatch, per iteration, before the executor): shape what *this* iteration sees — bound the loop with `ctx.iteration`, mutate `ctx.turnMessages` for retry semantics, decide what changes between iterations.
* **`dispatchOutputPipeline`** (dispatch, per iteration, after the executor): inspect what *this* iteration produced — call `ctx.ack()` when done, detect tool-call repetition with `ctx.toolCallCount`, postprocess streaming output.
* **`turnOutputPipeline`** (turn, once, after dispatch): post-hoc turn work — apply post-hoc safety, update memories that depend on the completed turn, record turn telemetry, mutate already-persisted records if needed. In the typical setup, record persistence and tool execution happened during dispatch (the executor calls `ctx.storeMessage` / `storeThought` / `storeToolCall` itself, and `tool.executor(ctx)(args)` runs inside the executor before the next iteration so the model can see results); this pipeline is for what should happen *after* all of that has settled, not for re-invoking handlers.
* Dispatch-only primitives on [`DispatchContext`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext) ([`DispatchContext.iteration`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext#property-iteration), [`DispatchContext.ack`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext#ack), [`DispatchContext.nack`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext#nack), [`DispatchContext.onAck`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext#onack), [`DispatchContext.toolCallCount`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext#toolcallcount)) — see [Signalling](../llm-dispatch/signalling).
* Dispatch-pipeline throws (both `dispatchInputPipeline` and `dispatchOutputPipeline`) share one class: `E_DISPATCH_PIPELINE_ERROR`. Turn-pipeline throws split: `E_INPUT_PIPELINE_ERROR` and `E_OUTPUT_PIPELINE_ERROR`.
* Common mistake: doing turn-level work (retrieval, memory loading, history packing) inside `dispatchInputPipeline`. It runs once per iteration. Retrieval in a ten-iteration dispatch costs ten times what it should.
* Detail pages: [Turn-scoped pipelines](./turn-scoped) covers `turnInputPipeline` and `turnOutputPipeline`. [Dispatch-scoped pipelines](./dispatch-scoped) covers `dispatchInputPipeline` and `dispatchOutputPipeline`.

::: tip TL;DR
**Four pipelines, two scopes.** Two of them fire once per *turn* (bookends — before and after the model is dispatched). Two of them fire once per *iteration* of the dispatch loop (the sandwich around each call to the executor).

The reason it splits like this is a cost story: turn-scoped work runs once, dispatch-scoped work runs every iteration. Put retrieval in the wrong pipeline and a ten-iteration dispatch costs you ten times what it should. The pipelines do not enforce this — the conventions exist precisely *because* the runner won't catch you doing it wrong.
:::

## The mental model

The pipelines do not enforce what runs inside them. Nothing stops you from dispatching a tool from `turnInputPipeline` or doing retrieval inside `dispatchInputPipeline`. The contracts are about *when* a pipeline fires and *what context* its middlewares see. *What they do* is convention.

Convention because the cost model is unforgiving. Turn-level work runs once per turn; dispatch-level work runs once per iteration. Confuse them and you pay for retrieval ten times in a ten-iteration dispatch, or you persist a half-built record before the dispatch has decided what the record is.

Top to bottom is one turn: `turnInputPipeline` builds context, the dispatch loop runs (`dispatchInputPipeline` → executor → `dispatchOutputPipeline`, repeated until `ack`, `nack`, or abort), and on `ack` — only on `ack` — `turnOutputPipeline` performs post-hoc turn work like safety, memory, and telemetry.

## The four pipelines at a glance

| Pipeline | Scope | When | Owns |
| --- | --- | --- | --- |
| [`turnInputPipeline`](./turn-scoped#input-middleware) | Turn | Once, before dispatch | Assemble what the model is about to see |
| [`dispatchInputPipeline`](./dispatch-scoped#dispatchinputpipeline) | Dispatch | Per iteration, before executor | Shape what *this* iteration sees |
| [`dispatchOutputPipeline`](./dispatch-scoped#dispatchoutputpipeline) | Dispatch | Per iteration, after executor | Decide whether the loop continues |
| [`turnOutputPipeline`](./turn-scoped#output-middleware) | Turn | Once, after dispatch (only on `ack`) | Post-hoc turn work — safety, memory, telemetry |

The split into two scopes is deliberate: turn-level cost should not be paid on every iteration, and a dispatch-scoped helper has no business leaking into the next turn. Turn-scoped pipelines see the [`TurnContext`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/TurnContext); dispatch-scoped pipelines see the [`DispatchContext`](https://adk.nht.io/api/@nhtio/adk/types/interfaces/DispatchContext), which adds the executor-iteration primitives.

## Detail pages

* [Turn-scoped pipelines](./turn-scoped) — `turnInputPipeline` (assemble the model's view) and `turnOutputPipeline` (post-hoc turn work).
* [Dispatch-scoped pipelines](./dispatch-scoped) — `dispatchInputPipeline` (per-iteration setup) and `dispatchOutputPipeline` (per-iteration decision).

## Where to go next

* [Pipelines](../pipelines), [Composition](./composition), [`stash`](./stash).
* [LLM Dispatch](../llm-dispatch), [Tools](../tools).
