Middleware¶
Middleware intercepts requests at the HTTP boundary, letting you extract secrets, inject headers, sanitize payloads, and log request/response metrics — all before your worker sees the data.
Example¶
from a2akit import (
A2AMiddleware,
A2AServer,
AgentCardConfig,
RequestEnvelope,
TaskContext,
Worker,
)
from fastapi import Request
class SecretExtractor(A2AMiddleware):
"""Move sensitive keys from message.metadata into transient context."""
SECRET_KEYS = {"user_token", "api_key", "auth_token"}
async def before_dispatch(
self, envelope: RequestEnvelope, request: Request
) -> None:
msg_meta = envelope.params.message.metadata or {}
for key in self.SECRET_KEYS & msg_meta.keys():
envelope.context[key] = msg_meta.pop(key) # (1)!
if auth := request.headers.get("Authorization"):
envelope.context["auth_header"] = auth # (2)!
class MyWorker(Worker):
async def handle(self, ctx: TaskContext) -> None:
token = ctx.request_context.get("user_token") # (3)!
trace_id = ctx.metadata.get("trace_id") # (4)!
await ctx.complete(f"Token present: {token is not None}")
server = A2AServer(
worker=MyWorker(),
agent_card=AgentCardConfig(
name="My Agent", description="...", version="0.1.0"
),
middlewares=[SecretExtractor()], # (5)!
)
app = server.as_fastapi_app()
- Secrets are removed from
message.metadata(which gets persisted) and moved toenvelope.context(transient). - HTTP headers can also be captured into the transient context.
- In the worker, access transient data via
ctx.request_context. This data is never written to Storage. - Non-secret metadata stays in
ctx.metadataand is persisted. - Register middlewares as a list. They run in order.
RequestEnvelope¶
The RequestEnvelope is the unit of work that flows through the middleware pipeline:
@dataclass
class RequestEnvelope:
params: MessageSendParams # A2A protocol payload (persisted)
context: dict[str, Any] # transient metadata (never persisted)
params— The A2A protocol payload. Storage only ever sees this.context— Framework-internal metadata. Populated by middleware, consumed by the Worker viactx.request_context. Discarded after the Worker finishes.
Middleware Methods¶
before_dispatch(envelope, request)¶
Called before TaskManager processes the request. Mutate the envelope in-place:
- Extract secrets from
envelope.params.message.metadataand move toenvelope.context - Read HTTP headers from
requestintoenvelope.context - Sanitize or validate
envelope.params
after_dispatch(envelope, result)¶
Called after TaskManager returns, before the HTTP response is sent. The same envelope object is available:
- Log timing and metrics
- Emit audit events
- Clean up transient resources
Execution Order¶
Middleware execution order
before_dispatch runs in registration order. after_dispatch runs in reverse order — like Python context managers or a stack.
Execution:
AuthMiddleware.before_dispatchLoggingMiddleware.before_dispatch- TaskManager processes request
LoggingMiddleware.after_dispatchAuthMiddleware.after_dispatch
Secrets vs. Metadata¶
ctx.metadata |
ctx.request_context |
|
|---|---|---|
| Source | message.metadata |
envelope.context |
| Persisted in Storage? | Yes | No |
| Use for | Trace IDs, correlation, non-sensitive data | Tokens, API keys, auth headers |
| Available in | Worker, Storage, EventBus | Worker only |
Never persist secrets
Always move sensitive data (tokens, API keys, passwords) from metadata to context in your middleware. Data left in metadata is written to Storage and may appear in task history.