step12 · Middleware (guardrail + logger)

This lesson teaches a middleware chain where a guardrail can block a run before the model is ever called, chained ahead of a logger.

What this lesson demonstrates

A middleware is any value with a Run method (agent.Middleware). It wraps every run: it sees the incoming messages before they reach the model and every update on the way back. This lesson chains two. The guardrail inspects the messages and, if the request looks harmful, yields its own refusal and never calls next — the model is never invoked. The logger is a transparent pass-through that records the run, then returns next(...) unchanged. The guardrail is outermost, so a blocked request stops there — offline, with no network.

Blocking vs. passing through is one branch

The crux of the whole lesson is a single decision inside the guardrail: yield your own update to short-circuit, or return next(...) to pass through.

func (guardrailMiddleware) Run(next agent.RunFunc, ctx context.Context,
    messages []*message.Message, options ...agent.Option) iter.Seq2[*agent.ResponseUpdate, error] {
    for _, msg := range messages {
        if strings.Contains(strings.ToLower(msg.String()), "harmful") {
            return func(yield func(*agent.ResponseUpdate, error) bool) {
                yield(&agent.ResponseUpdate{Role: message.RoleAssistant, Contents: message.Contents{
                    &message.TextContent{Text: "I can't help with harmful requests."},
                }}, nil)
            }
        }
    }
    return next(ctx, messages, options...) // safe → pass through
}

What to notice / the gotcha

How it maps to the Agent Framework

agent.Middleware is the Go SDK’s standard extension point for logging, tracing, and guardrails, all threaded through the iter.Seq2[*agent.ResponseUpdate, error] streaming type. Because a middleware can emit its own updates, safety policy lives in the same chain as observability — a guardrail is just a middleware that sometimes declines to call the next layer.

Run it

go run ./tutorial/02-agents/providers/foundry/step12_middleware

The first prompt is refused offline by the guardrail; only the second reaches the model and needs az login + FOUNDRY_PROJECT_ENDPOINT. The offline test drives each middleware with a fake next (proving the guardrail never calls it on a harmful message); the live call is gated behind AF_LIVE=1.


Next: step13 · Plugins (grouping tools)

Sources & References

The Go SDK this lesson exercises
Agent middleware chains for guardrails and logging