AI

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

7 min readLire en français
Contents
  1. TL;DR
  2. Part 1: Installation
  3. Part 2: Daily use
  4. Part 3: Going further
  5. Lessons learned
  6. Conclusion

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)
Model gpt-oss:20b (Ollama, daily driver); override to gemma4:26b MiniMax-M3 (minimax-oauth) plus a fallback chain
Network None. Strictly local. Cloud (Claude, Gemini, Nous, OpenRouter)
Sensitive data Yes Never
Toolset Light (6 toolsets, 31 tools, ~83 KB prompt) Full
Performance ~40 s first reply (gpt-oss), ~80 s (gemma4:26b) <5 s (cloud)
Switch ops/hermes-mode.sh a ops/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):

Provider Auth Command
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 Gemini API key (GEMINI_API_KEY) Deliberate choice: Google discourages Gemini OAuth inside third-party agents
Nous Portal OAuth hermes login
OpenRouter API key Wide 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

Model Size First-reply time Use
gpt-oss:20b 13 GB ~40 s Daily driver: Excel, CSV, transforms, debug
gemma4:26b-a4b-it-q4_K_M 17 GB ~80 s Sensitive 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 = 1m20s vs 1m, 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:

Action Command
Gateway state hermes gateway status
Stop the bot hermes gateway stop
Restart after a switch hermes gateway restart
Logs hermes logs gateway

Cheat sheet

You want to… You run…
Work locally cd ~/DEV/hermes-work && ops/hermes-mode.sh a && hermes chat
Work in the cloud cd ~/DEV/hermes-work && ops/hermes-mode.sh b && hermes chat
One cloud session hermes -p cloud chat (active profile unchanged)
Change model hermes chat -m <model-id>
Check state ops/hermes-mode.sh status
Switch + Telegram bot ops/hermes-mode.sh b && hermes gateway restart
Stop the bot hermes 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?

What Where
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, skills Inside 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 31 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.