Skip to content

fix(discord, toolsets): clarify body text, thread auto-archive, plugin toolset warning - #114561

Open
chrisluersen wants to merge 2 commits into
NousResearch:mainfrom
chrisluersen:fix/discord-cli-desktop
Open

chrisluersen wants to merge 2 commits into
NousResearch:mainfrom
chrisluersen:fix/discord-cli-desktop

Conversation

@chrisluersen

Copy link
Copy Markdown

Three independent fixes, one commit each. A closed PR is re-opened here with a corrected shape —
see fix 3.


1. Discord: clarify choices rendered in the message body, buttons carry numbers

send_clarify put the whole option text in the button label. Discord draws every button at the
same width, so a long choice is cut mid-word on mobile — and nothing else in the message carried
the option text: the (truncated) label was the only copy. Telegram and WhatsApp already print the
numbered list above the buttons.

Now the buttons are bare numbers 1..N and the full numbered list prints in the embed (fields
chunked at Discord's 1024-char field cap, budgeted against the ~6000-char embed cap) and in
plain content — embeds are invisible/detached on some clients, and the question is never the part
that gets dropped. Resolution is unchanged: a click round-trips the canonical choice text from the
gateway entry, not the label.

tests/gateway/test_discord_clarify_buttons.py — proven red on main (3 failed / 4 passed;
the exact failure is assert ['1. real choice'] == ['1']), 7/7 green with the fix.

2. Discord: threads Hermes opens inherit the channel's auto-archive default

_auto_create_thread and create_handoff_thread both hardcoded auto_archive_duration=1440,
contradicting the parent channel's own default. On a channel set to 10080 (7 d), a thread the
agent opened dropped out of the sidebar 24 h after its last message while a thread a human opened
in the same channel survived a week — two lifetimes for identical content. Measured live
2026-09-15: every gateway-opened thread carried dur=1440 while its channel default was 10080.

One rule now, derived from the channel, shared by both thread-opening paths. An absent or
non-conforming channel default still falls back to 1440, so the value passed to create_thread is
always one Discord accepts. The /thread command's documented 1440 default is untouched — that is
an explicit caller-supplied argument.

tests/gateway/test_discord_auto_thread_archive.py (new) — proven red on main
(7 failed / 6 passed), 13/13 green with the fix. The assertions are relationships between the
channel default and the duration passed to create_thread, not snapshots.

3. Toolsets: config-declared plugin toolsets no longer reported as "unknown"

Plugin toolsets (a2a, eikon, buzz, …) enter the tool registry only when plugins are
discovered — later than both config-migration validation (hermes_cli/config.py) and CLI
construction (cli.py::_init_toolsets). Both call sites validated against the registry alone, so
every startup warned Unknown toolsets: a2a, buzz, eikon about a toolset the user was explicitly
offered by hermes tools.

This re-submits #97374, which was closed on 2026-09-09 without merging. The bug still reproduces
on current main (0a8d4caef4), and it is not implemented_on_main or cannot_reproduce:

$ python repro_config_toolset.py            # fresh HERMES_HOME, plugin toolset declared in config
== before plugin discovery ==
   validate_toolset('a2a') = False
   warnings (config path, pre-discovery):
     - platform 'discord' references unknown toolset 'a2a' — did you mean 'hermes-discord'?
== after plugin discovery ==
   validate_toolset('a2a') = True

The previous shape called discover_plugins() from the config path. Measured cold, that costs
~870 ms of startup
for a warning — exactly the cost the CLI's deliberate "argparse setup skips
discovery (~500 ms)" rule exists to avoid — so this version never discovers plugins at all:

  • hermes_cli/plugins.py::get_plugin_toolset_keys_cached() — the live registry when it is already
    populated, otherwise the keys the previous launch recorded (cache/plugin_toolset_keys.json,
    already written on every discovery). Never blocks, never discovers.
  • hermes_cli/toolset_validation.py::known_plugin_toolset_keys() / with_plugin_toolsets() —
    the latter widens the injected validity predicate, so validate_platform_toolsets keeps its
    pure-predicate contract.

Both are probed only after a name has actually been rejected, so a config that validates clean
pays nothing:

import hermes_cli.config         : 250 ms
warn pass (all names validate)   :  10 ms   # unchanged from base — no plugin probe runs
end-to-end, plugin keys cached   : base  -> 2 false "unknown toolset" warnings
                                   fixed -> none, while a bogus name still warns

The set is advisory and self-healing by construction: it is only ever used to EXCLUDE names from a
warning, so a stale-too-small set can make a later run warn, never the reverse.

Tests (each proven red on main): tests/hermes_cli/test_toolset_validation.py
(test_declared_plugin_toolset_is_not_reported_unknown — asserts the premise
not validate_toolset("a2a") before asserting no warning) and
tests/hermes_cli/test_cli_init.py (test_plugin_toolset_not_warned, which fails on main with
Warning: Unknown toolsets: a2a, buzz, eikon), plus the guard that a genuinely unknown name still
warns.


Test receipts

All runs via scripts/run_tests.sh against a clone of origin/main 0a8d4caef4:

suite base (unfixed) with the fix
tests/gateway/test_discord_clarify_buttons.py 3 failed / 4 passed 7 passed
tests/gateway/test_discord_auto_thread_archive.py 7 failed / 6 passed 13 passed
tests/hermes_cli/test_toolset_validation.py 1 failed / 13 passed 14 passed
tests/hermes_cli/test_cli_init.py (new class) fails with the false-positive warning 2 passed
8 neighbouring suites (test_tools_config, test_commands, test_cli_tools_command, test_config_validation, test_setup_blank_slate, test_plugin_config_state_bridge, test_completer_config_reads, gateway/test_api_server_toolset) — 168 passed / 0 failed

Could not verify

  • tests/hermes_cli/test_cli_init.py::TestPromptToolkitTerminalCompatibility has 2 pre-existing
    failures on this Windows host
    (test_cpr_gating_posix_suppresses_without_ssh,
    test_lf_enter_binding_respects_multiline_shortcuts — the POSIX arm asserts on a Windows
    console). They fail identically with and without these commits and are untouched here.
  • The Discord fixes are asserted against the gateway test doubles, not a live Discord workspace;
    the thread auto-archive default was measured live on 2026-09-15 (recorded in the commit message)
    rather than re-measured for this PR.
  • The first-ever launch with no cache/plugin_toolset_keys.json and a hand-written plugin toolset
    in platform_toolsets can still warn once; it self-heals on the next launch. Closing that hole
    needs discovery, which is the ~870 ms this change deliberately avoids.

@chrisluersen

Copy link
Copy Markdown
Author

Re-submits #97374 (fix(config): stop flagging plugin toolsets as unknown), which was closed 2026-09-09 without merging. The bug still reproduces on current main (0a8d4caef4), so the close was not implemented_on_main and not cannot_reproduce. The config-path commit here also changes shape: instead of calling discover_plugins() at config load (measured ~870 ms cold), it reads the plugin keys the previous launch recorded and never triggers discovery.

The two Discord fixes in this PR have no upstream PR of their own.

@alt-glitch alt-glitch added type/bug Something isn't working P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard comp/plugins Plugin system and bundled plugins platform/discord Discord bot adapter area/config Config system, migrations, profiles sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Sep 18, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related: open #111322 also uses persisted plugin-toolset keys to avoid validation before discovery. This PR additionally repairs the CLI/config call sites and two Discord UX issues, so it is related rather than a duplicate; please consolidate the overlapping validation approach.

@whyyagswhy

Copy link
Copy Markdown

Independent verification on the PR head (b5338a5): cli-init plus toolset-validation suites 58/58 green (1 skipped) on Linux. Widening the unknown-toolset predicate with last-launch-recorded plugin keys (instead of paying discovery cost at validation time) is the right trade, and best-effort-empty beats raising on an unavailable layer. No findings.

@alt-glitch alt-glitch added the sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages label Sep 18, 2026
@kyssta-exe

Copy link
Copy Markdown

Summary

Three independent fixes, one commit each: Discord clarify buttons become bare numbers with the full numbered option list in the message body; gateway-opened threads inherit the parent channel's auto-archive window instead of hardcoded 1440; config-declared plugin toolsets (a2a, buzz, eikon) no longer warn as unknown, via a cached key set instead of ~870 ms of plugin discovery.

What changed

  • plugins/platforms/discord/adapter.py: bare-number clarify buttons + _numbered_choice_fields/_clarify_content (embed fields chunked at the 1024-char cap within the embed budget, mirrored in plain content); _auto_thread_archive_minutes shared by _auto_create_thread (both paths) and create_handoff_thread, falling back to 1440 on absent/invalid channel defaults.
  • hermes_cli/plugins.py, hermes_cli/toolset_validation.py: get_plugin_toolset_keys_cached (live registry if discovered, else last launch's cache), known_plugin_toolset_keys/with_plugin_toolsets; cli.py and hermes_cli/config.py probe only after a name is actually rejected.
  • Tests: new test_discord_auto_thread_archive.py (13), updated clarify-button tests, new toolset-validation and CLI-init cases.

Strengths

Findings

  • Non-blocking: with_plugin_toolsets memoizes plugin_keys in its closure, so keys from a mid-process discovery after a first rejection aren't picked up until restart. Acceptable given exclusion-only use, but a one-line comment would help future readers.
  • Non-blocking: _DISCORD_CHOICE_FIELD_VALUE_LIMIT = 1000 vs the 1024 field cap is conservative; fine.

Verdict

Looks good to merge.

Reviewed using Hermes-Agent

@chrisluersen
chrisluersen force-pushed the fix/discord-cli-desktop branch from b5338a5 to 0b47880 Compare September 19, 2026 11:54
@chrisluersen
chrisluersen force-pushed the fix/discord-cli-desktop branch from 0b47880 to ff85518 Compare September 29, 2026 22:55
… default

_auto_create_thread and create_handoff_thread both hardcoded
auto_archive_duration=1440, contradicting the channel's own default: on a channel
set to 10080 (7d) an agent-opened thread dropped out of the sidebar 24h after its
last message while a human-opened one in the same channel survived a week.
Measured live 2026-09-15: every gateway-opened thread carried dur=1440 while its
channel default was 10080.

One rule now, derived from the channel, shared by every thread-opening path; an
absent or non-conforming channel default still falls back to 1440 so the value is
always one Discord accepts. The /thread command's documented 1440 default is left
alone — that one is an explicit caller-supplied argument.

Invariant test proven red on current main (7 failed / 6 passed), 13 pass with the fix.
… carry numbers

Discord draws every button at the same width, so a long clarify choice was cut
mid-word on mobile while nothing else in the message carried the option text —
the label was the only copy. send_clarify now prints the numbered, full-length
choice list above the buttons (embed fields chunked to Discord's 1024-char field
cap, budgeted against the ~6000-char embed cap; mirrored into plain content,
which never drops the question) and ClarifyChoiceView labels buttons 1..N.
Resolution is unchanged: a click round-trips the canonical choice text from the
gateway entry, not the label. Matches Telegram and WhatsApp, which already do
this. Tests updated: bare-number labels + full text in embed and content.
@chrisluersen
chrisluersen force-pushed the fix/discord-cli-desktop branch from ff85518 to c6e098b Compare September 29, 2026 23:01

keeltrace commented Sep 30, 2026 •

Copy link
Copy Markdown

Correction to my comment above after one more direct probe:

I read /home/j/.hermes/profiles/muna/cache/plugin_toolset_keys.json from the same Muna runtime. It currently contains:

toolset_keys = [a2a, hermes_remote_worker, nerve, spotify]

and that very process still printed Warning: Unknown toolsets: nerve, shared_context, token_economy before the turn. So my proposed explanation that synchronous discovery had left the Muna cache stale is not supported by the actual cache contents. Please disregard that causal claim.

The stronger live finding still stands: nerve is enabled, is present in the profile-scoped persisted key cache, hermes tools list sees it, and fresh Muna sessions can call nerve_stats, while startup can still warn that nerve is unknown.

That points instead to the startup validation path reading the wrong profile scope/cache/manager (or otherwise not consulting the same profile-scoped key set), rather than simply failing to persist discovery. I am isolating that separately.

@alt-glitch alt-glitch added area/i18n Localization, locales, translations and removed comp/cli CLI entry point, hermes_cli/, setup wizard labels Sep 30, 2026

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

area/config Config system, migrations, profiles area/i18n Localization, locales, translations comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have platform/discord Discord bot adapter sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants