You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The clarify tool currently returns "Clarify tool is not available in this execution context." for any agent running in the gateway (Telegram, Discord, Slack, etc.). gateway/run.py never passes clarify_callback= into the AIAgent(...) constructor at any of its 4 instantiation sites, so the schema-documented behavior ("ask the user a multiple-choice question") silently no-ops on every messaging platform.
This issue scopes wiring clarify end-to-end in the gateway, with inline keyboard button rendering on Telegram as the first concrete UX. Other adapters (Discord, Slack, WhatsApp, Signal, Matrix, BlueBubbles, etc.) get a numbered-text fallback via the base adapter so clarify works everywhere, with rich button UI as platform-specific upgrades on top.
Why this matters
The tool schema actively encourages calling clarify:
"The task is ambiguous and you need the user to choose an approach"
"Post-task feedback ('How did that work out?')"
"Offer to save a skill or update memory"
"A decision has meaningful trade-offs the user should weigh in on"
Today the agent calls clarify on Telegram, gets back an error string, and the user never sees the question. This is a silent broken feature in the gateway.
All 4 AIAgent(...) sites (_process_message_background, hygiene-agent, cron worker, goal-judge) get a clarify_callback= kwarg pointing at a method that:
Generates a clarify_id (monotonic counter or short uuid)
Calls the adapter's send_clarify(...) with chat_id, question, choices, clarify_id, session_key, metadata
Blocks on wait_for_clarify(clarify_id, timeout=clarify_timeout)
Returns the resolved string (or a [user did not respond within Xm] sentinel on timeout)
Default impl: render as numbered text list ("1. Choice A / 2. Choice B / Reply with the number or 'other:'"). Captures the next message in the session as the response. Falls through to the normal message handler if no clarify is pending.
4. Telegram adapter — concrete send_clarify
Mirror send_exec_approval's pattern. Pseudocode:
keyboard_rows= []
ifchoices:
fori, cinenumerate(choices):
label=f"{i+1}. {c[:60]}"# Telegram button cap is 64 byteskeyboard_rows.append([InlineKeyboardButton(label, callback_data=f"cl:{clarify_id}:{i}")])
keyboard_rows.append([InlineKeyboardButton("✏️ Other (type answer)", callback_data=f"cl:{clarify_id}:other")])
keyboard=InlineKeyboardMarkup(keyboard_rows) ifkeyboard_rowselseNoneawaitself._bot.send_message(chat_id=int(chat_id), text=f"❓ {question}", reply_markup=keyboard, ...)
self._clarify_state[clarify_id] = (session_key, "buttons"ifchoiceselse"open")
Plus a CallbackQueryHandler for the cl: prefix:
cl:<id>:N (number) → resolve_gateway_clarify(clarify_id, choices[N]), edit message to show selection
In _handle_message(), before normal routing: if session_key in self._clarify_awaiting_text, pop the entry, call resolve_gateway_clarify(clarify_id, message.text), and return early. This is the same intercept-first-then-fallthrough shape /approve already uses.
Single PR. Adapter parity for Discord/Slack/etc. lands as text fallback in this PR; rich button UI on Discord can be a separate follow-up.
Out of scope
Group polls for collective approve/deny — discussed and rejected, not a real use case
Bot-to-bot / guest mode (Bot API 10.0) — needs PTB ≥23 (just released, untested with our adapter), separate work
Checklists for todo — Premium business-account-only, separate issue worth filing later
Time/date message entities — separate polish, low priority
Design question (resolved)
"Other" path: Option A — always include "Other (type your answer)" as a final button when choices is provided, mirroring CLI behavior and matching the documented schema contract. Open-ended clarify (no choices) skips buttons entirely and captures the next message directly.
Background
Investigated as part of a Telegram blog feature audit covering 9.7 → 10.0 (Jul 2025 – May 2026). Other Bot API capabilities considered and parked: bot-to-bot (10.0), guest mode (10.0), checklists (9.1, business-account-only), poll media (10.0), live photos (10.0), document scanner (client-side), member tags, login-with-Telegram, business accounts, suggested posts, gifts, blockchain gifts, Stars billing.
Summary
The
clarifytool currently returns"Clarify tool is not available in this execution context."for any agent running in the gateway (Telegram, Discord, Slack, etc.).gateway/run.pynever passesclarify_callback=into theAIAgent(...)constructor at any of its 4 instantiation sites, so the schema-documented behavior ("ask the user a multiple-choice question") silently no-ops on every messaging platform.This issue scopes wiring clarify end-to-end in the gateway, with inline keyboard button rendering on Telegram as the first concrete UX. Other adapters (Discord, Slack, WhatsApp, Signal, Matrix, BlueBubbles, etc.) get a numbered-text fallback via the base adapter so clarify works everywhere, with rich button UI as platform-specific upgrades on top.
Why this matters
The tool schema actively encourages calling
clarify:Today the agent calls clarify on Telegram, gets back an error string, and the user never sees the question. This is a silent broken feature in the gateway.
Scope
1. Block-and-wait primitive —
tools/clarify_gateway.pyMirror the shape of
tools/approval.py'sresolve_gateway_approval():2. Wire
clarify_callbackingateway/run.pyAll 4
AIAgent(...)sites (_process_message_background, hygiene-agent, cron worker, goal-judge) get aclarify_callback=kwarg pointing at a method that:clarify_id(monotonic counter or short uuid)send_clarify(...)with chat_id, question, choices, clarify_id, session_key, metadatawait_for_clarify(clarify_id, timeout=clarify_timeout)[user did not respond within Xm]sentinel on timeout)3. Adapter base class —
gateway/platforms/base.pyNew abstract method:
Default impl: render as numbered text list ("1. Choice A / 2. Choice B / Reply with the number or 'other:'"). Captures the next message in the session as the response. Falls through to the normal message handler if no clarify is pending.
4. Telegram adapter — concrete
send_clarifyMirror
send_exec_approval's pattern. Pseudocode:Plus a
CallbackQueryHandlerfor thecl:prefix:cl:<id>:N(number) →resolve_gateway_clarify(clarify_id, choices[N]), edit message to show selectioncl:<id>:other→ enter text-capture mode (see Update snapshot #5)5. "Other" path — text capture for next message
When user picks the "Other" button, OR when clarify was open-ended (no choices), the next text message in that session becomes the response.
In
_handle_message(), before normal routing: ifsession_key in self._clarify_awaiting_text, pop the entry, callresolve_gateway_clarify(clarify_id, message.text), and return early. This is the same intercept-first-then-fallthrough shape/approvealready uses.6. Discord adapter —
discord.ui.Viewbuttons (follow-up, optional)Same UX as Telegram, using
discord.ui.View+discord.ui.Button. Can ship in this PR or as a follow-up.7. Timeouts
CLI clarify blocks forever, but the gateway can't — a stuck agent thread holds the running-agent guard and prevents
/stopfrom working cleanly.gateway.clarify_timeout_seconds, default600(10 minutes)"[user did not respond within Xm]"so the agent can decide what to do (apologize, default-choose, give up)8. Tests
Minimum coverage:
test_clarify_button_resolves: button press → callback resolves → wait_for_clarify returns the chosen stringtest_clarify_other_captures_next_message: user picks "Other", next message is captured as response, normal handler doesn't see ittest_clarify_open_ended_captures_next_message: clarify with nochoicesskips buttons and captures text directlytest_clarify_timeout_returns_sentinel: response never arrives, sentinel string returnedtest_clarify_handler_intercept_does_not_break_normal_flow: messages outside an active clarify go through normal routing9. Docs
website/docs/user-guide/messaging/telegram.md: short "Interactive prompts" subsection covering buttons + Otherwebsite/docs/user-guide/configuration.mdunder gateway settings (timeout)Effort estimate
~250–400 LOC across:
tools/clarify_gateway.py(new, ~80 LOC)gateway/platforms/base.py(+abstract method, +default text impl, ~50 LOC)gateway/platforms/telegram.py(+send_clarify, +callback handler, +text-capture intercept, ~120 LOC)gateway/run.py(+callback factory, +wire at 4 AIAgent sites, ~50 LOC)hermes_cli/config.py(+gateway.clarify_timeout_secondsdefault)Single PR. Adapter parity for Discord/Slack/etc. lands as text fallback in this PR; rich button UI on Discord can be a separate follow-up.
Out of scope
todo— Premium business-account-only, separate issue worth filing laterDesign question (resolved)
"Other" path: Option A — always include "Other (type your answer)" as a final button when
choicesis provided, mirroring CLI behavior and matching the documented schema contract. Open-ended clarify (nochoices) skips buttons entirely and captures the next message directly.Background
Investigated as part of a Telegram blog feature audit covering 9.7 → 10.0 (Jul 2025 – May 2026). Other Bot API capabilities considered and parked: bot-to-bot (10.0), guest mode (10.0), checklists (9.1, business-account-only), poll media (10.0), live photos (10.0), document scanner (client-side), member tags, login-with-Telegram, business accounts, suggested posts, gifts, blockchain gifts, Stars billing.