hermes - 💡(How to fix) Fix Per-channel personality and model routing (extending channel_prompts)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Extend the gateway's per-channel configuration so that a channel can be bound to a personality and model in addition to (or in place of) the existing per-channel system prompt. Any conversation initiated in that channel would default to the configured personality + model, with global defaults preserved everywhere else.

Root Cause

Extend the gateway's per-channel configuration so that a channel can be bound to a personality and model in addition to (or in place of) the existing per-channel system prompt. Any conversation initiated in that channel would default to the configured personality + model, with global defaults preserved everywhere else.

Fix Action

Fix / Workaround

I considered PR #4602 (feat/custom-slash-commands) as an alternate path — letting users write a /chef script that flips personality + model at conversation start. After reading the patch I ruled it out: custom commands are spawned via asyncio.create_subprocess_exec, so the script runs in a detached subprocess that can echo to the channel but cannot mutate the live gateway session's persona/model state. The originating Discord conversation is unchanged. Per-channel routing is therefore the structural fit, not a slash-command primitive.

Code Example

discord:
  channel_prompts:
    \"<channel-id-1>\":
      prompt: \"You are operating in the #food channel...\"
      personality: chef
      model: x-ai/grok-4.20
    \"<channel-id-2>\": \"plain string still accepted\"   # backward compat

---

discord:
  channel_prompts: { ... }                # unchanged
  channel_personalities:
    - id: \"<channel-id>\"
      personality: chef
      parent_inheritance: true             # mirrors channel_prompts parent fallback
  channel_models:
    - id: \"<channel-id>\"
      model: x-ai/grok-4.20
RAW_BUFFERClick to expand / collapse

Summary

Extend the gateway's per-channel configuration so that a channel can be bound to a personality and model in addition to (or in place of) the existing per-channel system prompt. Any conversation initiated in that channel would default to the configured personality + model, with global defaults preserved everywhere else.

Use case

Concrete workflow that motivates this:

  • A Discord #food channel where every conversation should default to a chef personality on x-ai/grok-4.20 (more capable / more expensive).
  • A #health-fitness channel where every conversation should default to a dietitian personality on anthropic/claude-sonnet-4.6.
  • Everywhere else, the global default (e.g. a cheap model like DeepSeek Flash) stays in effect.

The user-facing benefit: "this channel is for X work" maps cleanly onto "this channel uses the X persona+model." Today this requires manual /personality and /model switching at the start of every conversation, which is friction that defeats the channel-as-context model.

Current state

Hermes already has a per-channel mechanism: channel_prompts (per-channel ephemeral system prompts), resolved per-message in gateway/platforms/base.py::resolve_channel_prompt (~L1121) and loaded from platform config in gateway/config.py (~L773-778, also hermes_cli/config.py per-platform defaults around L1121-1168).

There is also a richer adjacent feature, channel_skill_bindings, which uses a list-of-objects schema (id + skills) — precedent that per-channel config can carry structured fields, not just a prompt string.

What's missing: there is no equivalent per-channel binding for personality or model. Both are global/session-level today (hermes_cli/commands.py::CommandDef(\"personality\", ...) at L120 and the model picker), so users who want different defaults in different channels must switch manually each time.

Proposed shape

Two reasonable shapes; happy to defer to maintainers on which is preferred:

Option A — Extend channel_prompts value type (smaller diff, but a breaking-ish schema change for that key):

discord:
  channel_prompts:
    \"<channel-id-1>\":
      prompt: \"You are operating in the #food channel...\"
      personality: chef
      model: x-ai/grok-4.20
    \"<channel-id-2>\": \"plain string still accepted\"   # backward compat

Option B — Parallel keys mirroring channel_skill_bindings (cleaner, more future-proof):

discord:
  channel_prompts: { ... }                # unchanged
  channel_personalities:
    - id: \"<channel-id>\"
      personality: chef
      parent_inheritance: true             # mirrors channel_prompts parent fallback
  channel_models:
    - id: \"<channel-id>\"
      model: x-ai/grok-4.20

In either case:

  • Fields are optional — channels with no override behave exactly as today.
  • Resolution mirrors resolve_channel_prompt's exact-then-parent fallback, so forum/thread inheritance works.
  • Validation rejects unknown personality names and unknown model identifiers at config-load time.
  • Application is per-message (look up before agent invocation, apply for that turn), so no global state mutation. Whether it's per-message or sticky-per-conversation is a design call I'd defer to maintainers — both satisfy the use case.

Scope clarifications

  • Gateway only (Discord, Telegram, Slack via the shared base). The interactive CLI has no channels and is unaffected.
  • No new dependencies.
  • Backward compatible: existing channel_prompts-only configs continue to work unchanged.

Why not custom slash commands (PR #4602)

I considered PR #4602 (feat/custom-slash-commands) as an alternate path — letting users write a /chef script that flips personality + model at conversation start. After reading the patch I ruled it out: custom commands are spawned via asyncio.create_subprocess_exec, so the script runs in a detached subprocess that can echo to the channel but cannot mutate the live gateway session's persona/model state. The originating Discord conversation is unchanged. Per-channel routing is therefore the structural fit, not a slash-command primitive.

Willingness to contribute

I'm planning to implement this on a fork (extending the existing channel_prompts resolution path, adding tests mirroring tests/gateway/test_discord_channel_prompts.py). If maintainers can weigh in on Option A vs B, I'll align my PR to the preferred shape before submitting. Happy to iterate on review feedback.

Thanks for considering.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING