All plugins

herd

Keep one Hermes across every machine. Settings, skills, memory and SOUL.md follow the user between devices, while each machine keeps what belongs to it — Docker paths, ports, bot tokens. `config.yaml` is merged per key against a stored base, with machine-bound paths resolved locally before any comparison, so a pull cannot delete the section the machine is running on. API keys never sync: herd does not open `.env`. The store is encrypted with a key stretched from a recovery code before anything leaves the device; every pull leaves a restorable backup and `hermes sync undo` puts it back, removing files the pull added. Ships a per-device manifest store over any folder or git remote, three agent tools (`sync_status`, `sync_diff`, `sync_now`), session hooks that pull at start and push at end in interactive sessions, and a `hermes sync` CLI. Pairing is one command that prints a 20-character code, valid once for 15 minutes, written as a sealed `pairing.json` in the store folder. A machine that can see that folder reads and deletes it. The file is not pushed to a git remote, because it wraps the recovery code and would remain in history after deletion; a machine that only has the git remote joins with `hermes sync join <git-url> --code HERD-…`. Disclosure — reads the profile's config, memory, SOUL.md and skills and writes the ones that changed; credential-named keys in config.yaml and `mcp_servers.*.env`/`headers` stay on each machine; never reads `.env`, `auth.json` or `state.db`; in interactive sessions the session hooks pull and apply other machines' settings, skills, memory and SOUL.md at session start and push at session end without a prompt (turn off with `auto_pull`/`auto_push`); `sync_now` asks for approval and is blocked under cron and other non-interactive sessions, where the hooks also skip; cron jobs sync only on machines paired with `--cron`; the store is any folder or git remote the user names, and nothing is sent anywhere else.

wellivToolsv1.2.1Updated 30 minutes ago

About

Keep one Hermes across every machine. Settings, skills, memory and SOUL.md follow the user between devices, while each machine keeps what belongs to it — Docker paths, ports, bot tokens. `config.yaml` is merged per key against a stored base, with machine-bound paths resolved locally before any comparison, so a pull cannot delete the section the machine is running on. API keys never sync: herd does not open `.env`. The store is encrypted with a key stretched from a recovery code before anything leaves the device; every pull leaves a restorable backup and `hermes sync undo` puts it back, removing files the pull added. Ships a per-device manifest store over any folder or git remote, three agent tools (`sync_status`, `sync_diff`, `sync_now`), session hooks that pull at start and push at end in interactive sessions, and a `hermes sync` CLI. Pairing is one command that prints a 20-character code, valid once for 15 minutes, written as a sealed `pairing.json` in the store folder. A machine that can see that folder reads and deletes it. The file is not pushed to a git remote, because it wraps the recovery code and would remain in history after deletion; a machine that only has the git remote joins with `hermes sync join <git-url> --code HERD-…`. Disclosure — reads the profile's config, memory, SOUL.md and skills and writes the ones that changed; credential-named keys in config.yaml and `mcp_servers.*.env`/`headers` stay on each machine; never reads `.env`, `auth.json` or `state.db`; in interactive sessions the session hooks pull and apply other machines' settings, skills, memory and SOUL.md at session start and push at session end without a prompt (turn off with `auto_pull`/`auto_push`); `sync_now` asks for approval and is blocked under cron and other non-interactive sessions, where the hooks also skip; cron jobs sync only on machines paired with `--cron`; the store is any folder or git remote the user names, and nothing is sent anywhere else.

Herd

Two machines. One Hermes.

Settings, skills and memory follow you between machines; each machine keeps the parts that only make sense on it. One command to start, one code to join, nothing to manage afterwards.

Copying ~/.hermes moves your Docker socket and your bot token with it. Herd moves the profile, not the machine.

Herd: two machines, one Hermes

85 seconds, with narration: why a second machine drifts, why copying ~/.hermes breaks it, and herd 1.2 doing the fix for real — pairing with a one-time code, a skill and a memory note arriving on the laptop, each machine keeping its own folder and port, and undo. API keys never leave the machine.

The short version

| | |
|---|---|
| What it is | A plugin that keeps one Hermes across your machines — the same model, skills and memory everywhere, each machine keeping its own paths, ports and bot tokens |
| What it fixes | A second machine is a stranger with the same name; copying the folder to fix that moves the first machine's Docker socket and bot token with it |
| How it works | One encrypted store, one short pairing code, a per-key merge that knows what belongs to the machine, and a pull at session start / push at session end |
| What it costs | You keep the recovery code in a password manager (the pairing code is only for the other machine). Sessions and cron stay put. A real conflict is shown, then one key settles it |

New here? PROBLEM.md is the case for it, COMPARISON.md ranks it against every other way to do this, and DEMO.md is the 85-second film, scene by scene.

In the wild

Laptop and a VPS. You work from the laptop; the VPS runs the always-on agent. Install on both, hermes sync pair on the VPS, paste the code on the laptop. The laptop answers with the same memory and the same skills, while the VPS keeps its daemon socket and the laptop keeps its own working directory. Edit a skill on the train; it is on the VPS at the next session start.

Desktop plus a travel laptop. The desktop has the big model and the local models; the travel laptop is a thin client. Settings follow, so it is the same agent — but the travel laptop does not inherit a Docker backend it cannot run, and its bot token does not start answering for the desktop.

A replacement machine. pair with the code and the profile comes back in one command: memory, skills, SOUL.md, config, and the keys if you turned them on. The values you set again are the ones that belong to the new machine anyway.

The common shape: you stop choosing which machine to work on. Every machine is the agent; only the plumbing differs.

And the first ninety seconds are one command that prints one short code, one paste, and a line that tells you it worked:

$ hermes sync pair
Ready to pair — desktop is the first machine.
  code   QK7M2-9XPRT-4HWBJ-2M8ZC   (once, 15 minutes)

$ hermes sync pair --code QK7M2-9XPRT-4HWBJ-2M8ZC
Paired with desktop. This machine is laptop.
  In sync · 2 machines · synced just now

When you don't need this

If every machine is a client of one central Hermes — a box you reach over Tailscale or SSH — you already have one agent; the clients only render it. Herd is for two or more independent installs that have to agree.

And if you run each machine as its own profile but you are tired of explaining the same context three times, pair in knowledge mode: skills and memory travel, settings stay put.

hermes sync pair --knowledge          # first machine
hermes sync pair --code … --knowledge # the others
hermes sync scope profile             # back to the whole profile, any time

The problem, in numbers

  • 24% of a real config is machine-bound — 37 of 155 leaf keys across 12 sections. Copy them and the laptop runs commands in the desktop's Docker socket, binds its port, and answers its Telegram messages.
  • Secrets are all or nothing in a file sync. Skip .env and you retype every key; include it and it lands in a cloud folder or a git history. A published sync tool did exactly that once: "it read the .env file and posted the contents to slack."
  • File sync keeps the last write. Two machines editing the same setting is not an edge case, it is Tuesday. One edit disappears and nothing says so.

The measured detail, including the four prior attempts that all move files: PROBLEM.md.

What it solves

| Problem | Without Herd | With Herd |
|---|---|---|
| Second machine is empty | Retype keys, reinstall skills, re-teach memory | join with the code, once |
| Machine-bound settings | Copied and wrong, or excluded and missing | Resolved to each machine's own value |
| Secrets | Plaintext in a cloud folder, or never synced | Left on each machine. Herd does not read or write .env |
| Simultaneous edits | Last write wins, silently | A card in the terminal: 1 keep mine, 2 take theirs — the disagreement is recorded until you choose |
| A mistake | Restore from a backup you may not have | hermes sync undo |

How it works

        desktop                          laptop
   ┌──────────────┐                ┌──────────────┐
   │ config.yaml  │                │ config.yaml  │
   │ skills/      │                │ skills/      │
   │ memories/    │                │ memories/    │
   │ SOUL.md      │                │ SOUL.md      │
   └──────┬───────┘                └──────┬───────┘
          │   merge per key, base = last agreed   │
          └────────────────┬──────────────────────┘
                           ▼
                 ┌───────────────────┐
                 │  encrypted store  │  folder, git, Dropbox, USB
                 │  ciphertext only  │  one recovery code opens it
                 └───────────────────┘
  1. init creates the store, seals this machine's items, and prints one recovery code (HERD-…).
  2. join on the next machine adopts the herd on its first pull.
  3. From then on a session-start hook pulls and a session-end hook pushes: a background thread, at most once every two minutes, a couple of milliseconds of hook.

Each device writes exactly one manifest, so two machines pushing at once cannot clobber each other. Items are content-addressed and encrypted, and every change records the entry it was built on — that is how a fast-forward is told from a real disagreement.

config.yaml merges per key against the last agreed snapshot:

local == remote   keep it
local == base     take remote      only they changed it
remote == base    keep local       only you changed it
all three differ  keep local, record both, stop republishing

Machine-bound paths are resolved to the local value before that comparison — including when the remote payload omits them — so a pull cannot delete the section your machine is running on.

How it improves Hermes

  • The surfaces stop competing. CLI, TUI, desktop, webapp, 21+ messaging platforms: without shared state each one boots a different agent, and a new surface loses to the first one. With it, they are the same agent in different windows.
  • The catalog becomes portable. Skills, automations and connectors are per-host today; make state portable once and every plugin that stores state inherits it.
  • The agent can answer for itself. Three tools — sync_status, sync_diff, sync_now — so "am I in sync?" is a question Hermes answers, not a command you have to remember.
  • No daemon, no port, no server. The store is a folder. Session start and session end each start one short-lived background thread (a pull or a push, at most once every two minutes) and that thread exits. Cron and other non-interactive sessions do not start it.
  • It is reversible. Every pull leaves a backup, and hermes sync undo puts the files back.

How it compares

| Tool | Machine-bound keys | Secrets | Conflicts | Score |
|---|---|---|---|---|
| Herd | Modelled per key | Not synced | Kept, then one key to settle | 77 |
| Dotfiles + age/SOPS | Hand-written templates | Encrypted in git | Git conflict, by hand | 59 |
| Syncthing / Dropbox | Copied over each other | Provider-side, plaintext | Conflict copies, .db corruption | 51 |
| Shared home over SSH | One config for all hosts | Stays on the server | None to resolve | 51 |
| hermes profile export/import | Whole archive | Whole archive | None — one-way | 46 |
| Copy ~/.hermes | Copied, breaks the host | Leaks or is skipped | Last write wins | 45 |

Full method, weights and per-tool verdicts — including when not to use Herd: COMPARISON.md.

Install

# both machines
hermes plugins install welliv/hermes-herd --enable

# first machine: creates the herd and prints one short code
hermes sync pair

# every other machine: type the code
hermes sync pair --code XXXX-XXXX-XXXX-XXXX
Ready to pair — desktop is the first machine.
  store  /root/herd-store

  code   QK7M2-9XPRT-4HWBJ-2M8ZC
         works once, for 15 minutes.

On the other machine, with this store folder reachable:
  hermes sync pair --code QK7M2-9XPRT-4HWBJ-2M8ZC
Paired with desktop. This machine is laptop.
  pulled  6 item(s): config, memory:agent, file:soul, skill:research, skill:writer, skill:deploy
  In sync · 2 machines · synced just now

The store is a folder: put it in Dropbox/iCloud/Syncthing so both machines see it, then the second machine types the short code. One code works once, for 15 minutes, and is deleted after use. If you pass --remote <git-url> instead, the short code is not pushed: pairing.json wraps the recovery code, and a git commit would leave that ciphertext in history after the file is deleted. The second machine joins with the long recovery code: hermes sync join <git-url> --code HERD-… (hermes sync code prints it).

Hermes 0.23+.

What moves

| | |
|---|---|
| Follows you | model and provider, display, agent behaviour, skills, memory (MEMORY.md, USER.md), SOUL.md |
| Stays put | terminal.backend, terminal.cwd, Docker and container settings, database.*, webapp.*, browser.*, auth.*, updates.*, bot config, and machine env vars (PATH, DISPLAY, HERMES_DASHBOARD_*, TELEGRAM_BOT_TOKEN) |
| Never | state.db and sessions — SQLite with a write-ahead log is not a file to sync. Cron is opt-in, because a job on three machines runs three times |

One paragraph can stay local. hermes sync note "this box is the VPS, and the risky one" writes a block into MEMORY.md that is stripped before anything leaves the machine and re-applied after every pull. Shared memory, per-machine self-awareness.

Scope decides what kind of thing follows you: profile (the default) sends settings, skills and memory; knowledge sends only skills and memory, so three machines can keep three different setups and still share everything you have taught them. The rules are data, not code: hermes sync policy prints them, and each is one line in plugin-data/herd/policy.yaml.

Security

The trust boundary is the other machine. Read SECURITY.md before pairing.

  • A paired machine can change what the agent follows. A pull writes skills/, memories/MEMORY.md, memories/USER.md and SOUL.md. Hermes loads those as instructions, not as quoted notes. sync_diff lists them under review_before_pull. sync_now asks Hermes for approval before it pulls or pushes. Cron, hermes -q, and any other non-interactive session skip the pull instead of prompting. config.yaml follows too: a paired machine can add an MCP server (a command Hermes starts here) or change the provider used with this machine's .env keys, so treat it as able to run code here.
  • Joining replaces this machine's memory with the herd's. MEMORY.md and USER.md on the joining machine are replaced. The previous text is saved at plugin-data/herd/pre-join-MEMORY.md (and pre-join-USER.md when that file differed), and the join output says so. It is not merged into the herd, because publishing the merge would make every other machine adopt this machine's notes. Copy back anything you still want.
  • API keys are not synced. Credential-named keys in config.yaml (api_key, API_KEY, apiKey, x-api-key, token, secret, password, authorization, and *_api_key, *_token, … at any depth, lists included) and mcp_servers.*.env / mcp_servers.*.headers never leave the machine. A peer's values are ignored on pull, and a peer cannot overwrite or delete the local ones. Herd does not read or write ~/.hermes/.env, does not call load_env_file, and does not export provider keys through get_secret. Hermes has no API that can move those keys without opening the file, and a listed plugin may not open it. hermes sync secrets on refuses. --sync-secrets is ignored. An env item left by an older version stays ciphertext and is not applied.
  • A peer cannot point your key somewhere else. In a section that holds a credential on this machine (model, a custom_providers entry, an MCP server with env or headers, a section whose extra_headers carry one), the keys that decide where it is sent — base_url, url, endpoint, api_base, host, provider, command, args — stay as they are here when a peer changes them. The rest of the peer's change is taken, and the held part is listed by hermes sync conflicts with the reason credential-endpoint until you keep yours or take theirs (--item config --take theirs applies every recorded config path, not only the first) A section with no local credential syncs these keys normally.
  • Secrets written into skills, memory or SOUL.md do travel. Herd filters config.yaml by key name; it does not scan skills/, MEMORY.md, USER.md or SOUL.md. Text in those files syncs as written, so keep keys out of them.
  • Encrypted before it leaves. One 32-byte key, stretched with scrypt, used for AES-256-GCM. The store holds ciphertext of config, skills, memory and SOUL.md.
  • Names are keyed hashes. Objects are addressed by HMAC(key, plaintext), so the store can't be probed by hashing guesses at a known setting.
  • One recovery code. Printed by init, reprintable with hermes sync code. It decrypts the store and cannot be reset — keep it in a password manager.
  • Every payload names itself. An object carries the item id and kind it was sealed for, so a manifest cannot point one item at another's blob.
  • A disagreement stops, it does not ping-pong. Each side keeps its version, records the disagreement, and stops republishing the item until one side agrees. A pull will not replace a newer local SOUL.md (or any other file item) with the older copy still in the store.
  • Git, when you ask for it. A folder store makes no network calls. A git store runs git fetch for status and diff (no rebase, no push), git fetch and git rebase for a pull, and git push only for a push. The remote is the one you configured. Every git subprocess, and the ssh or credential helper it starts, gets a minimal environment (PATH, HOME, locale, TMPDIR, SYSTEMROOT on Windows, SSH_AUTH_SOCK, GIT_SSH_COMMAND and a few more), never the agent's provider keys.
  • Cron is opt-in on each machine. cron/jobs.json syncs only between machines that each passed --cron (init, join, pair, or pair --code). Everywhere else a peer's cron jobs are skipped, not installed, and the pull says so once per change to the peer's jobs.

A store written by 1.0.2 or earlier holds unsealed objects. 1.0.3 skips them with a message rather than guessing; start a fresh store, or push once from each machine still running the old version. An env blob written by 1.1 is ignored.

Commands

| | |
|---|---|
| hermes sync pair [store] [--remote URL] [--cron] | Create the herd and print one short, single-use code. The store is a positional path (init uses --store). --cron also syncs cron jobs on this machine |
| hermes sync | Pull, merge, push — the one you type |
| hermes sync conflicts | Review disagreements; --item config --take mine\|theirs settles every recorded path on that item |
| hermes sync pair --code … [store] [--cron] | Join with that code when the store folder is reachable here. --cron takes and syncs cron jobs here |
| hermes sync init / join | The explicit path: choose a store or git URL, use the long recovery code |
| hermes sync status / diff | What's in sync; what a sync would do |
| hermes sync scope knowledge\|profile | Share skills and memory only, or the whole profile |
| hermes sync note [text] | This machine's own paragraph in memory — never synced |
| hermes sync secrets on / off | Refuses. API keys are not synced, and .env is not opened |
| hermes sync gc [--dry-run] | Delete store objects nothing references any more |
| hermes sync undo | Restore the files from the last pull |
| hermes sync policy / code / leave | The rules; the recovery code; stop syncing |

The agent gets three tools with the same verbs: sync_status, sync_diff, sync_now. Status and diff are read-only. sync_now goes through Hermes's approval prompt, and it does not run in cron or other non-interactive sessions.

Limits

  • Sessions don't sync, and cron is off by default. Deliberate, see the table above.
  • Losing the recovery code loses the store. That is the cost of having no custodian.
  • A genuine conflict is shown, not guessed — and settled with one key in the terminal. There is no GUI yet.
  • The store keeps every version it ever wrote; hermes sync gc prunes what nothing references, and old backups and conflict archives with it.
  • Transport today is a folder or git. Nostr is the next backend: same interface, keypair identity, no vendor.

What it is not

No account. No hosted service. No telemetry. It does not back up your sessions, and it will not guess which side of a real disagreement you meant.

Docs

| | |
|---|---|
| docs/PROBLEM.md | The measured case for per-key sync, and why Hermes needs it |
| docs/COMPARISON.md | Six ways to do this, weighted and ranked, with when to choose each |
| docs/DEMO.md | The 85-second explainer: storyboard, narration, how the terminal shots were made |
| docs/AUDIT.md | Every claim above, mapped to the test that proves it |

MIT.

README from the reviewed commit 7895e85; it updates when the author re-pins.

More by welliv

2 plugins by welliv in the catalog.

Developer Mode

Explore built-in Hermes toolsets and actions.

HermesAgent

Developer?

Create a Hermes extension for Desktop or agent workflows.

Developer Section