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
- Use the exact signature async def closure(agent: ClosureContext, message: MsgType, ctx: MessageContext) -> ... and pass it unchanged.
- If you wrapped the handler (partial, decorator), ensure the wrapper itself declares (agent, message, ctx).
- 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
- Always write closures as async def(agent: ClosureContext, message: MsgT, ctx: MessageContext).
- Check inspect.getfullargspec(closure).args == ['agent','message','ctx'] in a unit test for each closure.
- Avoid partial()/decorators that change the declared arity.
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
- return not found in function signature
- Message type not found
- Return type not found
- ClosureAgent must be instantiated within the context of an A
- Message type {type(message)} not in target types {self._expe
AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15).
Data as JSON: /api/errors/a24310b740e6381d.
Report an issue: GitHub.