Mattermost
REST v4 polling client. Self-hosted, on-prem, or sovereign-cloud Mattermost servers all work the same way: the bot polls the channels it can read every 3 seconds for new posts, and reply posts go out via POST /api/v4/posts.
Quickstart
Minimum config for a multi-channel, DM-aware bot:
[channels.mattermost.work]
enabled = true
url = "https://mattermost.example.com"
bot_token = "..."
That alone gives you:
- Auto-discovery of every channel the bot can read across every team it belongs to.
- DM and group-DM channels auto-discovered and polled alongside team channels.
- New DMs (created after the bot starts) picked up at the next 60-second discovery refresh.
mention_onlybypassed inside DM and group-DM channels (so 1:1 conversations don’t need the bot to be @-mentioned).
To restrict the bot, narrow with channel_ids, team_ids, or discover_dms.
配置
[channels.mattermost.<alias>]
enabled = true # gate; required
url = "https://mattermost.example.com" # required
bot_token = "..." # secret; OR login_id+password
# login_id = "" # alternative auth path; only when bot_token is unset
# password = "" # secret; pairs with login_id
channel_ids = [] # [] or ["*"] = auto-discover
team_ids = [] # [] = all teams
discover_dms = true # include type=D and type=G
thread_replies = true # thread on the user's post
mention_only = false # filter ambient-channel chatter
interrupt_on_new_message = false # cancel in-flight on new sender post
proxy_url = "" # optional per-channel proxy
excluded_tools = [] # tools hidden from this channel
Field reference:
| field | 类型 | default | meaning |
|---|---|---|---|
enabled | 布尔 | false | Loaded only when true. |
url | 字符串 | (required) | Base URL of the Mattermost server, no trailing slash. |
bot_token | secret | 无 | Bot Account access token. Preferred. |
login_id | 字符串 | 无 | Email or username for password login. Used only when bot_token is unset. |
password | secret | 无 | Account password. Must pair with login_id. |
channel_ids | list | [] | Empty or ["*"] triggers auto-discovery. Explicit IDs pin the bot to that exact set. |
team_ids | list | [] | Auto-discovery allowlist for team channels. Empty = every team the bot belongs to. DM and group-DM channels are unaffected (they carry no team_id). |
discover_dms | 布尔 | true | When auto-discovering, include type=D and type=G channels. Set false to scope the bot to public/private team channels only. No effect when channel_ids is explicit. |
thread_replies | 布尔 | true | New top-level reply opens a thread rooted on the user’s post. Replies inside an existing thread always stay in that thread regardless. |
mention_only | 布尔 | false | Public/private team channels: ignore posts that do not @mention the bot. DMs and group DMs always bypass this filter. |
interrupt_on_new_message | 布尔 | false | A newer post from the same sender in the same channel cancels the in-flight turn. |
proxy_url | 字符串 | 无 | Per-channel proxy override (http, https, socks5, socks5h). |
excluded_tools | list | [] | Tool names hidden from the model on this channel. |
Channel discovery
There are two scoping modes.
- Auto-discovery (when
channel_idsis empty or["*"]). On startup and every 60 seconds thereafter, the bot callsGET /api/v4/users/me/channels, filters the result byteam_ids(public/private channels) anddiscover_dms(DMs/group DMs), and polls each surviving channel. New DMs created mid-runtime appear at the next refresh. - Explicit (when
channel_idsis a non-empty list of IDs other than*). On startup the bot callsGET /api/v4/channels/{id}for each entry to learn itstype(so it knows which are DMs for themention_onlybypass), then polls exactly those channels forever. No periodic re-discovery.
In both modes each channel has its own since cursor: the bot tracks the highest create_at it has processed per channel and passes that as since=<ms> on the next GET /api/v4/channels/{id}/posts call. Cursors do not leak across channels, so a slow-moving channel doesn’t suppress posts on a busy one.
Direct messages
Mattermost classifies channels by type:
type | meaning |
|---|---|
O | Public team channel. |
P | Private team channel. |
G | Group direct message (multi-user DM). |
D | Direct message (1:1). |
G and D are treated identically by ZeroClaw: both carry no team_id, both are gated by discover_dms, and both implicitly bypass mention_only (a private conversation has no ambient noise to filter against).
Authorization for DM senders still goes through the channel’s peer-group resolver, same as any other channel. discover_dms is a knob, not a security boundary; peer groups decide who is allowed to address the agent.
线程
- Inbound post is inside an existing thread (
root_idis set) → the reply always lands in that thread, regardless ofthread_replies. - Inbound post is top-level and
thread_replies = true(default) → the reply opens a thread rooted on the inbound post. - Inbound post is top-level and
thread_replies = false→ the reply is posted at channel root.
Authentication
Two paths:
- Bot token (preferred). Create at System Console → Integrations → Bot Accounts, copy the access token, store it in
bot_token. Tokens survive password rotations and are easier to revoke. - Login flow. Set
login_id(email or username) andpassword. The bot callsPOST /api/v4/users/loginon startup and caches the returned session token in memory. No persistence to disk.
bot_token wins when both are set.
Voice messages
When [transcription] is configured and an inbound post has an audio attachment (mime audio/* or extension ogg/mp3/m4a/wav/opus/flac) with no text body, the audio is downloaded via GET /api/v4/files/{file_id} and routed through the configured transcription provider. The transcript is prefixed [Voice] and becomes the message content. Attachments larger than 25 MB or longer than transcription.max_duration_secs are dropped with a WARN.
设置
- In Mattermost: System Console → Integrations → Bot Accounts → Add Bot Account. Set a username (e.g.
zeroclaw), enable the scopes you want. - Copy the access token. Store it in your ZeroClaw secrets backend.
- Invite the bot to whichever teams you want it active in. For DM auto-discovery, no extra invites needed: any user can DM the bot.
- Add
[channels.mattermost.<alias>]to your config.toml referencing the token. - Bind the channel to an agent in
[agents.<alias>]viachannels = ["mattermost.<alias>"].
Identity and peer groups
Inbound ChannelMessage.sender is the Mattermost user UUID (user_id from the post payload). Peer-group authorization matches against that UUID. If you want to allowlist a specific human, copy their user ID from System Console → User Management and add it to [peer_groups.<group>].external_peers. The bot does not currently resolve usernames at message-receive time; that’s an orthogonal concern shared with Discord and other UUID-based channels.
Operational notes
- Poll cadence is 3 seconds per channel. N discovered channels = N HTTP calls every 3 seconds against the Mattermost server. Self-hosted defaults handle this easily; if you’re on a shared cloud tenant with tight rate limits, consider scoping with
channel_idsorteam_ids. - The bot identity is fetched once via
GET /api/v4/users/meand cached for the process lifetime. Username changes require a restart. - The session token from the password login flow is in-memory only. A restart re-logs in.