hermes - 💡(How to fix) Fix Discord: tool-using responses (api_calls≥2) silently dropped — no `Sending response` log after `response ready`

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When an agent response involves at least one tool call (api_calls >= 2), the gateway logs response ready with a non-zero char count but never emits the corresponding [Discord] Sending response (...) log line. The session JSON contains the full final assistant message, but it never reaches the user's Discord channel.

Responses with api_calls == 1 (no tool call) deliver fine. The bug is consistent for multi-turn tool-using replies in our setup.

Root Cause

When an agent response involves at least one tool call (api_calls >= 2), the gateway logs response ready with a non-zero char count but never emits the corresponding [Discord] Sending response (...) log line. The session JSON contains the full final assistant message, but it never reaches the user's Discord channel.

Responses with api_calls == 1 (no tool call) deliver fine. The bug is consistent for multi-turn tool-using replies in our setup.

Fix Action

Fix / Workaround

Workaround (current)

Code Example

22:14:12,807  response ready: ... api_calls=1 response=179 chars
22:14:12,817  [Discord] Sending response (179 chars) to ...     ← delivered ✓

22:29:31,526  response ready: ... api_calls=1 response=288 chars
22:29:31,529  [Discord] Sending response (288 chars) to ...     ← delivered ✓

22:30:02,568  response ready: ... api_calls=2 response=1132 chars
              (no Sending response log)                          ← lost ✗

22:16:28,366  response ready: ... api_calls=4 response=372 chars
              (no Sending response log)                          ← lost ✗

---

if text_content:
    logger.info("[%s] Sending response (%d chars) to %s", ...)
    result = await self._send_with_retry(...)

---

if not text_content and response:
    logger.warning(
        "[%s] Response stripped to empty after extract pipeline (orig=%d chars). "
        "Sending raw response as fallback.",
        self.name, len(response),
    )
    text_content = response  # or some safe variant
RAW_BUFFERClick to expand / collapse

Tool-using responses silently dropped on Discord (no Sending response log after response ready)

Environment

  • Hermes Agent commit: 1c4d3216d3855848a632847306c96f4c3090b259
  • macOS Intel
  • Profile-based gateway: hermes --profile jenny gateway run --replace
  • Discord platform adapter
  • Model: google/gemini-2.5-flash via OpenRouter
  • Tools enabled: web, browser, terminal, file, code_execution, etc.

Summary

When an agent response involves at least one tool call (api_calls >= 2), the gateway logs response ready with a non-zero char count but never emits the corresponding [Discord] Sending response (...) log line. The session JSON contains the full final assistant message, but it never reaches the user's Discord channel.

Responses with api_calls == 1 (no tool call) deliver fine. The bug is consistent for multi-turn tool-using replies in our setup.

Reproduction

  1. Set up a Discord profile with web tool enabled.
  2. Ask the agent something that requires a real-time lookup (e.g. "最近台北到巴黎機票最便宜的是?").
  3. Agent generates a preamble announcing it will use web_search.
  4. Tool executes successfully (verified — KAYAK data returned in session).
  5. Agent generates a final 1000+ char answer with prices.
  6. No Discord message appears for the final answer.

Observed log pattern

~/.hermes/profiles/<profile>/logs/agent.log:

22:14:12,807  response ready: ... api_calls=1 response=179 chars
22:14:12,817  [Discord] Sending response (179 chars) to ...     ← delivered ✓

22:29:31,526  response ready: ... api_calls=1 response=288 chars
22:29:31,529  [Discord] Sending response (288 chars) to ...     ← delivered ✓

22:30:02,568  response ready: ... api_calls=2 response=1132 chars
              (no Sending response log)                          ← lost ✗

22:16:28,366  response ready: ... api_calls=4 response=372 chars
              (no Sending response log)                          ← lost ✗

Every entry with api_calls >= 2 is missing the corresponding send. No errors are logged in errors.log or gateway.log for the affected timestamps.

Hypothesis

In gateway/platforms/base.py around line 1699:

if text_content:
    logger.info("[%s] Sending response (%d chars) to %s", ...)
    result = await self._send_with_retry(...)

The send is silently skipped when text_content is falsy. Between the response = await self._message_handler(event) at line 1621 (which returned 1132 chars) and the if text_content: guard, the response flows through:

  • extract_media (line 1646)
  • extract_images (line 1649)
  • Manual strips for [[audio_as_voice]] and MEDIA: (lines 1651–1652)
  • extract_local_files (line 1658)

One of these pipelines is presumably reducing tool-using responses to an empty string. Likely candidates:

  1. extract_local_files regex matching URL fragments that look like local file paths (e.g. when responses contain markdown with paths-like strings)
  2. extract_images mishandling certain markdown link patterns
  3. Some other transformation specific to multi-LLM-call responses

It would help to add a logger.debug (or warning when response was non-empty but text_content is now empty) right before line 1699 so this category of silent failure is observable.

Session evidence

The full final assistant message exists in the session JSON at ~/.hermes/profiles/<profile>/sessions/session_<timestamp>.json. Happy to share a redacted copy if useful.

Suggested fix

At minimum, log a WARNING when response was non-empty but text_content ends up empty:

if not text_content and response:
    logger.warning(
        "[%s] Response stripped to empty after extract pipeline (orig=%d chars). "
        "Sending raw response as fallback.",
        self.name, len(response),
    )
    text_content = response  # or some safe variant

Or, narrower: ensure extract_local_files and extract_images never reduce non-empty content to an empty string — clamp to a minimum of original response if stripping is aggressive.

Workaround (current)

Profile SOUL.md updated to instruct the agent to never call web_search for flight lookups, and instead emit pre-filled search-engine URLs in a single LLM turn so api_calls == 1 and the response reliably delivers.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING