Skip to content

feat(gateway): add compact action buttons - #82727

Open
isolovyev77 wants to merge 2 commits into
NousResearch:mainfrom
isolovyev77:macbot/action-buttons-upstream-20260807-221539
Open

isolovyev77 wants to merge 2 commits into
NousResearch:mainfrom
isolovyev77:macbot/action-buttons-upstream-20260807-221539

Conversation

@isolovyev77

Copy link
Copy Markdown

feat(gateway): add compact action buttons

Summary

This PR adds a small fixed-choice interaction primitive, action_buttons, for cases where Hermes needs compact buttons rather than a full clarify/free-text prompt.

It adds:

  • a new action_buttons tool with 1-4 fixed choices;
  • gateway registration, waiting, timeout handling, and callback resolution;
  • Telegram inline keyboard rendering with compact numeric labels;
  • callback resolution back to the full semantic choice text;
  • tests covering rendering, callback resolution, tool callback behavior, and gateway register/resolve roundtrip.

Problem

clarify is intentionally general-purpose: it can show multiple choices and may include a free-text Other path. That is useful for open-ended clarification, but it is not ideal for Telegram workflows where:

  • the message body already explains the available actions;
  • the visible buttons should be only short selectors such as 1, 2, 3;
  • there should be no implicit Other branch;
  • a tapped button should still resolve to the full underlying action text.

Without a dedicated primitive, agents either duplicate long labels on Telegram buttons or fall back to plain numbered prose, which is easier to miss and harder to bind to a specific message.

Related work / duplicate check

I searched open issues and PRs before preparing this PR, per CONTRIBUTING.md:

  • repo:NousResearch/hermes-agent is:issue "action buttons"
  • repo:NousResearch/hermes-agent is:issue "inline keyboard" "Telegram"
  • repo:NousResearch/hermes-agent is:pr "action buttons"
  • repo:NousResearch/hermes-agent is:pr "inline keyboard" "Telegram"
  • repo:NousResearch/hermes-agent is:pr "Other" "clarify" "Telegram"

Relevant existing threads:

So this is not intended as a competing broad action-menu proposal. It is a small, reviewable implementation of the fixed-choice subset that keeps the gateway callback path explicit and testable.

Why this is a tool, not a skill

CONTRIBUTING.md says most new capabilities should be skills and new tools are rare. I think this case fits the tool/gateway exception because it cannot be expressed reliably as instructions plus existing tools:

  • Telegram callback queries must be handled by the gateway process that owns bot updates.
  • The choice must be bound to a specific pending tool call/session, not inferred from later free text.
  • The visible Telegram label (1, 2, 3) and the semantic value (Deploy to production) must be separated server-side.
  • The agent must block/wait and receive a structured result, which requires harness integration rather than a best-effort shell/skill recipe.

Proposed behavior

The model/tool call provides full semantic choices:

{
  "question": "Which deployment target?",
  "choices": ["Deploy to staging", "Deploy to production"]
}

Telegram renders the human-readable body with full options:

Which deployment target?

1. Deploy to staging
2. Deploy to production

The inline keyboard shows compact buttons in one row:

[1] [2]

When the user taps 2, the gateway resolves the tool response to:

Deploy to production

This keeps Telegram UI compact while preserving unambiguous semantic choice handling inside the agent flow.

Design notes

  • action_buttons is deliberately separate from clarify.
  • It has no automatic free-text Other branch.
  • The visible Telegram labels are numeric selectors; the choices stored in gateway state remain the full strings.
  • The Telegram callback payload is compact: act:{action_id}:{idx}.
  • Pending entries are stored in tools.action_buttons_gateway, mirroring the existing gateway-mediated interaction pattern.
  • The Telegram message is edited after resolution to remove the keyboard and show the selected option.
  • Short numeric buttons are kept on one row because the full text is already in the message body.

Non-goals

  • This does not replace clarify.
  • This does not add open-ended text capture.
  • This does not add a new approval/security prompt path.
  • This does not introduce platform-specific long labels into the model-visible choices.

Files touched

  • tools/action_buttons_tool.py
  • tools/action_buttons_gateway.py
  • toolsets.py
  • agent/agent_init.py
  • agent/tool_executor.py
  • agent/agent_runtime_helpers.py
  • run_agent.py
  • gateway/run.py
  • plugins/platforms/telegram/adapter.py
  • tests/gateway/test_telegram_action_buttons.py

Validation

Branch was rebased onto current origin/main before validation.

Local branch state:

  • git rev-list --left-right --count origin/main...HEAD -> 0 2
  • commits:
    • 4e4eb5a4d9 feat(gateway): add compact action buttons
    • a72015f776 fix(telegram): keep action buttons on one row

Checks run locally:

  • python3 -m py_compile agent/agent_init.py agent/tool_executor.py gateway/run.py plugins/platforms/telegram/adapter.py run_agent.py tools/action_buttons_gateway.py tools/action_buttons_tool.py tests/gateway/test_telegram_action_buttons.py toolsets.py
  • Direct contract check:
    • py_compile=ok
    • action_buttons_contracts=ok
  • git diff --check origin/main...HEAD -> ok

pytest could not be run in this local worktree environment because the environment lacks pytest:

  • command: python -m pytest tests/gateway/test_telegram_action_buttons.py -o 'addopts=' -q
  • result: No module named pytest

Live validation on a Telegram gateway after applying the same layout fix locally:

  • gateway restarted successfully;
  • Telegram rendered action buttons as [1] [2] [3] in one row;
  • a real user tap resolved successfully back into the agent flow.

Platforms/environment checked:

  • OS: Linux 6.8.0-124-generic x86_64
  • Python: 3.11.15
  • Head commit: a72015f776
  • Base commit at validation: 2446c8bb67

Security policy check

This PR is not a security vulnerability report and does not cross the security-policy reporting threshold. It does not claim a sandbox/containment boundary, does not change the approval security model, and does not include credentials.

Security-relevant review points:

  • callback payloads contain only action id + choice index, not executable commands or secrets;
  • choices are registered server-side and resolved through gateway state;
  • Telegram callback handling uses the existing adapter authorization pattern before resolving the button tap;
  • the keyboard is removed after selection to reduce stale-button reuse.

A simple changed-file scan for obvious credential strings was also run locally before PR preparation.

Maintainer review focus

The main design question is whether this should remain a separate fixed-choice primitive or be folded into clarify as a mode. I kept it separate because the UX contract is different: fixed actions, no Other, compact numeric labels, and full semantic resolution.

If maintainers prefer a different naming or platform abstraction, the implementation is intentionally small and isolated enough to adapt.

@alt-glitch alt-glitch added type/feature New feature or request comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets platform/telegram Telegram bot adapter P3 Low — cosmetic, nice to have sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Aug 9, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Related to #52252 and #52497. This PR implements a synchronous fixed-choice tool that waits for and returns a selected value; #52497 provides fire-and-forget action-menu infrastructure with a separate dispatch seam. These are overlapping approaches rather than duplicates; maintainer review should choose whether both primitives belong.

@isolovyev77

Copy link
Copy Markdown
Author

Thanks for the triage note. That distinction matches my intent.

I agree this overlaps with #52252 / #52497, but I see this PR as the synchronous fixed-choice subset rather than a competing fire-and-forget action-menu system:

  • feat(gateway/telegram): inline action-menu buttons for agent/scheduled messages #52497: attach action buttons to an outbound message and dispatch taps through a separate seam.
  • This PR: expose a tool-call primitive where the agent waits for a bounded user choice and receives the selected semantic value as the tool result.
  • clarify: remains the open-ended clarification primitive, including the Other path.
  • action_buttons: no Other, compact numeric labels, full semantic value returned to the waiting agent turn.

If maintainers prefer one unified abstraction, I’m happy to adapt this PR in that direction. The part I’d like to preserve is the synchronous “wait for one of these fixed choices and return the selected value” contract, because it covers a different agent-flow shape than fire-and-forget action menus.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets P3 Low — cosmetic, nice to have platform/telegram Telegram bot adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants