Skip to content

webhook: clarify tool unavailable to agents spawned by webhook routes #31565

@alexcf

Description

@alexcf

Issue title

gateway/webhook: clarify tool unavailable to agents spawned by webhook routes that have deliver=<messaging-platform> configured

Summary

When a webhook route is configured with deliver: discord (or
telegram, slack, etc.) and deliver_extra.chat_id set, the agent
that runs in response to the webhook can invoke the clarify tool —
but the call returns:

Clarify tool is not available in this execution context.

(In recent versions the agent does block on clarify for the
configured clarify_timeout — but the base text fallback writes a
numbered question to the webhook session, which the gateway's
text-intercept can't resolve because it's keyed on the SOURCE
platform's session_key. The agent times out after 10 minutes and the
user gets nothing actionable.)

Either way, webhook agents can't ask the user clarifying questions
even when the route explicitly delivers to a platform that supports
native buttons.

Reproduction

~/.hermes/config.yaml:

platforms:
  webhook:
    enabled: true
    extra:
      port: 8644
      routes:
        demo-clarify:
          secret: "demo-secret"
          deliver: discord
          deliver_extra:
            chat_id: "<your channel id>"
          prompt: |
            Use the `clarify` tool to ask the user "Which colour?"
            with choices ["red", "green", "blue"], then echo their
            choice as a one-line response.

POST to https://<host>:8644/webhooks/demo-clarify with the route
secret. Observe the response (no buttons; either an error string or
a 10-minute hang).

Expected behaviour

When deliver is a connected messaging platform and
deliver_extra.chat_id resolves to a real channel, clarify should
render in that channel via the target platform's existing
send_clarify implementation — native buttons on Discord/Telegram/
Slack, text fallback elsewhere — and button-click resolution should
unblock the webhook agent.

Why this matters

Webhook routes are documented as a way for external services to
trigger agent runs that produce rich responses on messaging
platforms (the docs page calls out Discord/Telegram/Slack as
first-class deliver targets). Without clarify access, the agent
can't ask the user follow-up questions — so the use case is limited
to one-shot text responses.

Practical example: a workflow engine (in our case, a personal email
triage tool) pushes "draft ready for approval" events into a Hermes
webhook. The agent needs to ask the user Send / Discard / Edit
and act on the answer. Without clarify, the agent can only emit
bare URLs the user has to tap manually.

Fix sketch (PR forthcoming)

Implement WebhookAdapter.send_clarify to delegate to the route's
target platform adapter:

async def send_clarify(self, chat_id, question, choices, clarify_id,
                       session_key, metadata=None):
    delivery = self._delivery_info.get(chat_id, {})
    platform_name = (delivery.get("deliver") or "").strip().lower()

    # Fall back to base text behaviour for log / github_comment / unknown.
    if not platform_name or platform_name in {"log", "github_comment"}:
        return await super().send_clarify(...)
    if not self.gateway_runner:
        return await super().send_clarify(...)
    try:
        target_platform = Platform(platform_name)
    except ValueError:
        return await super().send_clarify(...)
    adapter = self.gateway_runner.adapters.get(target_platform)
    if not adapter:
        return await super().send_clarify(...)

    extra = delivery.get("deliver_extra", {}) or {}
    target_chat = extra.get("chat_id") or (
        self.gateway_runner.config.get_home_channel(target_platform)
        and self.gateway_runner.config.get_home_channel(target_platform).chat_id
    )
    if not target_chat:
        return await super().send_clarify(...)

    # Forward Telegram thread_id when present.
    forwarded = dict(metadata) if metadata else {}
    thread_id = extra.get("message_thread_id") or extra.get("thread_id")
    if thread_id and "thread_id" not in forwarded:
        forwarded["thread_id"] = thread_id

    return await adapter.send_clarify(
        chat_id=target_chat,
        question=question,
        choices=choices,
        clarify_id=clarify_id,
        session_key=session_key,
        metadata=forwarded or None,
    )

Resolution is already platform-agnostic — clarify_gateway._entries
is module-level, so a Discord button click via the target adapter
correctly unblocks the webhook agent's wait_for_response.

Versions

  • Hermes: latest as of 2026-05-24 (commit bc3f1f4f3 after hermes update --yes)
  • macOS host running hermes gateway run
  • Discord adapter connected with native send_clarify implementation

Happy to send the PR — let me know if you'd prefer a different
approach (e.g. requiring an explicit clarify_target route key
rather than reusing deliver).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/gatewayGateway runner, session dispatch, deliveryplatform/webhookWebhook / API servertype/bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions