Connect Hermes
This page is for your own Hermes Agent install β not the Companion Hermes app from the Hub app store.
The Companion app does not fork Hermes. It builds on the official Hermes Agent image and layers a terminal UI and setup flow on top, so everything below works against a stock install.
Before you start
You need two API keys and the addresses of both servers.
<hub-url> and <memory-url> below are your two serversβ base URLs β everything before /api/mcp.
Neither has a port you can assume, so take both from the Hub dashboard if you are unsure:
Hub,
Memory.
Hermes can do this itself. Everything on this page β the MCP servers and
first-class memory β is also published as an agent-readable skill. Ask it to
set itself up from https://docs.ci.computer/connect/SKILL.md, or install
the skillΒ so it is there next
time. You still supply the two keys. See Or let your agent do
it.
Add the servers
Hermes has a command for this, and it is the better path: it tests each server before you commit to
it, and it keeps your key out of config.yaml. Editing the file by hand still works and is
documented below β the result is the same shape either way.
With hermes mcp add
hermes mcp add hub --url "<hub-url>/api/mcp" --auth header
hermes mcp add ci_server --url "<memory-url>/api/mcp" --auth headerThe name is the config key, so choose it deliberately: it is what hermes mcp test and
hermes mcp remove take afterwards, and it decides the environment variable name below. hub and
ci_server match the rest of this page.
Each run asks whether the server needs authentication, takes the key, then connects:
Does this server require authentication? [Y/n]: y
API key / Bearer token:
β Saved to ~/.hermes/.env as MCP_HUB_API_KEY
Connecting to 'hub'...
β Connected! Found 76 tool(s) from 'hub':
...
Enable all 76 tools? [Y/n/select]:Read the tool count before answering. That number is the check the
scope trap describes, arriving at the
one moment it is cheap to act on. A memory server reporting zero tools has a key without the
Intents scope.
Zero tools does not stop the save. Hermes warns β Server connected but reported no tools and then asks Save config anyway? [Y/n] β where the
capital Y is the default, so pressing Enter keeps it. Answer n on a memory
server showing zero, fix the keyβs scopes, and add it again.
What it writes
mcp_servers:
hub:
url: "<hub-url>/api/mcp"
headers:
Authorization: Bearer ${MCP_HUB_API_KEY}
enabled: trueYour key is not in there. It goes to $HERMES_HOME/.env β ~/.hermes/.env, or
%LOCALAPPDATA%\hermes\.env on native Windows β and config.yaml only references it. On macOS and
Linux that file is written at mode 0600; on Windows POSIX modes do not apply, and it inherits the
ACLs of your local app-data directory, which are user-private by default. The variable is MCP_ + the server name uppercased + _API_KEY, so hub gives
MCP_HUB_API_KEY and ci_server gives MCP_CI_SERVER_API_KEY.
That split is the main reason to prefer this over hand-editing. config.yaml is the file that gets
screenshotted, pasted into a support thread, and synced between machines; a bearer token written
inline goes everywhere it goes.
The key is written before the connection is tried, not after β the opposite
of hermes memory setup further down, which saves nothing
when its check fails. So a failed add still leaves a key behind: Hermes reports
β Failed to connect, offers Save config anyway (you can test later)? [y/N],
and on yes writes the entry with enabled: false plus hermes mcp test <name>
to retry. hermes mcp remove <name> takes the entry out of config.yaml but
leaves the variable in .env β delete that line yourself if you want the
key gone.
Raise the memory timeout by hand
One line the command cannot write, because there is no flag for it β --connect-timeout sets a
different thing. Add it to the memory server:
mcp_servers:
ci_server:
timeout: 600It is deliberate: recall over a large memory store can take longer than the default allows, and the
resulting failure looks like a broken server rather than a slow one. Hermesβ default is 300
seconds, so only a value above that actually buys you headroom β setting a smaller number makes the
timeout you were trying to avoid more likely. There is no need to set connect_timeout; its default
of 60 is already right for these servers.
Or edit config.yaml yourself
Hermes is configured through config.yaml, in $HERMES_HOME β ~/.hermes/config.yaml on a stock
macOS, Linux or WSL2 install, %LOCALAPPDATA%\hermes\config.yaml on native Windows. Both servers go
under mcp_servers:
mcp_servers:
hub:
url: "<hub-url>/api/mcp"
headers:
Authorization: "Bearer <hub-key>"
ci_server:
url: "<memory-url>/api/mcp"
headers:
Authorization: "Bearer <memory-key>"
timeout: 600This puts both keys in config.yaml in the clear. To keep the indirection the command gives you,
write Bearer ${MCP_HUB_API_KEY} here and put the key in ~/.hermes/.env yourself β the variable
name is yours to choose when you do it this way.
Do not add a transport: line
Hermesβ MCP client already defaults to Streamable HTTP for any entry that has a url, so the field
is unnecessary. Hermes does accept a transport key β but its only other value is sse, and setting
that on these servers points the client at a transport they do not serve.
Leave it out. This is the opposite of the OpenClaw page, where the field is
required. The two clients genuinely differ here. hermes mcp add never writes the field, which is
one more reason to prefer it.
Restart and verify
Test each server before restarting anything
hermes mcp test <name> connects from the command line, so you can check your work without ending
your session:
Testing 'ci_server'...
Transport: HTTP β https://memory.example.com/api/mcp
Authorization: Bear***f3a1
β Connected (92ms)
β Tools discovered: 3The key is masked in that output, and this is the only command on the page that prints an actual tool
count. A failure reads β Connection failed with the reason and the elapsed time.
Count, do not just look
A server that connected but advertises zero tools looks healthy everywhere else. hermes mcp list
shows it as β enabled alongside the working ones, because that column is status and its Tools
column is which tools you selected β neither is a count. If the memory server discovers nothing, the
key is missing the Intents scope β see
the scope trap.
Restart Hermes
config.yaml is read at startup, so end the current session and start a new one β quit the chat and
run hermes again. There is no reload command, and an open session will not pick this up.
Not hermes gateway restart. That subcommand manages the messaging
gateway β Telegram, Discord, WhatsApp β and has nothing to do with reloading
config.yaml. If you also run the backend server for the desktop app or a
remote client, restart that separately with hermes serve --stop and then
hermes serve. That works on every platform, Windows included β though the stop
is a hard kill there (taskkill /F) rather than the graceful signal it sends
elsewhere, so give an in-flight request a moment before restarting.
Confirm the tools arrived in the session
Ask the agent what tools it has, or inspect its startup log for the registered servers. mcp test
proved the server answers; this proves the running session picked it up.
Which side of WSL are you installing into?
Skip this unless you are on Windows. Hermes supports both a native Windows install and a WSL2 one, and they are separate installations with separate homes:
| Where Hermes runs | $HERMES_HOME | Reached from PowerShell as |
|---|---|---|
| Native Windows | %LOCALAPPDATA%\hermes | ${env:LOCALAPPDATA}\hermes |
| Inside WSL2 | ~/.hermes in the distro | \\wsl$\<distro>\home\<user>\.hermes |
Everything on this page β the plugin directory, config.yaml, .env β belongs to whichever one
runs your agent. A ~ typed in PowerShell resolves to C:\Users\<you>, which a WSL2 Hermes will
never scan; a ~ typed inside the distro is its own Linux home, which a native Hermes will never
scan. If you are not sure which you have, run hermes --version in each and see which answers.
First-class memory
Everything above gives your agent memory it can call. Hermes also has a native memory provider interface, separate from MCP, and that is what produces passive capture and injected context β conversations recorded without anyone calling a tool.
The provider is published as @companionintelligence/hermes-memory. Nothing about it requires
Node or npm: the npm registry is used purely as versioned tarball hosting, and Hermes discovers
providers by scanning a directory. You can do this instead of the MCP sections above if passive
memory is all you want.
The official way
Fetch the plugin
Hermes discovers memory providers by scanning $HERMES_HOME/plugins/ for a plugin directory β one
whose __init__.py implements a memory provider. There is no registry to register with β put the
files in place and they are found.
$HERMES_HOME is not ~/.hermes on native Windows. The Windows installer
sets it to %LOCALAPPDATA%\hermes; only a Linux, macOS or WSL2 install uses
~/.hermes. Extract into the wrong one and everything appears to work β the
files land, no error β and then hermes memory setup does not list
companionintelligence, because Hermes never scans there. Use the tab that
matches where Hermes runs, and see Which side of
WSL? if you run both.
macOS / Linux / WSL2
rm -rf ~/.hermes/plugins/companionintelligence
mkdir -p ~/.hermes/plugins/companionintelligence
curl -fL https://registry.npmjs.org/@companionintelligence/hermes-memory/-/hermes-memory-2026.8.3.tgz \
| tar -xz -C ~/.hermes/plugins/companionintelligence --strip-components=1Removing the directory first is deliberate. tar -x overwrites but never removes, so extracting over
an older install leaves behind modules a later version deleted β in a directory Python imports from.
Your credentials live outside this directory and are not affected.
Run the wizard
hermes memory setupPick companionintelligence from the list, then give it your server URL and API key.
This is a shell command, not a chat message. Typed at the Hermes chat
prompt (β―), Hermes answers it like a question β helpfully, plausibly, and
nothing gets configured. Run it in a terminal.
The key needs different scopes than an MCP key. This provider talks REST,
not MCP, so the key must carry the Memory scope (plus Intents for the
intent tools) at capability Read & write β the dialogβs default. A key
minted for the MCP section above (MCP + Intents) is refused on every
capture call, and the provider fails silently rather than break your session.
One key with Memory, Intents and MCP covers both setups. See Get Your
API Keys.
The wizard tests the connection against GET /api/health before saving and saves nothing if it
fails, so a wrong URL cannot leave you half-configured. The health endpoint does not
authenticate, though β the probe validates the address, not the key, so a mistyped or wrong-scoped
key saves cleanly and surfaces only as memory that never accumulates. On success the wizard writes
the key to .env (mode 0600 on macOS and Linux; on Windows it inherits your app-data ACLs), the
URL to companionintelligence/config.json, and sets memory.provider in config.yaml.
Verify
hermes memory statusExpect Provider: companionintelligence and Status: available β. Start a new session to activate.
Note what that proves: status confirms the configuration exists, and the wizardβs probe confirmed
the address β neither validates the key. The proof that capture is actually on: have a short
conversation, then look for the turn in the Companion Memory dashboard, or start a fresh session
and ask about something from the last one.
hermes plugins list will still show this as not enabled. That is not a
missed step. Hermes has two plugin subsystems and deliberately keeps memory
providers out of the general one β it coerces them to kind: exclusive and
routes them to its own discovery. Setting memory.provider, which the wizard
does, is the entire activation. Enabling it in the general registry activates
nothing and prompts you to grant a built-in tool override this plugin never
uses.
The URL default the wizard offers is http://127.0.0.1:8642, which is correct
only when Hermes runs on the Hub host itself. A bare hostname gets http://
prepended for you. The value is written once and never re-resolved, so
give the address that works from wherever this agent runs β see Find your
address first.
The cihub connect way
If you are working on the Hub host itself:
cihub connect hermes --memory-url "<memory-url>" --memory-key "<memory-key>"It probes the server first β the key as well as the address, unlike the wizardβs own check β then
fetches and unpacks the pinned version for you. Memory credentials still stay with the wizard, which
owns them, so you finish with hermes memory setup. Add --dry-run to see what it would do and
write nothing.
Adding --hub-url and --hub-key registers the Hub MCP server in config.yaml for you, as the
mcp_servers.hub block, so you can skip Add the servers for that one. Start a new
Hermes session afterwards β mcp_servers is read at startup.
Note that it writes the key inline rather than as the ${MCP_HUB_API_KEY} reference
hermes mcp add produces. If you would rather your token stayed out of config.yaml, use
hermes mcp add for the Hub server and let cihub connect handle only the plugin.
It configures the machine it runs on. If Hermes lives elsewhere, use the official way there.
When memory does not accumulate
The wizard did not list companionintelligence. The files are not where Hermes scans, and there
are two ways to land there. On native Windows, the usual one is the directory: $HERMES_HOME is
%LOCALAPPDATA%\hermes, not ~/.hermes, so an extraction into the latter succeeds and is never
seen β as is one done on the Windows side for a Hermes that lives in WSL2, or vice versa. Otherwise
check $HERMES_HOME/plugins/companionintelligence/__init__.py exists at all: --strip-components=1
is what puts the files at the top of that directory rather than one level down.
Run hermes memory status β or print the home Hermes resolves, using the tab for the shell you are
in:
macOS / Linux / WSL2
hermes --version && echo "[$HERMES_HOME]"Brackets so an unset value is visibly empty. An empty [] β from the form above that matches your
shell β means the platform default is in use: %LOCALAPPDATA%\hermes on native Windows, ~/.hermes
everywhere else.
The wizard printed Nothing was saved. The connection test failed, and the wizard refused to
save a configuration it had just proved does not work. The message names the cause: a connection
refused, a 401, or an unexpected status.
Setup succeeded, nothing accumulates. First: configuration is read at startup, so start a new
session. If a new session still captures nothing, suspect the key β wrong, missing the Memory
scope, or minted Read-only. The wizardβs check cannot catch any of those, and the provider fails
silently rather than break your session, so re-run hermes memory setup with a re-checked key.
Turning it off. hermes memory off clears memory.provider and returns you to built-in memory.
That alone is enough; the /plugins page does not gate this plugin.