Running Hermes Agent Across Multiple Devices with Forgejo and Matrix

Table of Contents ๐
- Changelog
- 1. Architecture
- 2. What to Share โ and What Must Stay Local
- 3. Prerequisites
- 4. Create the Shared Forgejo Repository
- 5. Build Workstation and Server Distributions
- 6. Install the Profiles on Each Device
- 7. Share Knowledge with QMD or LLM Wiki
- 8. Project Handoffs and Session Exports
- 9. Safe Git Synchronization and Offline Work
- 10. Sharing Reviewed Skills
- 11. Build a Matrix Fleet: One Profile, One Bot, One Gateway
- 12. Security Hardening
- 13. Backups and Recovery
- 14. Verification Checklist
- 15. Updates and Maintenance
- Conclusion
Hermes Agent by Nous Research is a self-hosted AI agent that runs shell, file, and development tools directly on the machine it lives on. That single fact decides the whole multi-device design: the agent acts where its files and tools are, so the useful question is not โcentral or local?โ but โwhich filesystem must the agent touch directly?โ
The answer is almost never one replicated Hermes spread across every device. It is several specialized instances โ workstation profiles for local files, optional customer/project profiles, and server profiles for work that must stay reachable around the clock. Every profile exposed through Matrix gets its own bot identity and gateway process. The instances share only what is genuinely device-independent: personality, a curated user profile, selected Markdown knowledge, deliberate handoffs, and reviewed skills. That shared layer lives in a single Forgejo repository. The local SQLite session database, secrets, and Matrix encryption state never leave the endpoint they belong to.
This guide turns that architecture into a working setup with real, copyable commands.
Changelog
| Date | Change |
|---|---|
| 2026-07-17 | Security Update: Corrected backup scope and clarified full-trust operation, approvals, Matrix recovery, and off-host protection. |
| 2026-07-16 | Fleet Update: Added the official one-profile/one-bot/one-gateway operating model, machine-aware Matrix naming, multi-gateway service rules, CLI/Matrix session boundaries, Kanban ownership, and the profile-versus-sandbox distinction. |
| 2026-07-16 | Initial Version: Multi-instance Hermes design with a shared Forgejo repo, local profile distributions, QMD knowledge, handoffs, safe Git sync, Matrix hardening, and backups. |
1. Architecture
Run one Hermes instance per device role. Each keeps its own state and tools; they meet only in the shared repository.
Forgejo
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ hermes-shared โ
โ โ
โ SOUL.md โ
โ USER.shared.md โ
โ knowledge/ โ
โ handoffs/ โ
โ skills/shared/ โ
โ distributions/ โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโ
โ git pull / push
โโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโ
โ โ โ
Desktop-Hermes Laptop-Hermes Server-Hermes
local backend local backend local backend
local state.db local state.db local state.db
local memory local memory local memory
local skills local skills server tools
profile gateways profile gateways 24/7 gateways
โ โ โ
โโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโ
separate Matrix bot identitiesThe instances are not broken copies of one agent. They are specialists that happen to share the same picture of you:
- Desktop / Laptop (workstation role): local Git repositories, dotfiles, development tools, local documents, plus a Matrix gateway for every local profile you actually want to reach remotely. The laptop is another independent endpoint, not a replica of the desktop database.
- Server (server role): Docker/Podman stacks, reverse proxy, databases, backups, cron automation, and deliberately scoped 24/7 Matrix profiles.
This separation is a feature, not a limitation. Server debugging stays with the server agent, and local ricing stays with the workstation agent, so unrelated topics never pollute each otherโs local session search.
2. What to Share โ and What Must Stay Local
Getting this boundary right is the entire point of the design.
Share through Forgejo:
- a common base personality (
SOUL.md), - a deliberately curated user profile,
- Markdown knowledge under
knowledge/, - handoff files for ongoing projects,
- skills you have explicitly reviewed and marked shared,
- optionally, an official Hermes profile distribution per role.
Keep strictly local (never in Git, never live-synced):
state.db,state.db-wal,state.db-shm,- full session history,
.env,auth.json, and API keys,- Matrix credentials and the E2EE crypto store,
- device-specific configuration and locally generated skills,
- logs, caches, gateway state, and search indexes.
| โ ๏ธ NEVER REPLICATE SQLITE STATE |
Hermes stores sessions in a local SQLite database with FTS5 search. WAL mode supports concurrent access on one machine, but it is not a conflict-free multi-master database. Distributing |
3. Prerequisites
You need:
- A Forgejo instance (or any Git host) reachable from every device, e.g.
git.example.com. - Hermes Agent installed on each device โ pin the same version everywhere to avoid config-schema drift.
- Basic Git familiarity; if you want a refresher, see the Git tutorial.
- For Matrix access: a running Matrix Synapse homeserver and one dedicated bot account per exposed profile instance.
- For local hybrid search (optional): Node.js
โฅ 22.
This guide uses neutral placeholders throughout. Replace them with your own values:
Repository https://git.example.com/user/hermes-shared.git
Local clone ~/hermes-shared
Matrix user @you:matrix.example.org
Matrix room !roomid:matrix.example.org4. Create the Shared Forgejo Repository
Create one private repository named hermes-shared and give it this layout:
hermes-shared/
โโโ README.md
โโโ SOUL.md
โโโ USER.shared.md
โโโ knowledge/
โ โโโ infrastructure/
โ โโโ devices/
โ โโโ workflows/
โ โโโ conventions/
โโโ projects/
โโโ handoffs/
โ โโโ current/
โ โโโ archive/
โ โโโ exports/ # redacted session exports only
โโโ skills/
โ โโโ shared/
โโโ distributions/
โโโ workstation/
โโโ server/Add a .gitignore that keeps runtime state and secrets out of the repository:
# Secrets
.env
auth.json
*.key
*.pem
# Hermes runtime state (never belongs in the shared repo)
state.db
state.db-wal
state.db-shm
memories/
sessions/
logs/
cache/
pending/
# Matrix crypto state
platforms/matrix/store/
matrix/store/
# Temporary
*.tmp
*.swp
.DS_StoreThere is no need to ignore the QMD index: it lives under ~/.cache/qmd/index.sqlite, outside the repository (see section 7).
Clone the repository on each device:
# HTTPS
git clone https://git.example.com/user/hermes-shared.git ~/hermes-shared
# or SSH (note the colon before the path)
# git clone git@git.example.com:user/hermes-shared.git ~/hermes-shared5. Build Workstation and Server Distributions
Hermes ships an official profile distribution mechanism. A distribution packages a reusable agent base โ SOUL.md, a config.yaml without secrets, skills, cron entries, and MCP connections โ behind a distribution.yaml manifest. It deliberately never carries memories, sessions, auth.json, or .env.
Keep one distribution per role, each in its own subdirectory with distribution.yaml in that subdirectoryโs root:
hermes-shared/distributions/workstation/
โโโ distribution.yaml
โโโ SOUL.md
โโโ config.yaml
โโโ skills/
โโโ cron/
โโโ mcp.jsonEach manifest names the profile it creates. A minimal distributions/workstation/distribution.yaml:
name: workstation
description: Shared workstation base for desktop and laptopUse name: server in distributions/server/distribution.yaml. This name: field is what the install command turns into a profile (section 6).
Give the workstation config.yaml sensible, review-first defaults:
memory:
memory_enabled: true
user_profile_enabled: true
memory_char_limit: 2200
user_char_limit: 1375
write_approval: true
skills:
write_approval: trueWith write_approval: true, memory and skill changes are staged for your confirmation before they persist. On the server you may relax this for autonomous background tasks, but true is the safer starting point.
| ๐ DISTRIBUTIONS ARE NOT A SANDBOX |
Profiles isolate config, A full-trust personal assistant may deliberately share your user account and credentials; in that model, the missing OS boundary is a conscious capability decision rather than a configuration mistake. A container is a hard boundary only when it has no Docker socket, no privileged mode, and no broad host mounts. Mounting |
6. Install the Profiles on Each Device
A Git-URL install expects distribution.yaml at the repository root, so you cannot point an install at a subdirectory of a Forgejo URL. Because every device already clones hermes-shared anyway, install from the local path of the subdirectory instead โ the folder you point at simply needs distribution.yaml in its root, which the layout above provides.
The profile name comes from the manifestโs name: field. --alias is a boolean switch that also creates a shell wrapper (workstation โ hermes -p workstation); pass --name <name> only if you want to override the manifest name.
On a workstation:
git -C ~/hermes-shared pull --rebase
hermes profile install ~/hermes-shared/distributions/workstation/ --aliasOn the server:
git -C ~/hermes-shared pull --rebase
hermes profile install ~/hermes-shared/distributions/server/ --aliasUpdate a profile later by pulling and running:
hermes profile update workstation| โ ๏ธ CONFIG.YAML IS PRESERVED BY DEFAULT |
Memories, sessions, and credentials are never touched by an update, with or without that flag. |
Handling USER.shared.md
USER.shared.md lives in the repository, but Hermes does not load it automatically โ distributions carry no memories, and memories/USER.md is git-ignored. Treat the file as storage, not an active profile, and choose one of two clean paths:
Truly stable, shared rules (tone, ground rules, fixed conventions) belong in
SOUL.md. That is distributed and is the correct home for device-independent personality.A device-tunable profile: copy
USER.shared.mdonce into the named profileโs memory file, then maintain it locally. A named profile is a separate Hermes home under~/.hermes/profiles/<name>/, so target that path โ not the default~/.hermes/:cp ~/hermes-shared/USER.shared.md \ ~/.hermes/profiles/workstation/memories/USER.md
Do not symlink it. Hermes performs atomic writes under HERMES_HOME, so a symlink there can be silently replaced by a regular file. A deliberate one-time copy per device is the robust option.
7. Share Knowledge with QMD or LLM Wiki
MEMORY.md and USER.md are tiny, always-in-prompt scratchpads (roughly 2,200 and 1,375 characters), loaded as a frozen snapshot at session start. They are not a knowledge base. Real knowledge lives as Markdown under knowledge/, projects/, and handoffs/, and is retrieved on demand.
QMD is the optional Hermes skill for local hybrid search over Markdown โ combining keyword search, vector search, and reranking. Installing the skill alone is not enough; QMD is a separate CLI tool:
# 1. Install the Hermes QMD skill (official slug)
hermes skills install official/research/qmd
# 2. Install the QMD CLI globally (standalone tool, not part of the skill)
npm install -g @tobilu/qmd
# 3. Add the shared repository as a collection
qmd collection add ~/hermes-shared --name hermes-shared
# 4. Build the index and embeddings
qmd embedNotes:
- Node.js
โฅ 22is required. - The first
qmd embedrun downloads roughly 2 GB of models once; everything runs locally afterwards. - The index is stored at
~/.cache/qmd/index.sqlite, outside the repository. It is never synchronized and is always reproducible from the Markdown files. - MCP integration is the preferred wiring: once configured as an MCP server, the agent gets the
qmdtools automatically without loading the skill each time.
Forgejo Markdown โ local QMD index (~/.cache/qmd/index.sqlite) โ Hermes via MCP/skillSimpler alternative: the LLM Wiki skill
If you do not want a search index at all, Hermes bundles an LLM Wiki skill for a linked Markdown knowledge base. Point it at the cloned repository with an environment variable:
WIKI_PATH=/home/user/hermes-shared/knowledgeUse an absolute path here: a ~ in a .env file is not reliably expanded. This keeps the actual knowledge entirely outside HERMES_HOME โ no symlink, no index to rebuild.
8. Project Handoffs and Session Exports
The one genuinely awkward case is switching between desktop and laptop on the same project, because local session search only sees the local state.db. Bridge it with a compact handoff file per topic:
handoffs/current/hermes-architecture.mdA good handoff records the goal, current state, decisions made, changed files, open points, and โ most importantly โ the next concrete step. At the end of each meaningful session, ask the agent to update it:
Update the handoff file for this project with decisions, changed state, open points, and the next concrete step. Do not include any secrets.
Within a single instance, Hermes can also hand a live CLI session to a messaging platform while preserving the session ID and full transcript:
/handoff matrixand resume a session later by title:
hermes -r "Session title"This bridges the CLI and the gateway of the same profile instance; it does not connect the desktop agent to the server agent. A task started purely in a local CLI process reports completion there โ a parallel Matrix gateway does not consume that process-local completion queue. Matrix /sessions and /resume are also intentionally scoped by room/thread, so they are not an unrestricted browser for every CLI session. Use /handoff matrix for the supported channel transition.
Session exports
Hermes can export sessions in several formats via --format: jsonl (default), md, qmd, html, and trace. The --redact flag scrubs secrets, tokens, and credentials from any format:
# One session, redacted, as Markdown for a cross-device handoff
hermes sessions export \
--format md \
--session-id SESSION_ID \
--redactMarkdown/QMD exports write one file per session plus a manifest.jsonl with paths and SHA-256 hashes. Drop the redacted files under handoffs/exports/ and let QMD index them.
| โ ๏ธ REDACT BEFORE COMMITTING, AND EXPORT IS NOT IMPORT |
Always use |
9. Safe Git Synchronization and Offline Work
Desktop and laptop are the real twin case: same projects, same repository. Sync must never block Hermes from starting, and โ crucially โ a merge conflict must not be mistaken for a network outage.
Before a work session, distinguish the two failure modes explicitly:
cd ~/hermes-shared
if ! git fetch; then
# fetch failed โ Forgejo/network unreachable
printf '%s\n' 'Forgejo unreachable โ using local state.' >&2
elif ! git rebase '@{u}'; then
# fetch worked, rebase failed โ a real conflict
git rebase --abort
printf '%s\n' 'Rebase conflict โ resolve manually before continuing.' >&2
fiAfter a session, re-sync before pushing so a conflict is never silently swallowed:
cd ~/hermes-shared
git add knowledge projects handoffs skills/shared
if git diff --cached --quiet; then
printf '%s\n' 'Nothing to commit.'
else
git commit -m "Update Hermes shared knowledge"
if ! git fetch; then
printf '%s\n' 'Push skipped โ Forgejo unreachable. Commit kept locally.' >&2
elif ! git rebase '@{u}'; then
git rebase --abort
printf '%s\n' 'Push skipped โ rebase conflict. Resolve manually.' >&2
elif ! git push; then
printf '%s\n' 'Push failed โ commit kept locally for later.' >&2
fi
fiTwo habits keep this painless: never edit the same handoff file on two devices at once, and never run a blanket rsync --delete against shared directories.
10. Sharing Reviewed Skills
Hermes can create and improve skills on its own, so shared skills must never blindly overwrite local ones. Keep locally generated skills in the active profileโs $HERMES_HOME/skills/ directory โ for example, ~/.hermes/profiles/workstation/skills/ โ and promote a skill to skills/shared/ only after you have reviewed it.
Instead of copying files around, wire the reviewed directory in natively:
skills:
external_dirs:
- ~/hermes-shared/skills/shared
write_approval: trueHermes then reads the reviewed skills straight from the clone. On a name clash the local skill wins, so there are no shadowing surprises.
This reviewed directory is not the complete skill trust boundary. Hermes also ships a large built-in catalog (69 enabled built-ins on the v0.18.2 installation tested for this guide). Those skills are trusted as part of the Hermes release, not because you reviewed each file. Audit the effective set and all additional sources explicitly:
hermes -p workstation skills list --enabled-only
hermes -p workstation skills list --source hub
hermes -p workstation skills list --source local
hermes -p workstation plugins list --userAn empty Hub/local list means no extra skill package is installed; it does not mean Hermes has no built-in skills. Treat Hermes updates as skill supply-chain updates too.
| ๐ WRITE_APPROVAL IS NOT A FILE PERMISSION |
External skill directories are treated like local skills. If the Hermes process can write there, the agent can modify those files. |
11. Build a Matrix Fleet: One Profile, One Bot, One Gateway
Hermesโ official profile guide is explicit: each profile runs its own gateway as a separate process with its own bot token. That standard mode gives this deployment rule:
machine ร work context
=
Hermes profile + Matrix bot account + profile gateway serviceA machine-aware naming scheme prevents ambiguity as the fleet grows:
| Endpoint | Local profile | Visible Matrix account | Role |
|---|---|---|---|
ws01 | workstation | @hermes-ws01-main:matrix.example.org | General desktop work |
ws01 | client-a | @hermes-ws01-client-a:matrix.example.org | Customer work using desktop files |
lap01 | workstation | @hermes-lap01-main:matrix.example.org | Mobile/offline workstation |
srv01 | server | @hermes-srv01-main:matrix.example.org | Scoped or deliberately full-trust 24/7 server work |
srv01 | client-a-server | @hermes-srv01-client-a:matrix.example.org | Optional scoped 24/7 customer role |
The bot name identifies both the machine and the context. A bot can serve several private rooms; you do not need one bot per room. Use rooms for projects/topics, Matrix threads for individual tasks, and a private Space such as Hermes Fleet to collect the rooms.
Create profile-scoped services
# A persistent customer assistant on ws01
hermes profile create client-a \
--description "Personal assistant for a continuously supported customer."
# Profiles do not automatically use their HERMES_HOME as the tool cwd
hermes -p client-a config set \
terminal.cwd "/absolute/path/to/client-a"
# Select and authenticate the profile's inference provider
hermes -p client-a model
# One independent gateway service per profile
hermes -p workstation gateway install
hermes -p client-a gateway install
hermes -p workstation gateway start
hermes -p client-a gateway start
# Machine-wide overview
hermes gateway listHermes creates profile-scoped service names such as hermes-gateway-workstation and hermes-gateway-client-a. hermes gateway start|stop|restart --all manages every profile gateway on the machine.
Inference credentials are profile-scoped too, in auth.json and/or .env. Give every concurrently running OAuth profile a fresh login rather than copying one auth.json: rotating refresh tokens are not safe to clone between live profiles. Restart the affected gateway after authentication.
Each profile gets a separate .env, Matrix account, token, device ID, home room, and crypto store:
MATRIX_HOMESERVER=https://matrix.example.org
MATRIX_USER_ID=@hermes-ws01-client-a:matrix.example.org
MATRIX_ACCESS_TOKEN=SECRET
MATRIX_ALLOWED_USERS=@you:matrix.example.org
MATRIX_ALLOWED_ROOMS=!client-a-room:matrix.example.org
MATRIX_HOME_ROOM=!client-a-room:matrix.example.org
MATRIX_DEVICE_ID=HERMES_WS01_CLIENT_A
MATRIX_E2EE_MODE=required
MATRIX_REQUIRE_MENTION=false
MATRIX_AUTO_THREAD=trueNever run the same bot credentials from two gateways. The account, access token, stable device ID, crypto store, and recovery material form one profile-instance identity.
Adopt an existing legacy bot without breaking E2EE
Matrix user IDs cannot be renamed. An existing account such as @hermes:matrix.example.org does not need replacement just to fit a prettier fleet scheme: change its display name to Hermes ยท SRV01 ยท Main, rename its room to [SRV01] Main, and add that room to the private Hermes Fleet Space. Record the old user ID as a legacy identifier.
Do not cosmetically edit its existing MATRIX_DEVICE_ID. The access token, device ID, E2EE crypto store, and recovery material form one device identity. Moving to a fully new ID such as @hermes-srv01-main:matrix.example.org requires a planned migration with a new account, token, E2EE device, verification, room membership, and controlled cutover โ not a one-line .env change.
Why not one device bot routing every profile?
Hermes also officially supports an opt-in multiplexing gateway. In its ordinary per-credential mode, one process loads every profileโs own bot credentials and adapters. The more advanced profile_routes mode can route rooms or threads from one shared bot credential into secondary profiles. On Hermes Agent v0.18.2, the release tested for this guide, auxiliary outbound paths such as progress, clarification, approval, streaming, errors, and background notification resolve a profile-owned adapter and fail closed when that routed secondary profile has none. For a small personal/customer fleet, the documented default of independent gateways and bot identities is therefore easier to reason about and safer to operate. Re-evaluate shared-bot routing only after an end-to-end test on your exact Hermes release.
CLI and Matrix share a profile, not a live process
CLI and Matrix under the same profile share HERMES_HOME, memory, knowledge, and the session database, but a session still starts on a channel:
- Matrix-started work replies in its room/thread.
- A local CLI background task reports back to the CLI process, not automatically to Matrix.
- Matrix
/sessionsand/resumeapply origin-aware room/thread restrictions for security. /handoff matrixis the reliable way to move an active CLI session to that profileโs Matrix bot.
If work must remain reachable while ws01 sleeps, place a deliberately scoped role on srv01. That role may be restricted, or it may intentionally be a full-trust personal infrastructure assistant; document which model you chose instead of assuming that a Hermes profile enforces the distinction. If it needs desktop-only files or tools, its bot belongs on ws01 and is available only while that machine is awake. Installing the same logical role on two endpoints still creates two independent profile instances; their sessions and memories do not replicate.
Multiple gateways on one machine
Matrix connects outbound to the homeserver and does not bind a local listener port. Multiple Matrix-only profile gateways therefore do not collide on port 8642. Assign distinct ports only when more than one profile enables a port-binding platform such as the API server, webhooks, Feishu/WeCom callbacks, SMS, WhatsApp Cloud, or LINE.
There are two additional official operating rules:
The web dashboard is machine-wide and manages every profile through its profile switcher; do not run one dashboard per profile.
Only one gateway owns the Kanban dispatcher. Keep the primary gateway at the default and configure every additional gateway with:
kanban: dispatch_in_gateway: false
Optional proxy mode
Proxy mode moves a messaging adapter and E2EE handling into another container or host while the actual agent remains where its files and tools live:
Matrix container Host profile
โโโ mautrix / E2EE โโโ LLM keys
โโโ access token โโHTTPโโโถโโโ memory / sessions
โโโ crypto store โโโ local toolsUse it only when that isolation solves a real deployment constraint. The current proxy path does not relay every tool-progress and dangerous-command approval interaction, so it is not a drop-in replacement for a local profile gateway.
The E2EE crypto store
The Matrix crypto store typically lives at:
~/.hermes/profiles/<profile>/platforms/matrix/store/crypto.dbThe default profile uses the corresponding path directly under ~/.hermes/. The store must never be committed to Git, live-replicated, used by two gateways at once, or regenerated independently with the same device identity.
| โ ๏ธ LOSING CRYPTO.DB CAN BREAK THE BOT'S IDENTITY |
Losing |
MATRIX_RECOVERY_KEY belongs to the specific Matrix bot account whose cross-signing identity it recovers. Do not reuse your personal accountโs recovery key or one botโs key for another bot. It is an E2EE recovery mechanism, not login two-factor authentication.
12. Security Hardening
Every Matrix-reachable profile exposes the tools and operating-system permissions of its host. That does not force one universal least-privilege design. Choose and document one of two legitimate operating models:
- Restricted automation identity: run Hermes as a separate Unix user or in a genuinely isolated container, then add only the filesystem paths, SSH identities, and operational commands that role needs.
- Full-trust personal assistant: intentionally let Hermes use the same files, SSH access, Docker access, and administrative capabilities you use. This preserves maximum utility, but the host operating system is no longer a containment boundary for agent mistakes or prompt injection. Matrix identity, tool and model supply chains, outbound-action policy, and recoverable backups become load-bearing controls.
A Hermes profile alone implements neither model at the OS level. It separates Hermes state, not Unix permissions.
Lock down the Matrix entry point
Without an allowlist, anyone who can message the bot in a joined room can trigger the agent. Always constrain it:
# Only these accounts may trigger the agent
MATRIX_ALLOWED_USERS=@you:matrix.example.org
# Only these rooms are served; DMs from allowed users are exempt and always work
MATRIX_ALLOWED_ROOMS=!roomid:matrix.example.org
# Stable, machine- and profile-specific device ID
MATRIX_DEVICE_ID=HERMES_SRV01_MAIN
# Enforce E2EE instead of allowing a plaintext fallback
MATRIX_E2EE_MODE=required
# Only the user who requested an approval may operate that prompt
MATRIX_APPROVAL_REQUIRE_SENDER=trueE2EE requires the mautrix[encryption] extras and the libolm library; install them before enabling encryption. With MATRIX_E2EE_MODE=optional, the bot falls back to an unencrypted client if those dependencies or the crypto setup are unavailable โ it logs a warning rather than failing. required refuses to start instead, which is the safe choice; off disables E2EE entirely.
Set a stable MATRIX_DEVICE_ID: each access token is bound to a device ID, and without a fixed one the E2EE keys rotate on every restart.
Treat the allowed Matrix account as an administrative identity: use a unique strong password, review and revoke old access tokens and devices, verify the devices you still use, and protect its recovery material. A plain local-password Synapse deployment does not gain login MFA merely by using Element. For real MFA, delegate authentication to Matrix Authentication Service/OIDC with an MFA-capable identity provider, or use an appropriate authentication module. MATRIX_RECOVERY_KEY is not a substitute for MFA.
Audit effective root access
Check the identity that actually runs each gateway:
id
sudo -n -l
stat -c '%A %U:%G %n' /var/run/docker.sock 2>/dev/nullNOPASSWD: ALL grants immediate root. Membership in the group that owns a rootful Docker socket is also root-equivalent: the process can mount the host filesystem into a container. Removing only one path does not create a meaningful root boundary while the other remains. If full host administration is intentional, keep it as an explicit full-trust decision. If it is not, remove every root-equivalent path from the gateway identity or use a separate service user. A verbal chat approval cannot create Linux permissions the process does not have; if it already has those permissions, the approval is a policy instruction, not an OS-enforced gate.
Separate soft guidance from hard enforcement
Use safe approval defaults on every exposed profile:
approvals:
mode: smart
cron_mode: denysmart evaluates flagged commands with an auxiliary model; it does not promise a human confirmation for every sudo, Docker, network, or filesystem action. SOUL.md rules and verbal confirmations are also useful but soft: they guide a cooperative model and reduce accidental overreach, yet a successful prompt injection targets that very decision-making process. Hard boundaries come from OS permissions, narrowly scoped credentials, config-enforced allow/deny rules, service ACLs, and independently verified approval brokers.
Guard the outbound edge
The central indirect-prompt-injection risk appears when three capabilities meet: Hermes reads untrusted content, can access private data, and can send information or make changes outside the machine. Email, websites, downloaded documents, issue comments, foreign repositories, and tool output must be treated as data, never as authority to expand permissions.
Reading, searching, analysis, drafts, local edits, and local commits can normally remain free. Require concrete approval for the destination, payload, and purpose before sending mail, submitting forms or API requests, uploading files, triggering webhooks, pushing Git commits, publishing, purchasing, inviting users, changing cloud resources, or running state-changing commands on another host. A local commit counts as outbound when hooks, CI, or automation transmit it. Headless jobs must use only destinations and data scopes approved in advance.
| โ ๏ธ SOUL.MD IS GUIDANCE, NOT A SECURITY BOUNDARY |
Standing Egress and prompt-injection rules in |
13. Backups and Recovery
Hermes ships a native full-backup command. Its scope is the entire Hermes installation on one machine, including every named profile under that Hermes root. Passing -p <profile> does not narrow a full backup to that profile, so run it once per device:
# One full machine-level Hermes backup
hermes backup
# Or write it to an explicit staging path
hermes backup --output /secure/staging/hermes-backup.zipIt creates a zip archive of global configuration, credentials, skills, sessions, data, and all profiles. SQLite databases are copied through SQLiteโs backup() API, so the snapshot is WAL-consistent even while Hermes is running; live WAL sidecars are intentionally excluded because the committed state is already captured. Running hermes -p server backup, hermes -p workstation backup, and hermes -p client-a backup on the same machine would therefore create three redundant full-scope archives, not three profile backups.
For a portable export of one profile, use:
hermes profile export server ./server-profile.tar.gzThat export contains the profileโs working state but deliberately strips .env and auth.json. It is useful for profile transfer, not a complete disaster-recovery backup unless credentials are protected separately.
Quick snapshots are different again:
hermes -p server backup --quick --label pre-change--quick copies critical files into $HERMES_HOME/state-snapshots/ for fast local rollback and automatically prunes older snapshots. It is not a zip, not encrypted, and not off-host; disk failure, theft, ransomware, or a root-equivalent agent can destroy the original and the quick snapshot together.
| โ ๏ธ THE BACKUP ARCHIVE IS NOT ENCRYPTED |
The full zip includes |
The Hermes archive does not cover data outside its root. Back up separately and encrypted:
- the Forgejo repository itself,
- the serverโs container/systemd configuration,
- external project data, credentials, and tool configuration that Hermes uses,
- any Matrix recovery material intentionally stored outside Hermes.
Copy the encrypted result to a second physical system. If the Hermes host holds credentials that can delete every remote snapshot, root compromise can destroy the backup too. Prefer server-side retention, snapshots, append-only or deletion-protected storage, or separate administrative credentials, depending on what your backup platform supports. Test an actual restore; a successful upload alone is not a recovery plan.
| ๐ A BACKUP IS NOT REPLICATION |
A backup is a resting recovery copy. It is not active replication and must never be opened by two Hermes instances at once. Restore the serverโs |
14. Verification Checklist
Before you rely on the setup, confirm each point:
- Each device runs its own local
state.db; none of them is shared. -
.gitignoreexcludesstate.db*,.env,auth.json,memories/,sessions/, and the Matrix store. -
hermes profile installsucceeded from the local distribution path on every device. -
hermes profile updatekeeps localconfig.yaml(test that an override survives). -
SOUL.mdcarries shared personality;USER.mdwas copied deliberately where needed. - QMD (or the LLM Wiki skill) returns results from
knowledge/. - A
--redactexport was reviewed by hand; secrets and tokens are gone and no sensitive path leaked. - The Git sync script distinguishes an unreachable Forgejo from a rebase conflict.
-
MATRIX_ALLOWED_USERS,MATRIX_ALLOWED_ROOMS,MATRIX_DEVICE_ID, andMATRIX_E2EE_MODE=requiredare set. - Matrix approval prompts are requester-bound; old tokens/devices were reviewed and revoked where appropriate.
- Every Matrix-exposed profile has its own bot account, access token, stable device ID, crypto store, and gateway service.
- Each botโs recovery key belongs to that bot account and is stored encrypted outside Forgejo.
- Bot and device names identify both endpoint and profile (for example,
ws01+client-a). - Two profile gateways can run concurrently; any enabled listener platforms use distinct ports.
- Exactly one gateway owns Kanban dispatch; additional profiles set
kanban.dispatch_in_gateway: false. - Normal replies, progress, clarification/approval, cron delivery, and
/handoff matrixwork in an encrypted room. - A local CLI background task is not incorrectly expected to notify Matrix without a handoff or explicit delivery workflow.
-
approvals.mode: smartandapprovals.cron_mode: denyare explicit on exposed profiles. - Docker-group membership, passwordless sudo, SSH identities, and other root-equivalent paths were audited and consciously accepted or removed.
- Hub/local skills, plugins, MCP servers, and the enabled built-in skill set were reviewed as separate trust sources.
- Untrusted input cannot silently authorize mail, pushes, uploads, publishing, or remote state changes.
- One full
hermes backupper device is encrypted and stored off-host; quick snapshots are not counted as disaster-recovery copies. - Remote backup retention resists deletion by ordinary client credentials, and a restore was tested.
15. Updates and Maintenance
Keep every device on the same tested Hermes version, and record the approved versions in the repository, e.g. hermes-shared/VERSIONS.md, covering the Hermes version, Matrix/mautrix dependencies, container image digest, config schema, distribution version, and Node/QMD version.
Roll updates out deliberately:
- Update one device first and run the verification checklist.
- Pull and
hermes profile update <alias>on the other devices; add--force-configonly when you intend to adopt new distribution config. - Rebuild the QMD index if the knowledge base changed significantly.
- Test Matrix E2EE after any server or crypto-related update.
Conclusion
The durable architecture is neither a single central master nor an unchecked multi-master system. It is several specialized local profile instances, a shared Git-versioned Markdown knowledge base, clear handoffs between equivalent workstations, one unambiguous Matrix bot and gateway per exposed profile instance, deliberately scoped 24/7 roles on servers, and strictly local session databases.
That combination keeps desktop, laptop, and server independently functional while sharing exactly the information that is genuinely device-independent. It avoids synchronizing SQLite state, secrets, and cryptographic device identities, but it does not by itself contain a full-trust agent or eliminate prompt-injection, credential, and host-level risk.





