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
When configured, contextSchema validates the merged context once before this pipeline. Middleware retries reuse that context without revalidating it. See Schema Validation and Types.
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.
For generateText, output middleware receives text even when you request structured output. After output middleware and guardrails finish, changed text is parsed with the requested Output specification so result.text and result.output stay consistent. Keep the replacement compatible with the schema; parsing failures reject the call without retrying the model or replaying tools. Unchanged text retains the original parsed output. See Generated Output With Middleware and Guardrails.
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. Tools completed during the previous attempt can execute again; use idempotency checks for actions with side effects.
- 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 to repeat only the failed provider call within the SDK loop, retaining completed steps and tool results. The retry counter resets for each new model step. For model fallback configuration, see Retries and Fallbacks.