Hermes Agent on Mac: a local-first setup with two profiles, A (local) and B (cloud)

Lire en français

Who is this for? macOS Apple Silicon users who want a sovereign, local-first AI assistant for their sensitive data, AND a high-performance cloud mode for the tasks that earn it. Technical readers comfortable in the terminal, who are not afraid to edit a config.yaml.

TL;DR

Mode A (local)Mode B (cloud)
Modelgpt-oss:20b (Ollama, daily driver); override to gemma4:26bMiniMax-M3 (minimax-oauth) plus a fallback chain
NetworkNone. Strictly local.Cloud (Claude, Gemini, Nous, OpenRouter)
Sensitive dataYesNever
ToolsetLight (6 tools, ~83 KB prompt)Full
Performance~40 s first reply (gpt-oss), ~80 s (gemma4:26b)<5 s (cloud)
Switchops/hermes-mode.sh aops/hermes-mode.sh b

Three principles of the setup:

  1. Code lives in ~/DEV/hermes-agent (Git-versioned), secrets in ~/.hermes (out of the repo)
  2. Two native Hermes profiles: default (local) and cloud, each with its own isolated HERMES_HOME
  3. A dedicated work directory (~/DEV/hermes-work/) with its own AGENTS.md, not run from the repo

Part 1: Installation

Why two profiles instead of one?

Before starting, I hesitated. A single profile with hermes chat -m <model> to switch model on the fly is tempting. Digging in, I understood that Hermes profiles are not just about picking a model. Each profile is a completely isolated HERMES_HOME:

  • config.yaml, .env, memory, sessions, sandbox, skills: everything is duplicated
  • the cwd’s AGENTS.md is read separately
  • tools can diverge between profiles

What that buys you, concretely:

  • Secret isolation: if a prompt injection reads the .env in Mode A, it finds no Claude/Gemini/OpenRouter token
  • Safe default posture: Mode A is active at startup, so I have to switch out explicitly
  • Sandbox isolation: my cloud work does not pollute my local files
  • Divergent toolsets: Mode A can be slimmed for speed without touching Mode B

Nous Research documents this on purpose: “profiles are independent islands on purpose” (see the repo’s AGENTS.md, line 138).

Why separate code and secrets?

~/DEV/hermes-agent is a Git repo (Nous Research’s own, forkable). If I put my API keys in it, git status shows them and a careless git add commits them. The fix: everything secret or shared lives in ~/.hermes/, never in the repo. That is the convention the official bootstrap follows.

Prerequisites

  • macOS Apple Silicon (tested on an M2 Max)
  • Docker Desktop running (active icon in the menu bar): the sandbox execution backend
  • Ollama installed (brew install ollama)
  • a zsh terminal
docker info        # must print server info, not a connection error
ollama --version

Build and install

# Build tools
curl -LsSf https://astral.sh/uv/install.sh | sh
brew install node
exec $SHELL -l

# Clone (submodules = third-party plugins/skills)
mkdir -p ~/DEV
git clone --recurse-submodules https://github.com/NousResearch/hermes-agent.git ~/DEV/hermes-agent
cd ~/DEV/hermes-agent

# venv + editable install (Python 3.11 pinned)
uv venv venv --python 3.11
export VIRTUAL_ENV="$(pwd)/venv"
uv pip install -e ".[all,dev]"

# Local browser (used by some Mode A tools)
npm install
npx playwright install chromium

# Expose the venv launcher (NOT the repo wrapper)
mkdir -p ~/.local/bin
ln -sf "$(pwd)/venv/bin/hermes" ~/.local/bin/hermes
grep -q 'HOME/.local/bin' ~/.zshrc || echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
exec $SHELL -l

hermes version      # verify the install

Initial configuration

hermes setup        # interactive wizard
hermes doctor       # full health check

Keys and identities: where each thing lives

Mode A, ~/.hermes/.env (the real sovereign posture, kept clean):

TAVILY_API_KEY=tvly-xxxx        # web search
FIRECRAWL_API_KEY=fc-xxxx       # page extraction
GITHUB_TOKEN=ghp_xxxx           # Skills Hub rate limit (optional)
TELEGRAM_BOT_TOKEN=...          # fine: chat channel, not model auth
TELEGRAM_ALLOWED_USERS=...
TELEGRAM_HOME_CHANNEL=...
TERMINAL_ENV=docker
# Deliberately absent: OPENROUTER_API_KEY, EXA_API_KEY, GOOGLE_API_KEY,
# ANTHROPIC_TOKEN. Every cloud key lives in profiles/cloud/.env.

Mode B, ~/.hermes/profiles/cloud/.env (and/or via OAuth login):

ProviderAuthCommand
MiniMax-M3 (current primary)OAuth (minimax-oauth)hermes -p cloud model
Claude (Anthropic)OAuth (Claude Pro)hermes -p cloud model then Anthropic then browser login
Google GeminiAPI key (GEMINI_API_KEY)Deliberate choice: Google discourages Gemini OAuth inside third-party agents
Nous PortalOAuthhermes login
OpenRouterAPI keyWide safety net

ToS note: using a Claude Pro subscription via OAuth inside a third-party agent is a grey area. It works, but the official path is the Anthropic API key (same as for Google Gemini).

Execution backend: Docker

hermes setup tools    # Execution / Terminal backend then docker
hermes config set terminal.backend docker    # verify

The default image is nikolaik/python-nodejs:python3.11-nodejs20. Sandboxes persist their files in ~/.hermes/sandboxes/<profile>/.

Models: choose one per use

Mode A: two models for two uses

ModelSizeFirst-reply timeUse
gpt-oss:20b13 GB~40 sDaily driver: Excel, CSV, transforms, debug
gemma4:26b-a4b-it-q4_K_M17 GB~80 sSensitive research, long reasoning

Why two models? At first I ran only the 26B Gemma. The problem: on an M2 Max, a forward pass through 26B parameters saturates compute before prompt size matters. Measured: full toolset vs light toolset = 1m vs 1m20s, inside the margin of error. The real local-speed lever is model size.

Setup:

# Check what is installed
ollama list

# Pull what is missing (gpt-oss:20b is 13 GB, gemma4:26b is 17 GB)
ollama pull gpt-oss:20b
ollama pull gemma4:26b-a4b-it-q4_K_M

# Set gpt-oss:20b as Mode A primary
hermes -p default model    # interactive picker

# One-off override to the 26B (sensitive research):
hermes -p default chat -m gemma4:26b-a4b-it-q4_K_M

Context-window trap: Hermes Agent requires at least 64K tokens of context. Several 7B models (like qwen2.5-coder:7b) cap at 32K and are rejected at init. Always check before adopting a model: ollama show <model> or ollama run <model> /show info.

Mode B: primary plus fallback chain

Current primary: MiniMax-M3 (provider minimax-oauth, base https://api.minimax.io/anthropic). Ordered fallback chain:

Primary:   MiniMax-M3              (via minimax-oauth)
  1. claude-opus-4-8              (via anthropic, Claude Pro OAuth or API key)
  2. gemini-3.5-flash            (via gemini, Google API key)
  3. stepfun/step-3.7-flash:free (via nous)
  4. openai/gpt-5.5              (via openrouter, API key)

The order is managed with:

hermes -p cloud fallback list        # inspect
hermes -p cloud fallback add         # add an entry (validated picker)
hermes -p cloud fallback remove      # remove an entry
hermes -p cloud fallback clear       # start fresh

People ask why MiniMax-M3 over Claude Opus 4.8. Two reasons. Cost first, and it is not close. Then concentration risk: leaning the whole stack on a single vendor, in a single jurisdiction, is exactly the dependency I spend my fractional-CIO days helping clients design out. A second provider in a different orbit is cheap insurance, not a downgrade.

Setting up the two profiles

The ops/hermes-mode.sh wrapper ships with the repo and simplifies the switch. For Mode B:

# One-time: create the isolated 'cloud' profile
ops/hermes-mode.sh bootstrap

# Cloud logins (one-time, interactive)
hermes -p cloud model              # MiniMax-M3 primary, then Claude OAuth if needed

# Put API keys in ~/.hermes/profiles/cloud/.env:
#   GEMINI_API_KEY=...
#   OPENROUTER_API_KEY=...

# Fallback chain
hermes -p cloud fallback clear
hermes -p cloud fallback add       # Gemini, then Nous, then OpenRouter
hermes -p cloud fallback list

Part 2: Daily use

The trap: NEVER run hermes chat from the repo

If you cd ~/DEV/hermes-agent && hermes chat, the repo’s AGENTS.md (69 KB, a dev guide for Hermes contributors) is auto-injected, then truncated to ~31 KB. The result: a constant warning, Context file AGENTS.md TRUNCATED: 69356 chars exceeds limit of 31457, and you burn prompt, so tokens, for nothing.

Fix: create a dedicated work directory.

mkdir -p ~/DEV/hermes-work

Put an AGENTS.md in it that sounds like you: general-purpose, 1 to 2 KB, with your working style and constraints. Example:

# User context

You are my personal AI assistant. You help with my daily tasks (data analysis,
research, scripting, debugging, writing, and so on).

## Default posture
- Local mode (default): Ollama via Hermes, no network calls.
- Cloud mode: only on explicit request.

## Working style
- Get to the point. No preamble, no restating the question.
- Answer in French unless the task is in English.
- For code: concrete, runnable snippets.
- If you do not know, say so.

## Security and data
- No destructive command without confirmation.
- No upload off the Mac without explicit request.
- For sensitive files: local processing (pandas/openpyxl).

## Memory and context
- For recurring work, suggest a project name for `hermes sessions --continue`.

Personalize it over time (preferences, business constraints). It is your per-directory context briefing.

A light toolset for Mode A

By default the default profile loads the full toolset (hermes-cli, ~100 tools, ~250 KB of schemas). For Mode A, slim it down.

In ~/.hermes/config.yaml:

platform_toolsets:
  cli: [file, memory, session_search, terminal, todo, web]
  telegram: [file, memory, session_search, terminal, todo, web]

Measured effect: light toolset = 31 tools, ~83 KB of prompt (vs ~250 KB). The speed gain is marginal on the 26B (compute-bound), but noticeable on gpt-oss:20b. The real benefit is elsewhere: smaller attack surface, more readable logs.

To re-enable everything for one session: cli: [hermes-cli], or switch to Mode B.

Daily workflow

# A local work session
cd ~/DEV/hermes-work
ops/hermes-mode.sh a          # Mode A (local/private)
hermes chat                   # open the session

# A cloud work session
cd ~/DEV/hermes-work
ops/hermes-mode.sh b          # Mode B (cloud)
hermes chat

# One-off overrides (without changing the active profile)
hermes chat -m gemma4:26b-a4b-it-q4_K_M    # another model, same profile
hermes chat -p cloud                        # one Cloud session without switching
hermes chat -p cloud -m claude-opus-4-8     # Cloud + specific model
hermes chat -t terminal,file,memory         # ad hoc toolset

The active profile is persistent

Once you run ops/hermes-mode.sh a (or b), every following hermes inherits the choice, including after you close the terminal. The single source of truth is ~/.hermes/active_profile. To check:

ops/hermes-mode.sh status
hermes profile

Telegram bot

The Telegram bot is a separate daemon, installed as a launchd service.

Initial setup:

# Mode A by default
ops/hermes-mode.sh a
hermes gateway install              # daemon + auto-start at login
hermes gateway start                # start now
hermes gateway status               # check: "running"

After every A/B switch, restart the gateway so it reloads the config (otherwise it keeps serving with the old profile):

ops/hermes-mode.sh b
hermes gateway restart

On mobile: nothing to configure. Send your bot a message, it answers with the active profile’s model.

Useful commands:

ActionCommand
Gateway statehermes gateway status
Stop the bothermes gateway stop
Restart after a switchhermes gateway restart
Logshermes logs gateway

Cheat sheet

You want to…You run…
Work locallycd ~/DEV/hermes-work && ops/hermes-mode.sh a && hermes chat
Work in the cloudcd ~/DEV/hermes-work && ops/hermes-mode.sh b && hermes chat
One cloud sessionhermes -p cloud chat (active profile unchanged)
Change modelhermes chat -m <model-id>
Check stateops/hermes-mode.sh status
Switch + Telegram botops/hermes-mode.sh b && hermes gateway restart
Stop the bothermes gateway stop

Updating

One command:

hermes update

It updates the repo, the Python deps, and the submodules. No manual git pull needed.

Where is my data stored?

WhatWhere
Hermes code~/DEV/hermes-agent
Mode A config + secrets~/.hermes/
Mode B config + secrets~/.hermes/profiles/cloud/
Mode A sandbox (Docker bind)~/.hermes/sandboxes/
Mode B sandbox (Docker bind)~/.hermes/profiles/cloud/sandboxes/
Sessions, memory, skillsInside each profile (~/.hermes/<profile>/...)

The Docker image is nikolaik/python-nodejs:python3.11-nodejs20. The container’s /root home is bind-mapped to the sandbox dir above, so it persists between sessions.

Periodic validation

ops/hermes-mode.sh status
hermes doctor                 # Mode A
hermes -p cloud doctor        # Mode B
hermes -p cloud fallback list # Mode B fallback chain
hermes prompt-size            # Mode A: 31 tools, ~83 KB total prompt

Part 3: Going further

Documented use cases

Official tips

https://hermes-agent.nousresearch.com/docs/guides/tips

When the CLI wears you down: Hermes Desktop

Once all of this runs and is fully operational, if the CLI starts to wear you down and you miss a little GUI à la Claude Desktop or Minimax Code, you can launch hermes desktop and there you go. Same config, same profiles, same isolated secrets: just a graphical layer on top.

https://hermes-agent.nousresearch.com/docs/user-guide/desktop

Lessons learned

  1. Hermes profiles are a security boundary, not a model picker. Isolation of secrets, sandboxes, sessions, and a safe default posture.
  2. The local performance bottleneck is compute (model size), not the prompt. Cutting the toolset from 100 tools to 6 barely moves the clock. The speed lever is the model.
  3. Always check the context window before adopting a model. Hermes requires at least 64K. 7B models often cap at 32K.
  4. NEVER run hermes chat from the framework repo. The repo’s AGENTS.md (69 KB) gets injected and truncated. Create a work directory with your own AGENTS.md.
  5. A clean Mode A .env is the real sovereign posture. Any cloud key in ~/.hermes/.env is a leak channel in case of prompt injection.

Conclusion

This setup has run in personal production for a few weeks. Mode A for all sensitive data (accounting spreadsheets, client CSVs, configs, GDPR inside), Mode B for public research and the tasks that need a big model. The ops/hermes-mode.sh wrapper makes the switch transparent. The Telegram bot gives me the same agent from my phone.

The complexity cost is the initial setup. Day to day, it is just cd ~/DEV/hermes-work && ops/hermes-mode.sh a && hermes chat.

Note: this is a personal note restructured for future publication. Feedback welcome.