Usage Tips

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:

  1. display.platforms.<platform>.<key> — an explicit per-platform override
  2. display.<key> — the global user setting
  3. the built-in per-platform default for that platform
  4. 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". Bare off is 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 global display.tool_progress is 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 — from $6.90/mo Your own hosted OpenClaw instance with automatic restarts and version upgrades. Starts with a 7-day free trial — import your current setup, keep your channels, cancel anytime.
$199 managed setup — optional Prefer we do it for you? One workspace configured end-to-end: first-run config, one 30-minute onboarding/debug session, and a 7-day follow-up. Limited weekly slots.
  • Managed hosting handles stale .jsonl.lock files, 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.

OpenClaw import first screen in the Lobsterland dashboard (light theme) OpenClaw import first screen in the Lobsterland dashboard (dark theme)
1) Paste import payload
OpenClaw import completed screen in the Lobsterland dashboard (light theme) OpenClaw import completed screen in the Lobsterland dashboard (dark theme)
2) Review and launch

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, including interim_assistant_messages staying on).
  • Nous Research, Hermes Agent documentation — "Slack" (Slack's own tool_progress: off default and its rationale; platforms.slack.extra.native_task_cards).
  • NousResearch/hermes-agent issue #6164, "Feature: per-platform tool_progress overrides" — opened 2026-04-08, closed 2026-04-09.
  • NousResearch/hermes-agent issue #14663, "slack gateway is reporting intermediate tool calls" — opened 2026-04-23, closed 2026-04-27.
  • NousResearch/hermes-agent issue #10942, "interim_assistant_messages causes final response to be silently dropped after tool calls" — opened 2026-04-16, closed 2026-04-23.
  • NousResearch/hermes-agent issue #16682, "slack adapter silently disabled when config.yaml has top-level slack: block" — opened 2026-04-27, closed 2026-04-28.
  • NousResearch/hermes-agent issue #29483, "Render Slack progress drafts as plan cards" — opened 2026-05-20, closed 2026-08-13.
  • NousResearch/hermes-agent issue #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.slack block shipped for hosted Hermes instances.
Cookie preferences