Hermes agent posting too many Slack messages: the two emitters
Pin both tool_progress and interim_assistant_messages under
display.platforms.slack in ~/.hermes/config.yaml. Setting
tool_progress alone is the fix almost everyone tries, and it does not work — not because the value
is wrong, but because Slack has a second, independent emitter, and because a global setting outranks
Slack's own quiet default. Below is the exact block, the resolution order that explains it, and the reason a
fix at the global level silently reverts on the next restart.
At a glance
| Question | Answer |
|---|---|
| Symptom | One user message produces dozens of bot messages in the channel |
| Emitter 1 | tool_progress — one message per tool call |
| Emitter 2 | interim_assistant_messages — one message per agent iteration, independent of emitter 1 |
| Where | ~/.hermes/config.yaml, under display.platforms.slack |
| Why the obvious fix fails | Emitter 2 keeps running, and a global setting outranks Slack's built-in off default |
| Why it reverts | A config.yaml with no _config_version re-runs the schema migration on every boot |
| Leave on | long_running_notifications — the only "still working" signal you have left |
| Not the answer | Mention gating and channel allowlists — they change whether the bot replies, not what a reply costs |
Why turning off tool_progress doesn't stop the messages
We hit this running Hermes agents in production. One instance on Hermes
v2026.7.7.2 posted roughly 44 Slack messages in response to a single user
message. It was reported to us as "it writes its reasoning logs", which turned out to be a
misdiagnosis worth recording: display.show_reasoning resolved False at every level and
StreamingConfig.enabled defaults to False, so neither reasoning summaries nor token
streaming were involved. If you are chasing this, check those two first and then stop looking at them.
Pinning tool_progress off for Slack removed a large share of the messages and did not fix the
problem. The reason is that Hermes has a second emitter, and its own source is explicit that the two are not
coupled:
Natural assistant status messages are intentionally independent from tool progress and token streaming. Users can keep tool_progress quiet in chat platforms while opting into concise mid-turn updates.
That setting is interim_assistant_messages, it posts the model's mid-turn commentary as one Slack
message per agent iteration, and it alone keeps the stream consumer alive — so it does not
merely survive turning off tool progress, it is the thing still holding the channel open. Nous documents the
default as staying on, describing mid-turn commentary as "signal, not noise", which is a defensible choice for
a personal Telegram assistant and a bad one for a shared team channel.
The message count then follows from the agent's iteration budget rather than from anything you configured. The
turn that produced our 44 messages ended with ⚠️ Iteration budget exhausted (90/90). One message
per iteration, ninety iterations available: the ceiling is high enough that a single hard question can bury a
channel.
Upstream reports read the same way. Issue #14663, "slack gateway is reporting intermediate tool
calls" (opened 2026-04-23, closed 2026-04-27), is a user asking for exactly this: "only text should be reported
to the slack channel, the tool calls are meaningless."
How Hermes decides which display setting wins
Hermes resolves a display setting through four levels, highest precedence first:
display.platforms.<platform>.<key>— an explicit per-platform overridedisplay.<key>— the global user setting- the built-in per-platform default for that platform
- the built-in global default
The consequence that catches people is level 2 beating level 3. A global value — including one you never consciously chose — outranks the built-in default that exists specifically to keep a platform quiet.
Slack has such a default. Its built-in tool_progress is already off, and Nous states
the rationale plainly: "Slack's default is tool_progress: off (text bubbles spam channels; native
cards don't)". Slack posts cannot be edited in place, so every progress line lands as a permanent channel
message rather than an updating one.
That quiet default is never reached, because the gateway materializes its own global
display.tool_progress: "all" into config.yaml at boot. The global sits at level 2;
Slack's default sits at level 3; the channel fills up on a platform whose own default was designed to prevent
precisely that. This is also why the per-platform layer exists at all — it was requested as issue
#6164 on 2026-04-08 (closed 2026-04-09) with the observation that
"display.tool_progress in config.yaml is a global setting that applies uniformly
across all platforms".
The published documentation describes a two-level model — per-platform blocks override the base
display section — and separately documents built-in per-platform defaults. Both statements are
true. Read together they imply a guarantee that does not hold, which is the trap.
The fix: pin the overrides under display.platforms.slack
Add this to ~/.hermes/config.yaml:
display:
platforms:
slack:
tool_progress: "off"
show_reasoning: false
interim_assistant_messages: false Three notes on the details, each of which matters:
- Quote
"off". Bareoffis a YAML 1.1 boolean and will not parse as the mode string you meant. - Put it under
display.platforms.slack, not at the top level. A globaldisplay.tool_progressis at level 2, where it is both outranked by nothing and rewritten by the gateway. The per-platform block is at level 1. - Scope it to the platform you actually want quiet. Setting this globally silences every surface, including ones where the progress stream is the point. We pin Slack and deliberately leave our own dashboard chat relay verbose.
Verify the result with Hermes' own resolver rather than by re-reading the YAML — the whole failure mode here is that the file and the resolved value disagree. The gateway's config load is cached on file mtime, so an edit takes effect on the next turn without a restart, which makes this quick to check.
Why the fix reverts after a restart
If your config.yaml has no _config_version key — which is the normal state for a file
written by hand, or emitted by a deployment tool that only writes the sections it cares about — Hermes reads it
as schema version 0 and re-runs its full migration on every boot:
[config-migrate] Migrating config schema 0 -> 33; backups: /opt/data/config.yaml.bak-<ts>
✓ Migrated tool progress to config.yaml: all
✓ Added display.interim_assistant_messages=true
Read what those two lines do. The migration writes the global display defaults back into the file at level 2 on
every start. A fix you made at the global level is therefore not durable — it is in a permanent argument with
the gateway, and the gateway restarts last. It also drops a
config.yaml.bak-<timestamp> each time, which is a useful tell that this is happening to you.
This is the real reason the fix has to be per-platform. Level 1 is above what the migration rewrites, so it survives; level 2 is exactly what the migration rewrites. It also means any behaviour change a future Hermes version adds to that migration path gets applied to your instance silently, without a version change on your side.
What to leave on, and the Slack-native alternative
Leave long_running_notifications on. It is the periodic
"⏳ Working — N min" heartbeat, and once the per-step chatter is gone it is the only signal a Slack user gets
that the agent is alive. During a thirty-minute turn, silence and a crash look identical. It fires on a timer
rather than per step, so it costs a handful of messages across a long turn rather than dozens.
There is also a second, better answer to the first emitter, and it is worth naming because for some teams it
beats switching progress off. Setting platforms.slack.extra.native_task_cards: true renders live
tool calls as Slack-native plan and task cards instead of text bubbles. It was requested as issue
#29483 on 2026-05-20 — "the visible progress draft can become repetitive and hard to scan" — and
closed on 2026-08-13. If your team wants to watch the work rather than hide it, use the cards.
But be clear about what it does and does not cover: cards address tool progress only. They do
not touch interim_assistant_messages, so a channel switched to cards can still take one plain
message per agent iteration. If you enable cards, pin the interim setting anyway.
One config.yaml trap to avoid
While you are editing this file: do not add a top-level slack: block. Issue
#16682 (opened 2026-04-27, closed 2026-04-28) documents that a top-level slack:
section containing any bridged key causes the platform config to default to enabled=False, and the
environment-override path then skips the branch that would enable the adapter from
SLACK_BOT_TOKEN. The adapter goes away silently, with no warning and no error.
The display settings in this article belong under display.platforms.slack, which is a different
key path and is not affected. It is worth knowing the neighbouring landmine exists, because "I edited
config.yaml to fix the spam and now the bot is gone" is an easy afternoon to lose.
Related failure modes
Two other reports are useful for telling this apart from things that look like it.
Issue #2954 (opened and closed 2026-03-25) covers Slack progress messages landing in the channel
instead of the thread when the thread id is not yet set — that is a routing problem, not a volume problem, and
the fix here does not address it. Issue #10942 (opened 2026-04-16, closed 2026-04-23) reports
interim_assistant_messages causing the final answer to be dropped after tool calls, which
is independent evidence that the interim emitter is a distinct code path with its own bookkeeping rather than a
verbosity level of tool progress.
One thing that will not help, despite being the most common advice on this topic: mention gating
(strict_mention), channel allowlists and user allowlists. Those are good settings and you may want
them, but they control whether the agent replies at all. They do nothing about how many messages a
single reply costs, which is the actual problem here.
Stop babysitting your OpenClaw box
Fix it once — or stop fixing it for good.
Apply the checklist above and keep self-hosting, or skip the maintenance entirely: run your OpenClaw on managed hosting from $6.90/mo, starting with a 7-day free trial. We handle the stale locks, gateway restarts, version upgrades, and uptime — and you can import your existing instance in a couple of minutes. Cancel anytime.
- Managed hosting handles stale
.jsonl.lockfiles, gateway restarts, and version upgrades for you - Import your existing OpenClaw setup in minutes — keep your channels and configuration
- The optional $199 setup is scoped: no custom development, enterprise/SRE support, or unsupported self-hosting repair
If you would rather compare options first, review OpenClaw cloud hosting or see the best OpenClaw hosting options before deciding.
How managed Hermes hosting handles this
On Lobsterland, every hosted Hermes instance is generated with the per-platform block already pinned —
tool_progress: "off", show_reasoning: false and
interim_assistant_messages: false under display.platforms.slack — with a regression
test asserting the exact block so a future config change cannot quietly drop it. The scoping decision above is
ours too: Slack is pinned, our dashboard chat relay is deliberately left verbose, and
long_running_notifications is left on.
That is the whole managed claim, and it is worth being precise about its size: it means a Slack channel is quiet on day one instead of after you have found two settings and a precedence order. It does not change how Hermes behaves, and everything above still describes the runtime you are running.
If you would rather not own this configuration surface at all, see managed Hermes runtime hosting. If you are still choosing a runtime, compare the OpenClaw and Hermes runtimes on what actually differs, or read the background on what the Hermes agent runtime is, and how it differs from the Hermes models. For a second failure of the same shape — a documented remedy with an undocumented failure mode — see the Hermes memory character-limit deadlock. Running the other runtime? Our OpenClaw Slack integration guide covers a different stack with different settings, and migrating an OpenClaw instance to Hermes covers what carries over.
FAQ
Why does Hermes still post a message for every step in Slack after I set tool_progress to off?
Because tool progress is not the only emitter. interim_assistant_messages is a separate code path
that posts the model's mid-turn commentary as one Slack message per agent iteration, and Hermes documents it as
intentionally independent from tool progress and token streaming. It also alone keeps the stream consumer
alive, so switching off tool progress leaves it running. On a long turn that reaches the 90-iteration budget,
that single setting can produce dozens of messages by itself. Pin both settings under
display.platforms.slack, not just one.
How do I turn off Hermes tool progress in Slack only?
Add a per-platform block to ~/.hermes/config.yaml under
display.platforms.slack and set tool_progress to "off" there, rather
than setting display.tool_progress globally. A per-platform override is the highest-precedence
source, so it wins over the global value and over the built-in defaults. Scoping it to Slack also leaves other
surfaces alone, which matters if you run a second platform where the progress stream is useful.
In what order does Hermes resolve a display setting?
Four levels, highest first: an explicit per-platform override at
display.platforms.<platform>.<key>; then the global user setting at
display.<key>; then the built-in per-platform default for that platform; then the built-in
global default. The consequence that surprises people is that level two beats level three, so a global value
you never consciously set outranks the built-in default that exists to keep a specific platform quiet.
Why do my Hermes display settings revert after a restart?
If config.yaml has no _config_version key, Hermes reads it as schema version 0 and
re-runs its full config migration on every boot. That migration writes the global display defaults back into
the file, including tool progress and interim assistant messages, at the global precedence level. Anything you
set at the global level is therefore fighting a value the gateway rewrites at each start. A per-platform
override under display.platforms is at a higher precedence level and survives.
Should I turn off long_running_notifications too?
No. Leave it on. It is the periodic working heartbeat, and once the per-step chatter is gone it is the only signal a Slack user gets that the agent is still running. On a turn that takes thirty minutes, silence is indistinguishable from a crashed agent. It emits on a timer rather than per step, so it costs a handful of messages, not dozens.
Do Slack native task cards fix this?
Partly. Setting platforms.slack.extra.native_task_cards to true renders live tool
calls as Slack-native plan and task cards instead of plain text bubbles, which is a genuinely better answer for
teams that want to watch the work. It was requested as hermes-agent issue #29483 on 2026-05-20 and
closed on 2026-08-13. But it addresses tool progress only. It does not touch
interim_assistant_messages, so a channel switched to cards can still receive one plain message per
agent iteration.
Sources
-
Nous Research, Hermes Agent documentation — "Messaging Gateway" (per-platform
display.platforms.<platform>overrides; the built-in per-platform defaults, includinginterim_assistant_messagesstaying on). -
Nous Research, Hermes Agent documentation — "Slack" (Slack's own
tool_progress: offdefault and its rationale;platforms.slack.extra.native_task_cards). -
NousResearch/hermes-agentissue #6164, "Feature: per-platform tool_progress overrides" — opened 2026-04-08, closed 2026-04-09. -
NousResearch/hermes-agentissue #14663, "slack gateway is reporting intermediate tool calls" — opened 2026-04-23, closed 2026-04-27. -
NousResearch/hermes-agentissue #10942, "interim_assistant_messages causes final response to be silently dropped after tool calls" — opened 2026-04-16, closed 2026-04-23. -
NousResearch/hermes-agentissue #16682, "slack adapter silently disabled when config.yaml has top-level slack: block" — opened 2026-04-27, closed 2026-04-28. -
NousResearch/hermes-agentissue #29483, "Render Slack progress drafts as plan cards" — opened 2026-05-20, closed 2026-08-13. -
NousResearch/hermes-agentissue #2954, "Slack tool call progress messages appear in channel instead of thread" — opened and closed 2026-03-25. -
Lobsterland production observation and the resulting configuration change, 2026-07-20: the measured ~44
messages for one user message on Hermes v2026.7.7.2, and the generated
display.platforms.slackblock shipped for hosted Hermes instances.