Implementation:Microsoft Agent framework Handler Decorator
Template:Microsoft Agent framework Sidebar
Overview
The Handler Decorator (@handler) is the primary mechanism in the Microsoft Agent Framework for declaring message handlers on executor nodes within a workflow. It attaches routing metadata to async methods so the workflow engine can dispatch typed input messages to the correct handler at runtime. The decorator supports both introspection-based type discovery (types inferred from the function signature) and explicit type specification via keyword parameters.
| Property | Value |
|---|---|
| Source | python/packages/core/agent_framework/_workflows/_executor.py (lines L546-685)
|
| Import | from agent_framework import handler
|
| Inputs | An async method on an executor class; optional input, output, workflow_output type overrides
|
| Outputs | The original method, annotated with handler metadata for the workflow routing table |
| Domains | Workflow_Engine |
| Related Principle | Handler Declaration |
Code Reference
Full Signature
def handler(
func: Callable | None = None,
*,
input: type | types.UnionType | str | None = None,
output: type | types.UnionType | str | None = None,
workflow_output: type | types.UnionType | str | None = None,
) -> Callable:
Source Location
python/packages/core/agent_framework/_workflows/_executor.py, lines L546-685. The decorator is defined as a top-level function that supports both @handler (no parentheses) and @handler(...) (with keyword arguments) usage patterns.
I/O Contract
Inputs
| Parameter | Type | Default | Description |
|---|---|---|---|
func |
None | None |
The async method to register as a handler. When using @handler without parentheses, this is populated automatically by the Python decorator protocol.
|
input |
types.UnionType | str | None | None |
The message type(s) this handler accepts. When None, the type is introspected from the first non-self parameter's type annotation.
|
output |
types.UnionType | str | None | None |
The output message type(s) this handler produces via ctx.send_message(). When None, the type is introspected from the WorkflowContext generic parameter.
|
workflow_output |
types.UnionType | str | None | None |
The type of the final workflow output, if this handler contributes to the workflow's terminal result. When None, defaults to the introspected or explicitly provided output type.
|
Outputs
The decorator returns the original method with handler metadata attached. The method is not wrapped or replaced; instead, internal attributes are set on the function object that the workflow engine reads during executor registration. The metadata includes:
| Metadata Field | Description |
|---|---|
| Input type | The resolved message type (introspected or explicit) that triggers this handler. |
| Output type | The resolved type of messages this handler sends downstream. |
| Workflow output type | The resolved type of final workflow results this handler can emit. |
| Handler marker | A sentinel attribute indicating the method has been registered via @handler.
|
Description
The @handler decorator operates through the following internal steps:
- Calling convention resolution: Determines whether the decorator was invoked as
@handler(bare) or@handler(...)(with arguments). In the bare case,funcis the decorated method and processing proceeds immediately. In the parameterized case, the decorator returns an intermediate function that will receivefuncwhen Python applies the decorator. - Type resolution: For each of
input,output, andworkflow_output:- If the parameter was provided explicitly, that value is used directly.
- If the parameter is
None, the framework introspects the function's type annotations. The input type comes from the first non-selfparameter annotation. The output type comes from the generic argument of theWorkflowContextparameter (e.g.,WorkflowContext[str]yieldsstr).
- Metadata attachment: The resolved types and a handler marker sentinel are stored as attributes on the function object. This is a non-destructive operation; the method's runtime behavior is unchanged.
- Return: The original function is returned, now carrying the metadata that the workflow engine will consume during executor registration.
The dual calling convention follows the same pattern used by the @tool decorator in the framework, providing a consistent developer experience across decorator-based APIs.
Examples
Introspection-Based (Default)
from agent_framework import handler
from agent_framework._workflows._executor import WorkflowContext
@handler
async def process(self, text: str, ctx: WorkflowContext[str]) -> None:
await ctx.send_message(text.upper())
The framework introspects the signature and determines:
- Input type:
str(from thetextparameter annotation). - Output type:
str(fromWorkflowContext[str]).
Explicit Types
from agent_framework import handler
@handler(input=str, output=int)
async def count_words(self, message, ctx) -> None:
await ctx.send_message(len(message.split()))
The input and output types are specified directly, bypassing introspection. This is useful when parameter annotations are absent or intentionally broad.
Explicit Types with Workflow Output
from agent_framework import handler
@handler(input=bytes, output=str, workflow_output=str)
async def decode(self, data, ctx) -> None:
decoded = data.decode("utf-8")
await ctx.send_message(decoded)
Union Input Types
from agent_framework import handler
@handler(input=str | bytes, output=str)
async def normalize(self, payload, ctx) -> None:
if isinstance(payload, bytes):
payload = payload.decode("utf-8")
await ctx.send_message(payload.strip())
Related Pages
- Principle:Microsoft_Agent_framework_Handler_Declaration
- Principle: Handler Declaration -- the theoretical basis for declaring message handlers on executor nodes