diff --git a/approaches/composable-recurrences.qmd b/approaches/composable-recurrences.qmd
new file mode 100644
index 0000000..20bbe39
--- /dev/null
+++ b/approaches/composable-recurrences.qmd
@@ -0,0 +1,279 @@
+---
+title: "Composable recurrences"
+description: "Build renewal processes, autoregressions and reporting delays from two fast, differentiable operators that compose."
+toc: true
+toc-location: left
+page-layout: full
+---
+
+:::::: {.side-space}
+
+::: {.what-is-turing-grid .approach-hero .section-start-space .section-end-space}
+
+::: {.feature-text-column}
+
+::: {.feature-item}
+::: {.feature-item-header}
+
+```{=html}
+
+```
+
+##### Two operators, many models
+
+:::
+
+:::
+
+::: {.feature-item}
+::: {.feature-item-header}
+
+```{=html}
+
+```
+
+##### Built for fast reverse-mode gradients
+
+:::
+
+:::
+
+::: {.feature-item}
+::: {.feature-item-header}
+
+```{=html}
+
+```
+
+##### No epidemiology built in
+
+:::
+
+:::
+
+:::
+
+::: {.code-example-column}
+::: {.code-window .box}
+::: {.code-window-header}
+::: {.code-window-dots}
+
+```{=html}
+
+```
+
+:::
+
+::: {.code-window-title}
+
+```{=html}
+renewal_then_delay.jl
+```
+
+:::
+
+:::
+
+```{.julia .code-overflow-scroll}
+using ComposableRecurrences
+
+# Generation interval, lag 1 first: weights on y[t-1], y[t-2], y[t-3]
+g = [0.6, 0.3, 0.1]
+# Two strata mixed by a contact matrix
+K = [0.9 0.1; 0.2 0.8]
+infections = Recurrence(g; coupling = K)
+
+# Reporting delay, lag 0 first: P(delay = 0, 1, 2, 3)
+reports = Convolution([0.0, 0.5, 0.3, 0.2])
+
+# Operators are callable. R_t is the gain, one column per day
+R = fill(1.1, 2, 10)
+y = infections(R; history = ones(2, 3))
+cases = reports(y)
+
+# Stop, keep the state, and resume for a forecast
+y1, state = infections(R[:, 1:5]; history = ones(2, 3),
+ return_state = true)
+y2 = infections(R[:, 6:end]; history = state)
+y ≈ hcat(y1, y2)
+```
+
+:::
+:::
+
+:::
+
+The example runs on the main branch of [ComposableRecurrences.jl](https://github.com/EpiAware/ComposableRecurrences.jl).
+The package is not registered yet, so install it from the repository to try it.
+
+## The approach
+
+Renewal processes, random walks, autoregressions and reporting delays share one structure.
+Each step reads a window of past values, weights it by a kernel, and produces the next value.
+This approach builds all of them from two operators rather than one bespoke loop per model.
+
+A `Recurrence` feeds its output back into its own history.
+At each step it convolves the last `L` values with its kernel, mixes strata through a coupling, scales by a gain, adds an input, and passes the result through a tuple of modifiers.
+A renewal process is a `Recurrence` with the generation interval as kernel and `R_t` as gain.
+A random walk is a `Recurrence([1.0])` with the noise as the added input.
+
+A `Convolution` has no feedback.
+It weights current and past inputs by a kernel, which covers reporting delays, moving averages and occupancy.
+
+The package has no epidemiological assumptions.
+It takes numbers in (kernels, gains, histories) and returns series out.
+Modelling packages build their own named steps on top.
+
+### Lag-first kernels, callable operators
+
+Every kernel is written lag first, the way a generation interval or AR coefficients are written down.
+A `Recurrence` kernel starts at lag 1 and a `Convolution` kernel at lag 0, so nothing is ever reversed by hand.
+Operators are built once, outside the model, and called with the per-day inputs that are usually sampled.
+Chaining is function composition, as in `reports(infections(R; history))`.
+
+### Strata, time variation and coupling
+
+Wrappers name the extra axis of a coefficient.
+`PerStratum` gives each stratum its own kernel, `TimeVarying` lets a kernel or coupling change by day, and `Pairwise` gives each pair of strata its own lag kernel.
+The coupling mixes strata before the gain, as a contact or mobility matrix does.
+Couplings can be dense, sparse or diagonal matrices, and any real element type is accepted, so `Float32` and dual numbers pass through.
+
+### Modifiers
+
+Modifiers are applied in order after each step and carry their own state.
+They handle what the linear core cannot, such as susceptible depletion or moving cases between strata.
+A modifier is a plain type with an `apply!` method, so a modelling package can add its own.
+
+### Plug-in adjoints
+
+Reverse-mode AD is slow when each step copies its lag window into a new state.
+The operators own a history buffer instead, which avoids that copy.
+Native adjoints will plug in per operator, modifier or coupling by adding a method, with no AD declarations from the user.
+`NoAdjoint(op)` turns an adjoint off, so its gain can be timed and the adjoint dropped if a backend catches up.
+A [benchmark script](https://github.com/EpiAware/ComposableRecurrences.jl/blob/main/benchmark/gradients.jl) in the repository times plain AD gradients of the operators under Mooncake and Enzyme.
+
+### How it fits with the other approaches
+
+[Composable Turing models](composable-turing-models.qmd) will build on these operators.
+Its renewal, delay and latent process steps become calls to `Recurrence` and `Convolution`, with the priors and observation models staying in the Turing layer.
+The [composed distributions](composed-distributions.qmd) stack supplies the delay pmfs that become `Convolution` kernels.
+The operators have no dependency on either, so they can also be used in other models.
+
+## Package map
+
+Solid arrows are data or code that will flow once the package is adopted.
+The dashed grey arrow is a test-time link.
+The dashed amber arrow is an optional future backend, not a dependency.
+
+```{=html}
+
+
+
+```
+
+The package has no runtime dependency on any other EpiAware package.
+Today ComposableTuringIDModels runs its own renewal, delay and latent process loops.
+Moving them onto one set of operators gives one forward kernel and one adjoint to maintain and test.
+
+## Status
+
+This approach is under development.
+The core operators are merged on the main branch of ComposableRecurrences.jl.
+These are `Recurrence`, `Convolution`, the `TimeVarying`, `PerStratum` and `Pairwise` wrappers, and the coupling and modifier interfaces.
+The test suite differentiates them with ForwardDiff, ReverseDiff, Mooncake and Enzyme through plain AD.
+Use-case tests against copies of the ComposableTuringIDModels steps they will replace are written but not yet switched on.
+
+The built-in modifiers (`Depletion`, `Imports`, `Redistribute`, `Clamp`) and the native Mooncake and Enzyme adjoints are in progress.
+A first registered release follows them, and then the port of ComposableTuringIDModels onto the operators.
+An FFT method for long fixed kernels and a Reactant backend are later extensions.
+
+## Where next
+
+- The [ComposableRecurrences.jl repository](https://github.com/EpiAware/ComposableRecurrences.jl)
+ tracks development, and its documentation will follow the first release.
+- Building a full model with priors and observations? See the
+ [composable Turing models approach](composable-turing-models.qmd).
+- Need the delay distributions that feed a `Convolution`? See the
+ [composed distributions approach](composed-distributions.qmd).
+
+::::::
diff --git a/approaches/index.qmd b/approaches/index.qmd
index 37bfd8b..c0a7470 100644
--- a/approaches/index.qmd
+++ b/approaches/index.qmd
@@ -44,7 +44,7 @@ This is our first cut, and we would like to be told where it is wrong.
## The approaches
We are not sure yet which of these will work best, so we are trying more than one and keeping them interoperable.
-Both work with the same underlying packages, and they can be combined in a single analysis.
+They are designed to work together and to combine in a single analysis.
::: {.feature-grid}
::: {.feature-card}
@@ -61,6 +61,13 @@ Any piece can be swapped for another without rewriting the rest.
[Read the approach →](composable-turing-models.qmd)
:::
+::: {.feature-card}
+### Composable recurrences
+Build renewal processes, autoregressions and reporting delays from two fast, differentiable operators, one that feeds back on its own history and one that does not.
+They carry no epidemiology of their own, and composable Turing models are planned to run on them.
+
+[Read the approach →](composable-recurrences.qmd)
+:::
:::
More approaches may join these as the ecosystem grows.
diff --git a/packages/index.qmd b/packages/index.qmd
index 867df1f..5cad9f1 100644
--- a/packages/index.qmd
+++ b/packages/index.qmd
@@ -40,6 +40,14 @@ Status reflects where a package sits in its release cycle rather than a judgemen
Assemble infectious disease models from interchangeable infection and observation parts as a single Turing model. The package that ties the ecosystem together.
Fast, differentiable recurrences and causal convolutions, the renewal, autoregressive and delay steps inside a model. The core operators are on the main branch but not yet registered, and native adjoints are in progress.