Skip to content

feat: restore /model slash command for CLI and gateway#3360

Open
virtaava wants to merge 2 commits intoNousResearch:mainfrom
virtaava:feat/restore-model-command
Open

feat: restore /model slash command for CLI and gateway#3360
virtaava wants to merge 2 commits intoNousResearch:mainfrom
virtaava:feat/restore-model-command

Conversation

@virtaava
Copy link
Copy Markdown
Contributor

@virtaava virtaava commented Mar 27, 2026

Summary

Reverts the functional removal from #3080 which deleted the /model slash command from both CLI and all messenger gateways.

The problem: #3080 left hermes model CLI subcommand and config.yaml editing as the only ways to switch models. This works for users with shell access but completely breaks model switching for:

  • All remote/gateway users (Telegram, Discord, Slack, WhatsApp, API server) who have no shell access to run CLI subcommands or edit config.yaml on the host machine
  • Headless/Docker/cloud deployments where the only interface is a messenger bot
  • CLI users who must exit their session or open a second terminal, breaking conversational flow

The ACP adapter (VS Code/Zed/JetBrains) kept its own /model handler, creating an inconsistency where IDE users can switch models but CLI and gateway users cannot.

Related Issues

This PR addresses or unblocks several open issues that depend on /model existing:

What this PR does

Restores /model as a thin wrapper around the existing model_switch.py module (which #3080 left intact for ACP use). No new logic — just reconnects the UI to the existing switching infrastructure.

Restored:

  • CommandDef entry in COMMAND_REGISTRY
  • CLI process_command() handler delegating to model_switch.py
  • Gateway _handle_model_command() for all messenger platforms
  • All /model-specific tests (210 lines)

NOT restored (intentionally):

  • SlashCommandCompleter model_completer_provider — Tab autocomplete (~95 lines, complex and fragile)
  • Two-stage ghost text suggestions

Test plan

  • /model with no args shows current model + available providers
  • /model deepseek:deepseek-chat switches model and provider
  • /model custom auto-detects model from custom endpoint
  • /model invalid:nonexistent shows clear error
  • Gateway (Telegram): /model works in chat
  • Tests pass: pytest tests/test_cli_model_command.py tests/hermes_cli/test_commands.py

…3080 revert)

PR NousResearch#3080 removed the /model slash command from both the interactive CLI
and all messenger gateways (Telegram/Discord/Slack/WhatsApp), leaving
only `hermes model` CLI subcommand and config.yaml editing as alternatives.

This breaks the workflow for:
- **All remote users** who access Hermes via Telegram, Discord, Slack,
  WhatsApp, or the API server — they have no shell access to run CLI
  subcommands and cannot edit config.yaml on the host machine
- **Gateway-only deployments** (headless servers, Docker, cloud) where
  the only interface is a messenger bot — model switching becomes
  impossible without SSH access to the physical machine
- **CLI users** who must exit their chat session or open a second terminal
  to run `hermes model`, breaking conversational flow
- **Custom endpoint users** who relied on `/model custom` for live API
  probe and auto-detection

The ACP adapter (VS Code/Zed/JetBrains) kept its own `/model` handler,
creating an inconsistency where IDE users can switch models but CLI and
gateway users cannot.

The underlying `model_switch.py` module was left intact by NousResearch#3080 (used
by ACP/IDE adapters), so the switching logic already exists. The /model
command is a thin wrapper calling it.

Restores:
- CommandDef entry in COMMAND_REGISTRY with help text and examples
- CLI process_command() handler delegating to model_switch.py
- Gateway _handle_model_command() for all messenger platforms
- All /model-specific tests

Does NOT restore (intentionally removed as unnecessary):
- SlashCommandCompleter model_completer_provider (Tab autocomplete)
- Two-stage ghost text suggestions

The autocomplete was complex (~95 lines) and fragile. The command itself
is simple enough without it: `/model deepseek:deepseek-chat` or
`/model custom` works without Tab completion.

Co-Authored-By: Sona <sona_openclaw@proton.me>
@NivOO5
Copy link
Copy Markdown

NivOO5 commented Mar 28, 2026

I reviewed and locally reimplemented/tested this flow on a separate branch before noticing this PR already covered the same area. One subtle thing I’d recommend double-checking before merge: preserve any existing model.api_key config reference (for example model.api_key: ) when /model updates provider/default/base_url, and avoid writing resolved runtime API keys back into config.yaml during command handling.\n\nI also added/reran focused gateway-side tests locally around:\n- /model registration + dispatch\n- /model show current model/provider\n- provider:model persistence\n- /model custom persisting base_url\n- preserving existing config api_key references\n\nLocal validation on my side passed for the targeted gateway tests.\n\nThanks for restoring this — gateway /model is definitely important for Telegram/headless deployments.

virtaava added a commit to virtaava/hermes-agent that referenced this pull request Mar 28, 2026
…witch

When /model updates config.yaml via yaml.dump, preserve any existing
model.api_key value (e.g. env var placeholder like ${ANTHROPIC_API_KEY}).
Never write resolved runtime API keys back into the config file.

Also handle the case where model config is a bare string (e.g.
"model: claude-opus-4.6") by converting to dict format while preserving
the original value as model.default.

Addresses review feedback from NousResearch#3360.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…witch

When /model updates config.yaml via yaml.dump, preserve any existing
model.api_key value (e.g. env var placeholder like ${ANTHROPIC_API_KEY}).
Never write resolved runtime API keys back into the config file.

Also handle the case where model config is a bare string (e.g.
"model: claude-opus-4.6") by converting to dict format while preserving
the original value as model.default.

Addresses review feedback from NousResearch#3360.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@virtaava
Copy link
Copy Markdown
Contributor Author

Good catch @NivOO5. Pushed a fix in 9c0f07b:

What changed:

  • Both config write paths in the gateway now preserve any existing model.api_key value before yaml.dump. Resolved runtime keys are never written back to config.yaml.
  • When the config has model as a bare string (e.g. model: claude-opus-4.6), it's now converted to dict format while preserving the value as model.default, instead of being wiped to {}.

The CLI side was already safe — it uses save_config_value() per-key, which doesn't touch unrelated keys.

Thanks for catching this before merge.

crxssrazr93 added a commit to crxssrazr93/hermes-agent that referenced this pull request Mar 29, 2026
Example plugin demonstrating the lifecycle hooks activated in NousResearch#3542.
Auto-manages a local llama-server (or any OpenAI-compatible server) when
the active model matches a locally configured model name.

Features:
- pre_llm_call hook: auto-starts the correct server on first message
  when hermes is configured with a local model name
- on_session_end hook: kills the server on exit
- switch_local_llm tool: mid-session model switching — the agent swaps
  the server when asked ("switch to the code model")
- Declarative YAML config for model definitions (GGUF paths, context
  sizes, KV cache quantization, sampling params) replacing shell scripts

The plugin is self-contained in docs/llm-switch-plugin-example/ with a
README, example config, and full implementation. Users copy it to
~/.hermes/plugins/llm-switch/ to install.

Complements NousResearch#3360 and NousResearch#3548 which restore /model as a slash command —
once merged, /model custom:write would trigger the pre_llm_call hook
to auto-start the right server seamlessly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@crxssrazr93
Copy link
Copy Markdown

+1 on restoring /model. Just opened #3672 — an example plugin (llm-switch) that auto-manages a local llama-server via the pre_llm_call hook from #3542.

The plugin works today via the switch_local_llm tool (agent-mediated switching), but with /model restored it becomes seamless: /model custom:write → next message → hook detects "write" in models.yaml → server auto-starts → LLM call proceeds. No manual server management needed.

This is a concrete use case where /model being missing blocks a natural UX. The ACP adapter already has in-session /model — CLI and gateway users should have parity.

@lugnicca
Copy link
Copy Markdown

Really need this, it's a pain to change model when running hermes on a vps without the /model command

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants