Task Module shows popup but Adaptive Card does not render using M365 Agent SDK (Python)

Akash Agarwal 0 Reputation points
2025-12-11T17:24:14.7233333+00:00

I am using the new M365 Agent SDK for Python to return a Task Module (task/fetch). The popup opens successfully when I click the button in Teams, but the Adaptive Card inside the Task Module does not render. Instead, Teams shows the generic error:

“Unable to reach app. Please try again.”

My invoke handler returns the following structure:

adaptive_card = {
    "type": "AdaptiveCard",
    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.4",
    "body": [
        {"type": "TextBlock", "text": "Sample task module card"},
        {"type": "Input.Text", "id": "notes", "isMultiline": True}
    ],
    "actions": [
        {
            "type": "Action.Submit",
            "title": "Submit",
            "data": {"action": "sample_task_submit"}
        }
    ]
}

task_response = {
    "task": {
        "type": "continue",
        "value": {
            "title": "Dialog",
            "width": "medium",
            "height": "medium",
            "card": {
                "contentType": "application/vnd.microsoft.card.adaptive",
                "content": adaptive_card
            }
        }
    }
}

return InvokeResponse(status=200, body=task_response)

Key symptoms:

Task module window opens (so invoke is successful)

Adaptive card does not render

Shows “Unable to reach app” inside popup

No errors in server logs

This happens only with Task Modules; normal message replies work fine

Questions:

Is the above Task Module response format correct for the new M365 Agent SDK for Python?

Does the SDK require a different return type or content-type for invoke responses?

Are Task Modules fully supported in the M365 Agent SDK preview?

Is there an example of returning a Task Module using the Python SDK?

Any clarification or working sample for Python would be very helpful.

SS - User's image


Microsoft Teams | Development
Microsoft Teams | Development
Building, integrating, or customizing apps and workflows within Microsoft Teams using developer tools and APIs
0 comments No comments
{count} votes

Answer recommended by moderator
  1. Akash Agarwal 0 Reputation points
    2025-12-15T14:48:58.7966667+00:00

    Thank You Nivedipa-MSFT, Teddie-D and Jeremy Saps for your quick responses,

    I was able to resolve the issue. The problem was not with the Adaptive Card itself, but with how the invoke response was being returned in the M365 Agent SDK for Python. Task Modules in Teams require a very specific response flow, and the SDK does not automatically handle it unless you wire the pipeline correctly.

    Below is the working pattern.


    1. Main request handler (teams_main.py)

    The key point:

    • task/fetch must return a JSON response containing the dialog payload.
    • task/submit should return a 202 (no UI update).
    
    if activity.type == "invoke" or activity.delivery_mode == DeliveryModes.expect_replies:
    
        # Submit dialog → no content expected by Teams
    
        if not response.body:
    
            return Response(status_code=202)
    
        # Fetch dialog → return your Task Module JSON payload
    
        return JSONResponse(status_code=response.status, content=response.body)
    
    return Response(status_code=202)
    
    

    This ensures Teams gets the correct response format for each stage of the Task Module flow.


    2. Invoke routing (teams_event.py)

    
    @agent_app.activity("invoke")
    
    async def on_invoke(context: TurnContext, state: TurnState):
    
        return await event_handler.handle_invoke_event(context, state)
    
    

    3. Invoke event handler (teams_event_handler.py)

    
    async def handle_invoke_event(self, context: TurnContext, state: TurnState):
    
        activity = context.activity
    
        name = getattr(activity, "name", None)
    
        if name == "task/fetch":
    
            return await self._handle_task_fetch(context)
    
        if name == "task/submit":
    
            return await self._handle_task_submit(context)
    
        return InvokeResponse(status=200, body={})
    
    

    4. Handling task/fetch

    This builds the Adaptive Card, wraps it in the required Task Module structure, and returns it as an invokeResponse.

    
    async def _handle_task_fetch(self, context: TurnContext):
    
        adaptive_card = self._build_sample_form_card()
    
        invoke_activity = self._build_task_module_response(
    
            title="Form",
    
            height="medium",
    
            width="medium",
    
            card=adaptive_card,
    
        )
    
        await self.adapter.send_activities(context, [invoke_activity])
    
    

    5. Adaptive Card

    
    def _build_sample_form_card(self):
    
        return {
    
            "type": "AdaptiveCard",
    
            "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
    
            "version": "1.4",
    
            "body": [
    
                {"type": "TextBlock", "text": "Sample task module card", "weight": "Bolder", "size": "Medium"},
    
                {"type": "TextBlock", "text": "This is a sample dialog.", "wrap": True},
    
                {"type": "Input.Text", "id": "notes", "isMultiline": True, "placeholder": "Add notes here"},
    
            ],
    
            "actions": [
    
                {
    
                    "type": "Action.Submit",
    
                    "title": "Submit",
    
                    "data": {
    
                        "msteams": {"type": "task/submit"},
    
                        "action": "sample_task_submit"
    
                    }
    
                }
    
            ]
    
        }
    
    

    Note the required:

    
    "msteams": { "type": "task/submit" }
    
    

    Teams uses this to identify a dialog submission.


    6. Building the Task Module response

    
    def _build_task_module_response(self, title, height, width, card):
    
        return Activity(
    
            type="invokeResponse",
    
            value={
    
                "status": 200,
    
                "body": {
    
                    "task": {
    
                        "type": "continue",
    
                        "value": {
    
                            "title": title,
    
                            "height": height,
    
                            "width": width,
    
                            "card": {
    
                                "contentType": "application/vnd.microsoft.card.adaptive",
    
                                "content": card
    
                            }
    
                        }
    
                    }
    
                }
    
            }
    
        )
    
    

2 additional answers

Sort by: Most helpful
  1. Teddie-D 9,505 Reputation points Microsoft External Staff Moderator
    2025-12-12T04:45:28.02+00:00

    Hi @Akash Agarwal 

    Thank you for posting your question in the Microsoft Q&A forum. 

    Since I’m not a Python developer, I don’t want to accidentally give you misleading instructions. For the most accurate guidance, I’d recommend opening a new thread in the Microsoft Community Hub or on GitHub, there are many developers and SDK contributors there who can give you more targeted help. 

    Alternatively, I did some research that may help clarify what’s going on: 

    The structure  task.type="continue" with an Adaptive Card contentType="application/vnd.microsoft.card.adaptive" is the correct format that Teams expects for a Task Module dialog. 

    However, the Agents SDK preview enforces typed models for invoke responses. If your handler returns a plain dictionary, the SDK’s serializer won’t accept it. When that happens, Teams falls back to the generic banner “Unable to reach app. Please try again.” 

    In the newer SDK builds, the expected response chain looks like this: 

    -TaskModuleResponse contains task and cache_info (required). 

    -TaskModuleContinueResponse wraps the TaskInfo value. 

    -CardTaskModuleTaskInfo (or equivalent) holds the Adaptive Card attachment 

    You must wrap the whole thing in an invokeResponse activity whose value is InvokeResponse
    If you want to double‑check the expected behavior, Microsoft’s documentation on Teams dialogs is helpful: Use dialogs in Microsoft Teams bots - Teams | Microsoft Learn

    Task Modules are supported in Teams when using the Python Agents SDK preview. The SDK’s dialog/activity handlers automatically map Teams invoke activities (task/fetch, task/submit) to your bot’s handler. 

    For a working reference implementation, you can look at the official sample: Microsoft-Teams-Samples/samples/bot-task-module/python at main · OfficeDev/Microsoft-Teams-Samples ….  

    I hope this information is helpful. 


    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread. 


  2. Jeremy Saps 0 Reputation points
    2025-12-15T06:26:33.2833333+00:00

    The “Unable to reach app” banner is commonly a symptom of Teams not being able to parse the Task Module invoke response - Microsoft support threads call out that these errors typically point to problems with the bot’s Task Module response format (even if your handler executed)

    In the M365 Agents SDK for Python, double-check you’re returning the SDK’s typed Task Module response objects, since TaskModuleResponse requires cacheInfo and Teams can fail to render if required fields are missing or the shape doesn’t serialize as expected

    The underlying Task Module payload shape you’re aiming for is still the Teams doc contract for task/fetch -> task.type="continue" with value containing the card, and a good reference for a known-working Python implementation is the official bot-task-module Python sample

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.