01 / WHAT IT IS

Inside the gateway
you already run.

An app that is not running has no connection. iOS suspends it seconds after you put your phone down, and Android does the equivalent. So something that is always awake has to notice that a bot answered — and the gateway is always awake by definition.

The plugin lives in the hermes serve process. It listens to the gateway’s own hooks, reads the devices that registered themselves in the gateway’s profile metadata, and sends notifications from there. There is no relay, no account, no second process and nothing new listening on a port.

Runs in
Your own gateway, as a Hermes plugin
Licence
MIT
Adds
Push notifications, device context and a memory browser
02 / INSTALL IT

Two commands, on the gateway machine.

Run these where Hermes runs. The first clones the plugin into your Hermes home and switches it on; the second restarts the gateway so it loads.

On your gateway machine
hermes plugins install fullstackstudio-org/hermie-plugin --enable
hermes gateway restart
On some installs the hermes serve process is managed for you — a systemd unit, a container, a launch agent. There, restart the process that runs hermes serve in whatever way you normally would, instead of the second command.

What it needs

Hermes Agent 0.21 or newer

The plugin manifest is version 1. It is tested on 0.21.x.

Python cryptography

Only for Web Push, and the Hermes runtime ships it. Without it the plugin simply does not offer Web Push.

Outbound access to exp.host

The gateway sends through Expo’s push service. Nothing needs to reach your gateway.

Check what the gateway thinks it has with hermes plugins list and hermes plugins show hermie.

03 / TURN IT ON

Then, in Hermie.

Notifications are off until you turn them on, and nothing asks your device for permission before you do.

  1. Open Settings → Notifications

    Turn the switch on. Your device asks for permission, and Hermie registers itself on the gateway you are connected to.

  2. Choose what is worth a buzz

    New messages, questions that need you, turns that finished or failed, routines, and scheduled runs that ended either way — globally here, and per chat in the chat’s own menu. A preview switch decides whether a notification may carry the message text; it is off to begin with.

  3. Check the registration

    The Registration row says Registered with the tail of this device’s address. If something went wrong it says what, and offers a retry.

The plugin publishes what it can do into the gateway’s profile metadata, under its own key. Reading that advert in the app — offering notifications during setup when the plugin is there, naming the installed version and saying when an update is available — is planned; the advert is already written today.

That advert is a list of strings rather than a version to compare: the app asks for something only when the string naming it is there, which is how an older gateway and a newer app get along. The list has grown — memory.browse and memory.edit for the memory browser, context.live for a context edit that reaches a chat you already have open, push.type.cron_done and push.type.cron_failed for scheduled jobs that finished or failed, push.per_bot for a chat’s own notification switches, push.gateway_key for a payload that names the gateway it came from, and push.session_kind for one that says which conversation to open — beside a version for the installed build and a source naming the repository and the commit its tree sits at.

04 / WHAT YOU GET

News, and a sense of who is asking.

Notifications

Seven kinds, and a gateway decides which of them it will send at all before a device ever asks for one.

  • A bot wrote something — unless a device says somebody is reading that chat right now.
  • An agent is asking for approval, or asking you a question. Never held back.
  • A turn finished, and — separately — a turn failed. A turn you interrupted yourself is not worth a notification.
  • A routine delivered its result.
  • A scheduled run finished, and — separately again — a scheduled run failed. The bot whose nightly digest is chatter is exactly the bot whose digest not running is worth a buzz, so switching off the one no longer switches off the other.

Each of those is a switch in Settings, and each is a switch on a chat’s own notifications page too: a bot can stay quiet about its scheduled runs and still wake you when a turn fails. A chat’s answer is folded over your global one, so a type a chat says nothing about keeps following the global switch as that switch moves. Above both sits push.types in the gateway’s configuration — the ceiling a device cannot switch its way past — and a mute still silences the lot.

A payload also carries enough to land in the right place. Which gateway it came from, so a device set up against two of them switches first and then opens the chat rather than opening a bot with the same name on the wrong machine; and which conversation it was — a bot’s own chat, a branch, or one that /new put away — so a tap lands on the transcript the thing that buzzed actually happened in. Both are left out rather than guessed at when the gateway cannot read them, and a device that does not recognise a gateway key simply does not switch.

An approval notification carries Allow and Deny. Tapping one does not approve anything by itself: the app opens, connects to your gateway, re-reads the open requests, and answers only if that request is still open and still says what the notification said it did.

Two caveats worth knowing before you rely on them. A cron delivery is recognised from the scheduler’s own marker where the run carries one, and from the session it ran in where it does not — so a notification says whether it knows or is guessing, and a delivery it did not recognise arrives as an ordinary message, which it also is. And bot-to-bot messages are not notified at all: Hermes fires no hook when one arrives, so the plugin never claims that it can.

Device context

The app can keep a short profile beside the registrations: your name, your device model and its operating system, the app version, your timezone and locale, free text about yourself, and a note per bot.

The plugin puts that into the bot’s system prompt once per session, so a bot knows what you are reading on and answers accordingly, without the text appearing in the conversation or growing with it. It is bounded — per field and as a whole — and it ends by saying it is background you set in your app, not an instruction for this turn.

An edit reaches a chat that is already open. Hermes renders that section once per session and replays it afterwards, so a correction made halfway through would otherwise wait for a chat you have not started yet. Instead the plugin sees that the profile file moved and hands the bot the new text on the next turn, saying out loud that it replaces the copy the prompt froze — and saying in words that you cleared it, when you did, since the frozen copy cannot be taken back out. Nobody has to start a new session after fixing their timezone.

On a gateway with sign-in the plugin knows which person sent a turn and uses their context. On a gateway with several registered people and no way to tell them apart, it injects nothing: showing a bot the wrong person’s notes is worse than showing it none.

Memory

The app can open what a bot remembers. Hermes keeps two files for it — MEMORY.md, what the bot wrote down about its work, and USER.md, what it wrote down about you — and Hermie shows them as two lists, each with the characters it has spent against its limit. This is the one part of the plugin that answers HTTP, over the gateway’s own dashboard, because a memory store is more than a profile file should be asked to carry.

Search runs on the gateway, by the same rule as the bot’s own /memory command: every word of the query has to appear in an entry, in any order, as plain text. No ranking and no patterns, so nothing matches something you did not mean.

Entries can be added, edited and removed. Every write goes through Hermes’ own memory store — its file lock, its backup when the file moved underneath, its character limits — and what comes back is the store’s own answer rather than our account of it. An entry is addressed by its text and never by its position: a memory file has no ids, so a position is only a way of looking an entry up, and one that has gone stale is an error instead of a guess at whatever took its place.

There is a map as well: the bot in the middle, its entries around it, and a line between two entries that mention the same thing — a capitalised phrase, an @handle, a #hashtag, a date.

Both halves switch off per profile, in the gateway’s own config: plugins.entries.hermie.settings.memory.browse for reading and .memory.edit for writing. Editing without browsing is not a state — an app that cannot list an entry cannot name one to change. An external provider such as mem0 is named where one is configured and never opened: that interface hands a bot text for a single turn and offers no call that lists what it holds, so there is nothing to show without inventing an answer Hermes does not have.

05 / WHERE IT LIVES

All of it stays on your gateway.

Registrations and context are stored in your gateway’s own profile metadata, the same place the app keeps its other settings. Nothing is written anywhere else, and there is no service of ours in the path.

That metadata belongs to the profile, not to a person: everyone who can reach that gateway can read it. A push token is only an address, but a context section is a name, a device and whatever you wrote about yourself. The memory routes are gated by the dashboard’s own sign-in and nothing finer — every signed-in caller counts as an operator of the machine, exactly as on Hermes’ own /api/memory/reset — so on a gateway you share, everyone who can sign in can read and rewrite every profile’s memory. Fill it in accordingly.

A notification says which bot and what kind of event, and nothing about what was said — it is rendered by Apple, Google or a browser vendor on a lock screen. Turning previews on is a decision you make per device, and the gateway can forbid it outright.

The name a payload gives your gateway is derived from the address the device registered against, so the two sides agree without comparing notes. A device that registered before the app wrote that key itself falls back to push.public_url in the plugin’s own settings, and then to Hermes’ dashboard.public_url — which is worth setting only when the gateway is reached somewhere other than the address it thinks it is served on.

Your push token goes to your gateway and to Expo’s push service, which relays to Apple and Google; a browser subscription goes to that browser’s own push service, signed with a key the plugin mints on your gateway. That is the whole list.

Read about app privacy
06 / KEEPING IT CURRENT

Updates, and what to do when it is quiet.

Update the installed plugin from the gateway machine, then restart the process that runs hermes serve.

On your gateway machine
hermes plugins update hermie

Your registrations are not the plugin’s own data — they live with the app’s settings on the gateway and survive an update, a downgrade, even removing the plugin altogether.

The Update button in the Hermes dashboard can fail on a gateway running several profiles at once, with an internal GitHub token scoping error. Updating from the command line is unaffected.

Nothing arrives

Is the plugin actually on?

Run hermes plugins list on the gateway. If hermie is missing or disabled, the install or the --enable did not take. hermes plugins show hermie prints what it is loading.

Is this device registered?

Settings → Notifications shows a Registration row whenever notifications are on. It should say Registered. Anything else names the problem — permission refused in your device settings, a build without a push project, or a refused token — and the ones worth retrying get a button.

Can the gateway reach the outside?

The gateway sends to exp.host and, for browsers, to the browser vendor’s push service. A gateway with no outbound access can receive nothing to notify about either, but a proxy that allows only your own traffic will look exactly like silence.

Is the gateway running at all?

A gateway that is off sends nothing, and nothing on your phone will say so. Notifications are best effort, and Hermie never treats their absence as news.

Do I need the plugin to use Hermie?

No. Hermie connects to your gateway and works without it. The plugin adds notifications and device context, nothing else.

What if I cannot install a plugin?

Hermie Web can also send push notifications where a plugin is not an option — see the Hermie Web documentation — but run one or the other, never both, or every device is notified twice.