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.
Who can talk to the agent
Inbound senders are gated against the peer set resolved for the bound
agent, drawn from the peer_groups config the agent belongs to. Matching strips
a leading @ and is case-insensitive against the channel’s native sender
identifier. An empty set denies everyone; a set containing "*" accepts
anyone; otherwise only the listed external peers (and peer agents) are accepted.
This is separate from gateway pairing (gateway.require_pairing), which
authenticates HTTP/WebSocket clients, not chat-channel senders.
A peer group for mattermost sets channel to mattermost, lists the allowed senders in
external_peers (for mattermost, the Mattermost user UUID (not a username); ["*"] accepts anyone), optionally
names peer agents for cross-agent dispatch, an ignore blocklist, and an
output_modality (mirror, voice, or text). See Peer Groups
for the field reference.
Where to set this:
Gateway dashboard
Open /config/peer_groups in the web dashboard.
zerocode
In the Config pane, under Peer groups.
To allowlist a specific human, copy their user ID from System Console → User Management. Mattermost matches the user UUID, not a username, and does not resolve usernames at message-receive time.
Quickstart
Configure a Mattermost channel (url plus a bot_token secret, see Authentication) through one of the surfaces below. 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.
Configuration
bot_token and password are secrets:
channels.mattermost.<alias>.bot_tokenis a secret. Stored encrypted, never in plainconfig.toml. Set it through one of these, which encrypt on write:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.bot_token field there.
zerocode
In the Config pane, set the channels.mattermost.<alias>.bot_token field (input is masked).
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.bot_token # prompts for masked input, stores encrypted
Field reference
bot_token 🔑
Mattermost bot access token. When unset, the channel falls back to the login flow using login_id + password.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.bot_token field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.bot_token field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.bot_token # masked input, stored encrypted
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__bot_token=
channel_ids
Channel IDs to restrict the bot to. Empty or ["*"] = auto-discover every channel the bot can read (public, private, DMs, group DMs) and poll them all. Explicit IDs disable discovery and pin the bot to the listed channels only. Migrated from the legacy channel_id singular field.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.channel_ids field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.channel_ids field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.channel_ids <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__channel_ids=
discover_dms
When true (default), auto-discovery includes DM (type=D) and group DM (type=G) channels. Set false to restrict the bot to public and private team channels only. Has no effect when channel_ids lists explicit IDs. Defaults to true at the call site via discover_dms.unwrap_or(true).
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.discover_dms field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.discover_dms field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.discover_dms <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__discover_dms=
excluded_tools
Tools excluded from this channel’s tool spec. When set, these tools are not exposed to the model when responding via this channel.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.excluded_tools field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.excluded_tools field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.excluded_tools <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__excluded_tools=
interrupt_on_new_message
When true, a newer Mattermost message from the same sender in the same channel cancels the in-flight request and starts a fresh response with preserved history.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.interrupt_on_new_message field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.interrupt_on_new_message field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.interrupt_on_new_message <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__interrupt_on_new_message=
login_id
Login ID (email or username) for the password login flow. Used only when bot_token is unset; both login_id and password must be set together.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.login_id field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.login_id field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.login_id <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__login_id=
mention_only
When true, only respond to messages that @-mention the bot. Other messages in the channel are silently ignored. DM and group DM channels always bypass this filter: a 1:1 (or small-group) direct conversation has no ambient noise to gate against, so every message is treated as addressed to the bot.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.mention_only field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.mention_only field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.mention_only <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__mention_only=
password 🔑
Account password for the login flow. Used only when bot_token is unset; both login_id and password must be set together.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.password field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.password field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.password # masked input, stored encrypted
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__password=
proxy_url
Per-channel proxy URL (http, https, socks5, socks5h). Overrides the global [proxy] setting for this channel only.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.proxy_url field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.proxy_url field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.proxy_url <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__proxy_url=
reply_min_interval_secs
Per-(channel, recipient) outbound pacing floor in seconds. Range: 0..=REPLY_MIN_INTERVAL_MAX_SECS (0 disables).
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.reply_min_interval_secs field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.reply_min_interval_secs field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.reply_min_interval_secs <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__reply_min_interval_secs=
reply_queue_depth_max
Per-(channel, recipient) outbound pacing queue depth. Range: 0..=REPLY_QUEUE_DEPTH_CEILING. When reply_min_interval_secs > 0 and this value is 0, the pacing wrapper substitutes DEFAULT_REPLY_QUEUE_DEPTH (16). When the queue is full, the newest send is dropped and a WARN is logged.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.reply_queue_depth_max field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.reply_queue_depth_max field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.reply_queue_depth_max <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__reply_queue_depth_max=
team_ids
Team IDs to restrict auto-discovery to. Empty = discover across every team the bot belongs to. Non-empty = only discover public/private channels whose team_id is in this list. DMs and group DMs (which have no team) are governed by discover_dms instead.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.team_ids field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.team_ids field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.team_ids <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__team_ids=
thread_replies
When true (default), replies thread on the original post. When false, replies go to the channel root.
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.thread_replies field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.thread_replies field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.thread_replies <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__thread_replies=
url*
Mattermost server URL (e.g. "https://mattermost.example.com").
Set it on any surface:
Gateway dashboard
Open /config/channels/mattermost and set the channels.mattermost.<alias>.url field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.url field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.url <value>
Environment variable
Export the override (POSIX shells; drop into ~/.bashrc, ~/.zshrc, .env, or a Dockerfile). Replace <alias> with the literal alias:
export ZEROCLAW_channels__mattermost__<alias>__url=
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.
Threading
- 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.
Context management
When a Mattermost conversation happens in a thread, that thread is its own
conversation. ZeroClaw derives a distinct session key per thread, so every
thread carries an independent context window and history: messages in one
thread never bleed into another, and the agent does not see a sibling thread’s
earlier turns. For Mattermost this is controlled by thread_replies: when it is on, top-level messages open a thread and each thread is a separate conversation; when off, replies post at the channel root and history is keyed by sender and target instead of by thread.
- Isolation is the point. Each thread’s context is self-contained: it does not leak outside the thread, and nothing from outside the thread leaks in. Parallel threads hold separate conversational state, so unrelated tasks never contaminate each other.
- Long threads grow context. A thread accumulates history while it stays active, so a very long thread eventually fills the model’s context window like any other long conversation. Start a new thread to reset.
- In-flight work is scoped per thread. A new message in one thread does not cancel an in-flight response in another; each thread’s task stands alone.
Set the thread behavior on any surface:
Gateway dashboard
Open /config/channels/mattermost and toggle the channels.mattermost.<alias>.thread_replies field.
zerocode
In the Config pane, set the channels.mattermost.<alias>.thread_replies field.
zeroclaw config
zeroclaw config set channels.mattermost.<alias>.thread_replies true # thread replies on
zeroclaw config set channels.mattermost.<alias>.thread_replies false # replies at the 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.
Setup
- 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.
- Create the
mattermost.<alias>channel referencing the token through the gateway, zerocode, orzeroclaw config set. - Bind the channel to an agent in
[agents.<alias>]viachannels = ["mattermost.<alias>"].
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.