Summary
Downstream (salesagent) runs audit logging + activity-feed hooks on every A2A skill dispatch today. Their 2,288 LOC custom A2A executor exists partly because ADCPAgentExecutor.execute() is a black box — there's no pre/post hook.
Proposed API
Two shapes to choose from; either solves the problem:
Option A — middleware list (async, ordered):
class SkillMiddleware(Protocol):
async def __call__(
self,
skill_name: str,
params: dict[str, Any],
context: ToolContext,
call_next: Callable[[], Awaitable[Any]],
) -> Any: ...
ADCPAgentExecutor(handler, ..., middleware=[AuditLogMiddleware(), ActivityFeedMiddleware()])
Option B — two callbacks (simpler, no recursion):
ADCPAgentExecutor(
handler,
...,
before_skill: Callable[[str, dict, ToolContext], Awaitable[None]] | None = None,
after_skill: Callable[[str, dict, ToolContext, Any | Exception], Awaitable[None]] | None = None,
)
Option A is more expressive (middleware can short-circuit, transform result, wrap in retry). Option B is easier to compose at the callsite and doesn't require Protocol/abstract-class boilerplate downstream. Leaning toward A because audit requires exception-capture semantics.
Acceptance
- Hook wired into
ADCPAgentExecutor.execute().
- Surfaced on
create_a2a_server(middleware=...) pass-through.
- Integration test exercising a logging middleware observing every skill dispatch (including error paths).
- Section in
docs/handler-authoring.md showing the audit-log recipe.
Context
Roadmap item PR-P (Phase 2) from .context/sdk-adoption-roadmap.md. Third of three blockers gating salesagent's A2A migration (with #224, #225).
Summary
Downstream (salesagent) runs audit logging + activity-feed hooks on every A2A skill dispatch today. Their 2,288 LOC custom A2A executor exists partly because
ADCPAgentExecutor.execute()is a black box — there's no pre/post hook.Proposed API
Two shapes to choose from; either solves the problem:
Option A — middleware list (async, ordered):
Option B — two callbacks (simpler, no recursion):
Option A is more expressive (middleware can short-circuit, transform result, wrap in retry). Option B is easier to compose at the callsite and doesn't require Protocol/abstract-class boilerplate downstream. Leaning toward A because audit requires exception-capture semantics.
Acceptance
ADCPAgentExecutor.execute().create_a2a_server(middleware=...)pass-through.docs/handler-authoring.mdshowing the audit-log recipe.Context
Roadmap item PR-P (Phase 2) from
.context/sdk-adoption-roadmap.md. Third of three blockers gating salesagent's A2A migration (with #224, #225).