Hermes Agent plugin · in the official plugin catalog
protean-model-sync gives one profile a fleet-wide voice. Set per-member model and reasoning in Bot Mode group chats, push one profile's model to every session of an agent, and apply named provider/model presets across profiles.
Three surfaces, one package: a CLI, one right-side desktop pane, and a read-only status command. Every write is diffed, backed up, and read back.
50d3d09
stdlib + PyYAML only · no pip
no network of its own · no telemetry
install does not activate either half
Install per profile. Writes are profile-native, so the habit is one install per profile. Pinning --ref is deliberate: an upgrade becomes an explicit choice of a new commit.
hermes -p <profile> plugins install ahrazzle/hermes-group-model-sync --ref <40-char-commit-sha> hermes -p <profile> plugins enable hermes-group-model-sync
install is not activation. The CLI half turns on through plugins enable. The desktop half turns on in Settings → Plugins, and it hot-reloads on save once enabled. Both halves are opt-in and off by default.
Needs: Hermes with plugin support (agent half: CLI plus dashboard routes; desktop half: Hermes Desktop). The Groups and Agents tabs also need Bot Mode group chats (the bundled hermes-bots plugin) and backend support for profiles.configure. Nothing else: the agent half runs on the stock Hermes Python.
Upgrade: hermes -p <profile> plugins update hermes-group-model-sync — refused while pinned, which is intended. The desktop half needs no second command: the runtime loader watches its folder and reloads it in place. Uninstall: hermes -p <profile> plugins remove hermes-group-model-sync, and turn the desktop half off in Settings → Plugins. Removing the install folder does not touch any profile's config.yaml or any backup. presets/presets.json lives inside the install folder, so an upgrade replaces it: keep your own preset values in your own copy of this repository.
Hermes Desktop switches a model per session, and the Dashboard switches one profile's default. Nothing shows what each agent in a group chat is running, nothing pushes one configuration to every session of an agent, and nothing applies a named provider/model preset across profiles. This plugin fills those three gaps.
hermes group-model-sync
Six verbs: preset, plan, apply, verify, rollback, doctor. Writes a named profile's config.yaml with a backup and a read-back, or writes nothing at all.
Model Sync pane
One right-side pane, three tabs. Groups shows every member agent's model and reasoning in a Bot Mode room, changeable per member. Agents pushes one profile's model and reasoning to every existing session of that agent. Presets applies a named preset to explicitly chosen profiles, with a diff preview first.
/gms status
An in-session command for the CLI and the gateway. Reports the current state and writes nothing, by design.
There is no model-facing tool. A tool that can rewrite config.yaml is a mis-invocation risk with no upside. Presets are applied by a human act: the CLI, or the pane.
Both halves read one file: presets/presets.json inside the installed package. That file is the only catalog v1 reads. The manifest declares no tools, no hooks, no middleware, and no settings (config_schema: {}).
Every verb is safe to explore: plan prints the diff and writes nothing. Add --json to any verb for a machine-readable receipt.
hermes group-model-sync preset list # ids + one-line descriptions hermes group-model-sync preset show fleet-default # every key that preset names hermes group-model-sync plan fleet-default --profiles a,b # read-only diff; writes nothing hermes group-model-sync apply fleet-default --profiles a,b --yes --verify hermes group-model-sync verify --profiles a,b # read-back only (config + state.db) hermes group-model-sync rollback --profile a # restore the newest backup hermes group-model-sync doctor # env, profiles, schema, version drift
Two modes without --yes: plain apply prints the plan and exits non-zero. --dry-run prints the plan and exits 0.
The key scope is locked at six. A preset can touch these and nothing else:
| Key | What it sets |
|---|---|
model.provider | provider route for the profile |
model.default | default model |
model.base_url | endpoint override |
model.api_mode | API mode |
agent.reasoning_effort | reasoning effort |
fallback_providers | fallback chain |
apply needs an explicit target and --yes. It refuses an unknown preset, a profile that does not exist, a profile with no config.yaml, and an unparseable config.yaml.config.yaml.bak-<UTC>-<preset id> first; every changed key is read back from the file afterwards. A write that does not read back as intended is a failure, not a warning. rollback restores a backup, takes its own safety copy first, and re-verifies.--confirm-expensive (CLI) or the confirm dialog (pane) before they will write.aux tier is reported as "declared, not applied". It is carried and schema-validated, but the target's auxiliary.* slots sit outside the locked scope.Verification reads reality: apply --verify reads the target's state.db for usage rows of the declared model. "No usage rows yet" is reported as UNVERIFIED, never as success. --require-verified makes that fatal.
Three presets ship with the package. Two are samples. One is a real fleet array.
| Preset | What it is |
|---|---|
fleet-default | Labelled EXAMPLE ONLY. Illustrative machine-specific values (an opencode-go route) so plan has something real to diff against on a first run. Not a fleet default and not a recommendation. |
minimal | Labelled EXAMPLE ONLY. Illustrative values (a direct-deepseek route). Same rule: replace every value with your own before applying. |
config-3 | A real guarded per-profile array: seven fleet profiles on three distinct routes, read 2026-09-17. It names no reasoning effort and no fallback chain, and it refuses every profile outside its seven. |
The two example presets may be stale or invalid on another install. Replace every value with your own before applying. config-3 is guarded because one route names a model the gateway may treat as expensive, so applying it needs --confirm-expensive. Replace it with your own array before relying on it.
A preset with per-profile assignments (a named fleet array: one route per profile) applies only to the profiles it names. A target it does not name is refused with the assigned names listed. Presets never guess a route.
Capability boundary: the manifest declares no tools, no hooks, no middleware, and no settings (config_schema: {}). It does not hook the session loop and writes nothing outside the target profile's own config.yaml and backups.
MIT licensed. One unified package: agent half at the root, desktop half in desktop/plugin.js. In the official Hermes plugin catalog.
hermes-group-model-sync/ ├── plugin.yaml # manifest: no tools, no hooks, no settings ├── __init__.py # register(ctx): CLI command + /gms command + bundled skill ├── plugin.py # shared core: load, validate, diff, apply, verify, rollback ├── presets/ │ ├── presets.json # the one source of preset truth (both halves read this) │ └── README.md # format, key scope, the absent-key rule ├── dashboard/ │ ├── manifest.json # publishes the backend router to dashboard/desktop │ ├── plugin_api.py # FastAPI router: presets, plan, apply, profiles, status │ └── config_core.py # the single host-config write path (atomic, profile-scoped) ├── skills/group-model-sync/ # agent-facing how-to for the verbs ├── desktop/plugin.js # desktop half: Groups, Agents, Presets tabs ├── test/ # pytest + node suites └── README.md
Built by Aska Digital