microsoft/autogen · error · AssertionError

Closure must have 4 arguments

Error message

Closure must have 4 arguments

What it means

get_handled_types_from_closure() inspects the closure passed to ClosureAgent and requires exactly 3 declared parameters (self/agent, message, ctx — the message says '4 arguments' counting the implicit bound instance). AssertionError fires when inspect.getfullargspec(closure)[0] has a different length.

Source

Thrown at python/packages/autogen-core/src/autogen_core/_closure_agent.py:31

from ._cancellation_token import CancellationToken
from ._message_context import MessageContext
from ._serialization import try_get_known_serializers_for_type
from ._subscription import Subscription
from ._subscription_context import SubscriptionInstantiationContext
from ._topic import TopicId
from ._type_helpers import get_types
from .exceptions import CantHandleException

T = TypeVar("T")
ClosureAgentType = TypeVar("ClosureAgentType", bound="ClosureAgent")


def get_handled_types_from_closure(
    closure: Callable[[ClosureAgent, T, MessageContext], Awaitable[Any]],
) -> Sequence[type]:
    args = inspect.getfullargspec(closure)[0]
    if len(args) != 3:
        raise AssertionError("Closure must have 4 arguments")

    message_arg_name = args[1]

    type_hints = get_type_hints(closure)

    if "return" not in type_hints:
        raise AssertionError("return not found in function signature")

    # Get the type of the message parameter
    target_types = get_types(type_hints[message_arg_name])
    if target_types is None:
        raise AssertionError("Message type not found")

    # print(type_hints)
    return_types = get_types(type_hints["return"])

    if return_types is None:
        raise AssertionError("Return type not found")

View on GitHub (pinned to 027ecf0a37)

Solutions

  1. Use the exact signature async def closure(agent: ClosureContext, message: MsgType, ctx: MessageContext) -> ... and pass it unchanged.
  2. If you wrapped the handler (partial, decorator), ensure the wrapper itself declares (agent, message, ctx).
  3. Count parameters excluding *args/**kwargs — the check uses getfullargspec[0], so keyword-only args also count if listed.

Example fix

# before
async def handler(agent, ctx):  # missing message param
    ...
ClosureAgent("d", handler)

# after
async def handler(agent: ClosureContext, message: str, ctx: MessageContext) -> None:
    ...
ClosureAgent("d", handler)
Defensive patterns

Strategy: validation

Validate before calling

import inspect

def closure_signature_ok(closure) -> bool:
    spec = inspect.getfullargspec(closure)
    return len(spec.args) == 3  # agent, message, ctx

assert closure_signature_ok(my_closure), "closure must be (agent, message, ctx)"

Prevention

When it happens

Trigger: Passing a closure like async def handler(agent, ctx) (2 params) or async def handler(agent, msg, ctx, extra) (4 params) to ClosureAgent; passing a bound method or partial whose effective signature has a different arity.

Common situations: Writing closure agents quickly and miscounting parameters; refactoring a handler signature and forgetting the ClosureAgent call site; wrapping handlers with functools.partial.

Related errors


AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15). Data as JSON: /api/errors/a24310b740e6381d. Report an issue: GitHub.