Agent Middleware
Middleware runs before guardrails and hooks on each agent call. Use it to read or modify input and output, or to stop a call with a typed error.
Execution Order
generateText and generateObject:
- Input middlewares
- Input guardrails
- Hooks (
onStart) - Model call (with retries and fallback)
- Output middlewares
- Output guardrails
- Hooks (
onEnd)
streamText and streamObject:
- Input middlewares
- Input guardrails
- Stream starts
Output middlewares do not run in streaming calls.
Configure Middleware
Agent-level middlewares run before per-call middlewares. Per-call middlewares are appended to the list for that call.
import { Agent, createInputMiddleware, createOutputMiddleware } from "@voltagent/core";
const normalizeInput = createInputMiddleware({
name: "NormalizeInput",
handler: ({ input }) => {
if (typeof input !== "string") return input;
return input.trim();
},
});
const requireSignature = createOutputMiddleware<string>({
name: "RequireSignature",
handler: ({ output, abort }) => {
if (!output.includes("-- Support")) {
abort("Missing signature", { retry: true, metadata: { reason: "signature" } });
}
return output;
},
});
const agent = new Agent({
name: "Support",
instructions: "Answer support questions with short, direct replies.",
model: "openai/gpt-4o-mini",
maxMiddlewareRetries: 1,
inputMiddlewares: [normalizeInput],
outputMiddlewares: [requireSignature],
});
Per-call middleware:
await agent.generateText("hi", {
inputMiddlewares: [({ input }) => (typeof input === "string" ? `Customer: ${input}` : input)],
});
Middleware API
Input middleware receives:
input: current input valueoriginalInput: input from the calleragent: agent instancecontext: operation contextoperation: operation name (for examplegenerateText)retryCount: current middleware retry countabort(reason?, options?): stop the call or request a retry
Output middleware receives:
output: current output valueoriginalOutput: output from the modelusage,finishReason,warnings: model metadata when availableagent,context,operation,retryCount,abort(...)
Return a new value to replace the input or output; return void to keep the current value.
Retry Behavior
Middleware retries are controlled by maxMiddlewareRetries. The default is 0.
- Call
abort("reason", { retry: true })to request a retry. - Each retry restarts the full attempt: input middleware, guardrails, hooks, model selection, model retries/fallback, output middleware.
- The retry reason is added as a system message for the next attempt.
retryCountstarts at0and increments after each middleware-triggered retry.- If
maxMiddlewareRetriesis exceeded, the middleware error is returned to the caller. - In streaming calls, retry can only happen before the stream starts, so only input middlewares can trigger it.
Middleware retries are separate from model retries. Model retries use maxRetries and are evaluated per model call. For model fallback configuration, see Retries and Fallbacks.