Routing and sessions
- Session keys follow the standard agent format (see /concepts/session):
- Direct messages share the main session (
agent:<agentId>:main) by default. - Channel/group messages use conversation id:
agent:<agentId>:msteams:channel:<conversationId>agent:<agentId>:msteams:group:<conversationId>
- Direct messages share the main session (
Reply style: threads vs posts
Teams has two channel UI styles over the same underlying data model:
The problem: the Teams API does not expose which UI style a channel uses. If you use the wrong
replyStyle:
threadin a Threads-style channel → replies appear nested awkwardly.top-levelin a Posts-style channel → replies appear as separate top-level posts instead of in-thread.
replyStyle per-channel based on how the channel is set up:
Resolution precedence
When the bot sends a reply into a channel,replyStyle is resolved from the most specific override down to the default. The first non-undefined value wins:
- Per-channel -
channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle - Per-team -
channels.msteams.teams.<teamId>.replyStyle - Global -
channels.msteams.replyStyle - Implicit default - derived from
requireMention:requireMention: true→threadrequireMention: false→top-level
requireMention: false globally without an explicit replyStyle, mentions in Posts-style channels surface as top-level posts even when the inbound was a thread reply. Pin replyStyle: "thread" at the global, team, or channel level to avoid surprises.
For proactive sends into a stored channel conversation (queued tool-call replies, long-running agents), the same team/channel resolution applies; group chats and personal (DM) conversations always resolve to top-level for proactive sends regardless of replyStyle.
Thread context preservation
WhenreplyStyle: "thread" is in effect and the bot was @mentioned from inside a channel thread, OpenClaw re-attaches the original thread root to the outbound conversation reference (19:...@thread.tacv2;messageid=<root>) so the reply lands inside the same thread. This holds for both live (in-turn) sends and proactive sends made after the Bot Framework turn context has expired (e.g., long-running agents, queued tool-call replies via mcp__openclaw__message).
The thread root is taken from the stored threadId on the conversation reference. Older stored references that predate threadId fall back to activityId (whatever inbound activity last seeded the conversation), so existing deployments keep working without a re-seed.
When replyStyle: "top-level" is in effect, channel-thread inbounds are intentionally answered as new top-level posts; no thread suffix is attached. This is correct for Threads-style channels; top-level posts where you expected threaded replies means replyStyle is set incorrectly for that channel.
Attachments and images
Current limitations:- DMs: images and file attachments work via Teams bot file APIs.
- Channels/groups: attachments live in M365 storage (SharePoint/OneDrive). The webhook payload only includes an HTML stub, not the actual file bytes. Graph API permissions are required to download channel attachments.
- For explicit file-first sends, use
action=upload-filewithmedia/filePath/path; optionalmessagebecomes the accompanying text/comment, andfilename(ortitle) overrides the uploaded name.
channels.msteams.mediaAllowHosts (use ["*"] to allow any host).
Authorization headers are only attached for hosts in channels.msteams.mediaAuthAllowHosts (defaults to Graph + Bot Framework hosts). Keep this list strict (avoid multi-tenant suffixes).
Sending files in group chats
Bots can send files in DMs using the built-in FileConsentCard flow. Sending files in group chats/channels requires additional setup:Why group chats need SharePoint
Bots use an application identity, while Microsoft Graph’s/me resource requires a signed-in user. To send files in group chats/channels, the bot uploads to a SharePoint site and creates a sharing link.
Setup
-
Add Graph API permissions in Entra ID (Azure AD) → App Registration:
Sites.ReadWrite.All(Application) - upload files to SharePoint.ChatMember.Read.All(Application) - least-privileged tenant-wide permission for group-chat file sends.Chat.Read.Allalso works and already covers this when group-chat history is enabled. As a per-chat alternative, use theChatMember.Read.Chatresource-specific consent permission.
- Grant admin consent for the tenant.
-
Get your SharePoint site ID:
-
Configure OpenClaw:
Sharing behavior
Per-user sharing is more secure since only chat participants can access the file. OpenClaw requires a successful member lookup for group chats; timeouts, transport failures, empty results, and Graph API denials fail the send instead of widening access to the organization.
Fallback behavior
Files stored location
Uploaded files are stored in a/OpenClawShared/ folder in the configured SharePoint site’s default document library.