ag2-middleware
Skill252 starsChanged 6 months agoApache-2.0View on GitHub
Intercept the AG2 beta agent loop with `BaseMiddleware` — wrap full turns (`on_turn`), each LLM call (`on_llm_call`), each tool execution (`on_tool_execution`), or each human-input request (`on_human_input`). Use for retry, logging, history trimming, request mutation, tool auditing, guardrails, or rate limiting. Built-ins: `LoggingMiddleware`, `RetryMiddleware`, `HistoryLimiter`, `TokenLimiter`, `TelemetryMiddleware` (see `ag2-telemetry`). For per-tool hooks see also `ag2-add-custom-tool` tool-middleware section.
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 67
Add temporary middleware for one turn.
- Distinctive: line 76
Call-level middleware is appended after the agent's middleware list.
- Very specific: line 130
If your middleware needs constructor args beyond event and context, wrap with Middleware(YourClass, ...) when registering.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Middleware
When to use
Middleware is for cross-cutting behaviour that should apply consistently across many runs without changing the agent, model client, or tools themselves. Common use cases:
- Logging, tracing, timing
- Retry on transient failures
- Trim history before it reaches the model
- Cap or estimate token usage
- Rewrite tool arguments / results
- Enforce policies before a tool runs
- Audit human-input requests
Four hooks
BaseMiddleware exposes four async hooks. Implement only the ones you need:
| Hook | Wraps | Use for |
on_turn(call_next, event, context) → ModelResponse | The whole agent turn | Total latency, request/response inspection, turn-level policies |
on_llm_call(call_next, events, context) → ModelResponse | Each LLM API call | Retry, logging, history trim, request mutation, caching |
on_tool_execution(call_next, event, context) → ToolResultType | Each tool invocation | Validate args, redact results, fallback on failure, access control |
on_human_input(call_next, event, context) → HumanMessage | Each context.input() | Audit, rewrite prompts, automated short-circuit, rate limit |
Each instance is created once per turn and can hold per-turn state on self. The same instance can implement multiple hooks.
Built-in middleware
Importable from autogen.beta.middleware:
| Middleware | Purpose | Constructor |
LoggingMiddleware | Logs turn start/end, each LLM call, each tool execution | no args |
RetryMiddleware | Retries failed LLM calls | max_retries=N, retry_on=ExceptionClass |
HistoryLimiter | Cap event count before LLM call | max_events=N |
TokenLimiter | Char-based token-budget cap before LLM call | max_tokens=N, chars_per_token=4 |
TelemetryMiddleware | OpenTelemetry GenAI spans (see ag2-telemetry) | see telemetry skill |
Registration — agent-level
Apply to every turn:
from autogen.beta import Agent
from autogen.beta.config import OpenAIConfig
from autogen.beta.middleware import LoggingMiddleware, RetryMiddleware
agent = Agent(
"assistant",
config=OpenAIConfig(model="gpt-4o-mini"),
middleware=[
LoggingMiddleware(),
RetryMiddleware(max_retries=2),
],
)Registration — call-level
Add temporary middleware for one turn. Both agent.ask(...) and reply.ask(...) accept it:
from autogen.beta.middleware import TokenLimiter
reply = await agent.ask("Summarise the latest messages.", middleware=[LoggingMiddleware()])
next_turn = await reply.ask("Now answer in one paragraph.", middleware=[TokenLimiter(max_tokens=4000)])Call-level middleware is appended after the agent's middleware list.
Ordering
Middleware runs in registration order, like nested with blocks. Registering [A, B, C] enters A → B → C and unwinds C → B → A:
enter A
enter B
enter C
<LLM call>
exit C
exit B
exit AThis matters when you mix logging, mutation, retry. If RetryMiddleware should retry mutated requests, mutation goes inside retry; if you want each retry attempt logged separately, logging goes inside retry.
Writing your own
Subclass BaseMiddleware, implement the hooks you need:
import logging
from collections.abc import Sequence
from autogen.beta import Agent, Context
from autogen.beta.config import OpenAIConfig
from autogen.beta.events import BaseEvent, ModelResponse, ToolCallEvent
from autogen.beta.middleware import BaseMiddleware, LLMCall, Middleware, ToolExecution
class AuditMiddleware(BaseMiddleware):
def __init__(self, event: BaseEvent, context: Context, logger: logging.Logger) -> None:
super().__init__(event, context)
self.logger = logger
async def on_llm_call(self, call_next: LLMCall, events: Sequence[BaseEvent], context: Context) -> ModelResponse:
self.logger.info("Calling model with %d events", len(events))
response = await call_next(events, context)
self.logger.info("Model returned: %s", response)
return response
async def on_tool_execution(self, call_next: ToolExecution, event: ToolCallEvent, context: Context):
self.logger.info("Executing tool: %s", event.name)
return await call_next(event, context)
agent = Agent(
"assistant",
config=OpenAIConfig(model="gpt-4o-mini"),
middleware=[
Middleware(AuditMiddleware, logger=logging.getLogger("ag2.audit")),
],
)If your middleware needs constructor args beyond event and context, wrap with Middleware(YourClass, ...) when registering. Zero-config middleware can be passed bare (middleware=[LoggingMiddleware()]).
Tool-scoped vs agent-scoped
For behaviour that applies to one tool only (validation, redaction for that tool's output, approval gates), use tool middleware instead — middleware=[hook] on @tool, @agent.tool, or Toolkit. See ag2-add-custom-tool for the syntax. The approval_required() built-in (see ag2-hitl) is a tool middleware.
Agent middleware runs outside tool middleware: BaseMiddleware.on_tool_execution() sees the full execution including tool-scoped hooks.
Picking the right hook
on_turn→ behaviour about the whole request/response lifecycle.on_llm_call→ behaviour about what goes into / comes out of the model.on_tool_execution→ tool safety / auditing / result shaping across many tools.- Tool-scoped middleware (not
BaseMiddleware) → behaviour for a single tool's definition. on_human_input→ intercept HITL requests/responses.
Going deeper
references/builtin_middleware.md— every built-in's params, common-case recipes, when each fits.website/docs/beta/middleware.mdx— full reference, ordering examples, custom-middleware guidelines.website/docs/beta/tools/tool_middleware.mdx— per-tool hooks (different mental model — plain async callables, notBaseMiddleware).- For OpenTelemetry instrumentation specifically, see
ag2-telemetry.
Common pitfalls
- Forgetting
Middleware(...)for constructor args —middleware=[AuditMiddleware](no wrapper) only works if the class needs onlyeventandcontext. Otherwise wrap:middleware=[Middleware(AuditMiddleware, logger=...)]. - Mutation order surprises — middleware runs in registration order. If middleware A trims history and middleware B logs it, register
[A, B]so B sees the trimmed view. - Per-call middleware doesn't replace agent middleware — it's appended. Agent middleware still runs.
- One big middleware doing five things — keep hooks focused. Logging + retry + mutation + policy in one class is hard to reason about and order. Split into multiple instances.
on_tool_executionbranching onevent.namefor a single tool — that's a smell; use tool-scoped middleware for one-tool behaviour and reserveon_tool_executionfor cross-cutting policies.- Putting OpenTelemetry instrumentation in custom code — there's a
TelemetryMiddlewarefor that; seeag2-telemetry.
In this repository15 other files it gives its agents.
- ag2-add-custom-toolSkill
- ag2-ag-uiSkill
- ag2-hitlSkill
- ag2-knowledge-and-memorySkill
- ag2-multimodal-inputSkill
- ag2-observers-and-alertsSkill
- ag2-overviewSkill
- ag2-quickstartSkill
- ag2-shell-toolSkill
- ag2-structured-outputSkill
- ag2-subagent-delegationSkill
- ag2-telemetrySkill
- ag2-testingSkill
- ag2-use-builtin-toolsSkill
- calculatorSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

