Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
ag2ai avatar

Ag2 Middleware

  • 27 installs
  • 8 repo stars
  • Updated July 27, 2026
  • ag2ai/ag2-skills

ag2-middleware is a Claude Code skill that intercepts the AG2 beta agent loop with BaseMiddleware hooks for retry, logging, history trimming, and guardrails.

About

This skill covers intercepting the AG2 beta agent loop with BaseMiddleware, which exposes four hooks: on_turn, on_llm_call, on_tool_execution, and on_human_input. It is for cross-cutting behaviour like retry, logging, history trimming, request mutation, tool auditing, and rate limiting. It documents built-ins such as LoggingMiddleware, RetryMiddleware, HistoryLimiter, and TokenLimiter, plus how to write your own.

  • Intercept the AG2 agent loop with BaseMiddleware and four async hooks
  • Built-ins for logging, retry, history trim, and token limiting
  • Registers middleware at agent level or per call

Ag2 Middleware by the numbers

  • 27 all-time installs (skills.sh)
  • Ranked #9,601 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
  • Data as of Aug 1, 2026 (Skillselion catalog sync)
At a glance

ag2-middleware capabilities & compatibility

Free skill; requires an LLM provider API key to run the agent.

Capabilities
agent middleware · retry logic · request logging · history trim
Use cases
orchestration · token optimization · debugging
Pricing
Bring your own API key
From the docs

What ag2-middleware says it does

Middleware is for **cross-cutting behaviour** that should apply consistently across many runs without changing the agent, model client, or tools themselves.
SKILL.md
`BaseMiddleware` exposes four async hooks. Implement only the ones you need:
SKILL.md
npx skills add https://github.com/ag2ai/ag2-skills --skill ag2-middleware

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs27
repo stars8
Last updatedJuly 27, 2026
Repositoryag2ai/ag2-skills

What it does

Add cross-cutting logging, retry, history-trim, or guardrail logic to an AG2 beta agent's run loop.

Who is it for?

Developers adding cross-cutting behaviour to AG2 agents without changing the agent, model client, or tools.

Skip if: Per-tool-only hooks, which use tool middleware instead.

When should I use this skill?

The user needs retry, logging, history trimming, request mutation, tool auditing, or rate limiting across an AG2 agent's runs.

What you get

One set of middleware hooks applies retry, logging, and trimming consistently across every run.

By the numbers

  • 4 middleware hooks (on_turn, on_llm_call, on_tool_execution, on_human_input)
  • 5 built-in middleware documented

Files

SKILL.mdMarkdownGitHub ↗

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:

HookWrapsUse for
on_turn(call_next, event, context) → ModelResponseThe whole agent turnTotal latency, request/response inspection, turn-level policies
on_llm_call(call_next, events, context) → ModelResponseEach LLM API callRetry, logging, history trim, request mutation, caching
on_tool_execution(call_next, event, context) → ToolResultTypeEach tool invocationValidate args, redact results, fallback on failure, access control
on_human_input(call_next, event, context) → HumanMessageEach 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:

MiddlewarePurposeConstructor
LoggingMiddlewareLogs turn start/end, each LLM call, each tool executionno args
RetryMiddlewareRetries failed LLM callsmax_retries=N, retry_on=ExceptionClass
HistoryLimiterCap event count before LLM callmax_events=N
TokenLimiterChar-based token-budget cap before LLM callmax_tokens=N, chars_per_token=4
TelemetryMiddlewareOpenTelemetry 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 A

This 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, not BaseMiddleware).
  • For OpenTelemetry instrumentation specifically, see ag2-telemetry.

Common pitfalls

  • Forgetting `Middleware(...)` for constructor argsmiddleware=[AuditMiddleware] (no wrapper) only works if the class needs only event and context. 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_execution` branching on `event.name` for a single tool — that's a smell; use tool-scoped middleware for one-tool behaviour and reserve on_tool_execution for cross-cutting policies.
  • Putting OpenTelemetry instrumentation in custom code — there's a TelemetryMiddleware for that; see ag2-telemetry.

Related skills

FAQ

What hooks does BaseMiddleware expose?

on_turn, on_llm_call, on_tool_execution, and on_human_input.

In what order does middleware run?

In registration order, like nested with blocks; [A, B, C] enters A then B then C and unwinds in reverse.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.