Session bundles compaction, retry, persistence, turn tracking, and hooks into a single class. If you want more control — composing only the behaviors you need, or writing custom middleware — use the functional Runner / Middleware / Conversation API instead.
Runners and Middleware
ARunner is an async generator function with the same shape as agent.run(). A Middleware transforms one Runner into another. You compose them with apply():
apply() wraps outermost. The ordering above means: turn tracking brackets everything, compaction runs once before retries, retry wraps only the agent call, and persistence saves after a successful response.
Available Middleware
| Middleware | Description |
|---|---|
withTurnTracking() | Emits turn.start/turn.done events. Maintains a turn counter across calls. |
withCompaction(config) | Auto-compacts history when approaching the context window limit. Tracks lastInputTokens from step.done events. |
withRetry(config?) | Retries on transient API errors (429, 500, etc.) with exponential backoff. Only retries before content has been streamed. |
withPersistence(config) | Auto-saves messages to a SessionStore on every done event. |
withHooks(hooks) | Applies SessionHooks (onBeforeSend, onAfterResponse, onError) around the inner runner. |
Conversation
Conversation is a thin stateful wrapper over a composed Runner. It manages messages (updating from done events) and provides the same toUIMessageStream() and toResponse() methods as Session for AI SDK 5 integration:
Stream Combinators
For lightweight event stream transforms, four curried combinators are available:Writing Custom Middleware
A middleware is a function that takes a Runner and returns a Runner:Composing with pipe
For reusable middleware stacks, use pipe() to create a combined middleware: