Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Auto-discovery of every channel the bot can read across every team it belongs to.
  2. DM and group-DM channels auto-discovered and polled alongside team channels.
  3. New DMs (created after the bot starts) picked up at the next 60-second discovery refresh.
  4. mention_only bypassed 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_token is a secret. Stored encrypted, never in plain config.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 🔑 secret · default null

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 string[] · default []

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 bool? · default null

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 string[] · default []

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 bool · default false

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 string? · default null

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 bool? · default null

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 🔑 secret · default null

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 string? · default null

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 integer · default 0

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 integer · default 0

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 string[] · default []

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 bool? · default null

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* string · default

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.

  1. Auto-discovery (when channel_ids is empty or ["*"]). On startup and every 60 seconds thereafter, the bot calls GET /api/v4/users/me/channels, filters the result by team_ids (public/private channels) and discover_dms (DMs/group DMs), and polls each surviving channel. New DMs created mid-runtime appear at the next refresh.
  2. Explicit (when channel_ids is a non-empty list of IDs other than *). On startup the bot calls GET /api/v4/channels/{id} for each entry to learn its type (so it knows which are DMs for the mention_only bypass), 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:

typemeaning
OPublic team channel.
PPrivate team channel.
GGroup direct message (multi-user DM).
DDirect 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

  1. Inbound post is inside an existing thread (root_id is set) → the reply always lands in that thread, regardless of thread_replies.
  2. Inbound post is top-level and thread_replies = true (default) → the reply opens a thread rooted on the inbound post.
  3. 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:

  1. 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.
  2. Login flow. Set login_id (email or username) and password. The bot calls POST /api/v4/users/login on 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

  1. In Mattermost: System Console → Integrations → Bot Accounts → Add Bot Account. Set a username (e.g. zeroclaw), enable the scopes you want.
  2. Copy the access token. Store it in your ZeroClaw secrets backend.
  3. 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.
  4. Create the mattermost.<alias> channel referencing the token through the gateway, zerocode, or zeroclaw config set.
  5. Bind the channel to an agent in [agents.<alias>] via channels = ["mattermost.<alias>"].

Operational notes

  1. 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_ids or team_ids.
  2. The bot identity is fetched once via GET /api/v4/users/me and cached for the process lifetime. Username changes require a restart.
  3. The session token from the password login flow is in-memory only. A restart re-logs in.

See also