microsoft/semantic-kernel · error · AgentExecutionException

Invalid message role `{message.role.value}`. Allowed roles a

Error message

Invalid message role `{message.role.value}`. Allowed roles are {allowed_message_roles}.

What it means

Same role guard as the content-generation helper, applied inside AssistantThreadActions when creating a thread message. allowed_message_roles defaults to [USER, ASSISTANT]; TOOL is always allowed. Note a string message is auto-wrapped as USER, and messages containing FunctionCallContent are short-circuited (return None) before this check. Any remaining message with a disallowed role raises AgentExecutionException.

Source

Thrown at python/semantic_kernel/agents/open_ai/assistant_thread_actions.py:119

                Providing an empty list will disallow all message roles.
            kwargs: Additional keyword arguments.

        Returns:
            The created message.
        """
        from semantic_kernel.contents.chat_message_content import ChatMessageContent

        if isinstance(message, str):
            message = ChatMessageContent(role=AuthorRole.USER, content=message)

        if any(isinstance(item, FunctionCallContent) for item in message.items):
            return None

        # Set the default allowed message roles if not provided
        if allowed_message_roles is None:
            allowed_message_roles = [AuthorRole.USER, AuthorRole.ASSISTANT]
        if message.role.value not in allowed_message_roles and message.role != AuthorRole.TOOL:
            raise AgentExecutionException(
                f"Invalid message role `{message.role.value}`. Allowed roles are {allowed_message_roles}."
            )

        message_contents: list[dict[str, Any]] = get_message_contents(message=message)

        return await client.beta.threads.messages.create(
            thread_id=thread_id,
            role="assistant" if message.role == AuthorRole.TOOL else message.role.value,  # type: ignore
            content=message_contents,  # type: ignore
            **kwargs,
        )

    # endregion

    # region Invocation Methods

    @classmethod
    async def invoke(

View on GitHub (pinned to c028a0c7dc)

Solutions

  1. Pre-filter history to USER/ASSISTANT roles before passing to the assistant invoke path.
  2. Do not pass allowed_message_roles unless you actually need to restrict; the default covers the common case.
  3. Keep SYSTEM content in the assistant instructions, not in thread messages.

Example fix

// before
allowed = [AuthorRole.USER]
await actions.create_message(client, thread_id, assistant_msg, allowed_message_roles=allowed)
# assistant_msg.role == ASSISTANT -> Invalid message role

// after
await actions.create_message(client, thread_id, assistant_msg)  # default allows USER+ASSISTANT
Defensive patterns

Strategy: validation

Validate before calling

from semantic_kernel.contents import AuthorRole

def filter_thread_messages(history):
    allowed = {AuthorRole.USER, AuthorRole.ASSISTANT, AuthorRole.TOOL}
    return [m for m in history if m.role in allowed]

Type guard

from semantic_kernel.contents import AuthorRole

def is_thread_safe_role(msg) -> bool:
    return msg.role in (AuthorRole.USER, AuthorRole.ASSISTANT, AuthorRole.TOOL)

Prevention

When it happens

Trigger: Submitting a ChatMessageContent with role SYSTEM (or another disallowed role) to the thread-actions message-create path; passing a custom allowed_message_roles list that excludes the role of a message being added.

Common situations: Reusing a multi-role chat history (with SYSTEM entries) as the thread's messages; building allowed_message_roles dynamically and accidentally omitting the needed role; migrating prompts that previously put system text inline.

Related errors


AI-assisted analysis of microsoft/semantic-kernel@c028a0c7dc (2026-08-13). Data as JSON: /api/errors/dce64efde846b5ad. Report an issue: GitHub.