Skip to content

feat: isolated profiles — multiple Hermes instances with independent config, gateway, and data#1750

Closed
teknium1 wants to merge 1 commit into
mainfrom
hermes/hermes-4c573e36
Closed

feat: isolated profiles — multiple Hermes instances with independent config, gateway, and data#1750
teknium1 wants to merge 1 commit into
mainfrom
hermes/hermes-4c573e36

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Adds profile management so users can run multiple fully isolated Hermes instances on the same machine. Each profile has its own config, API keys, memory, sessions, skills, gateway, cron, and logs — completely independent from other profiles.

Related to #896 (multi-agent architecture), but orthogonal — profiles provide infrastructure-level isolation (separate gateways, separate secrets), while #896 provides application-level routing within a single profile.

The Problem

Today, running two Discord bots on the same machine requires manually juggling HERMES_HOME env vars. Worse, hermes gateway stop uses a greedy ps aux scan that kills ALL gateway processes regardless of which instance they belong to.

What This Does

New: hermes_cli/profiles.py (~280 lines)

  • Profile CRUD: create, list, clone, delete
  • Profiles live at ~/.hermes/profiles/<name>/, each a full HERMES_HOME directory
  • Clone support: --clone default copies config.yaml + .env; --clone-data also copies memories/skills/skins
  • Gateway collision detection (refuses to delete a profile with a running gateway)
  • Active profile detection from HERMES_HOME

Modified: hermes_cli/main.py

  • --profile/-p flag pre-parsed before any module imports — critical because 30+ modules cache HERMES_HOME at import time via module-level constants
  • New profile subcommand: hermes profile create|list|delete|show

Fixed: hermes_cli/gateway.py

  • find_gateway_pids() now uses the HERMES_HOME-scoped PID file instead of a greedy ps aux scan
  • This prevents hermes -p work gateway stop from killing the personal profile's gateway
  • Legacy fallback scan retained for backward compat with old PID-less installs

Modified: hermes_cli/banner.py

  • Shows active profile name in CLI banner when not using the default profile

Usage

# Create profiles
hermes profile create work-bot
hermes profile create personal-bot --clone default    # clone config + API keys

# Configure each independently
hermes -p work-bot setup                              # separate .env, model, etc.
hermes -p personal-bot setup

# Run independent gateways (separate Discord/Telegram bots)
hermes -p work-bot gateway install && hermes -p work-bot gateway start
hermes -p personal-bot gateway install && hermes -p personal-bot gateway start

# Chat in a specific profile
hermes -p work-bot

# See all profiles
hermes profile list

Architecture

Each profile is just a HERMES_HOME directory. The -p flag sets HERMES_HOME before anything loads, so all downstream code naturally scopes:

Component Isolation mechanism
config.yaml, .env Separate HERMES_HOME directories
Memory {HERMES_HOME}/memories/
Sessions {HERMES_HOME}/sessions/
Skills {HERMES_HOME}/skills/
Gateway PID {HERMES_HOME}/gateway.pid (already scoped)
Systemd service Hash-scoped name per HERMES_HOME (already scoped)
Token locks XDG_STATE_HOME scoped locks (prevents same bot token on two profiles)
Cron jobs {HERMES_HOME}/cron/
State DB {HERMES_HOME}/state.db

The default profile is ~/.hermes itself — zero migration, fully backward compatible.

Tests

  • 40 new tests in test_profiles.py: validation, create, clone, delete, list, isolation, gateway PID scoping, systemd service name scoping
  • 3 new tests in test_gateway_pid_scoping.py: PID-file based lookup, cross-profile isolation
  • Full suite: 5278 passed, 0 new failures

Design Decisions

  1. Pre-import flag parsing--profile is extracted from sys.argv before any hermes modules import, because 30+ files cache HERMES_HOME at module level
  2. Profiles root at ~/.hermes/profiles/ — anchored to $HOME, not $HERMES_HOME, so profiles are always discoverable
  3. PID-file first for gateway stop — eliminates the main multi-profile collision vector while keeping a legacy fallback
  4. Clone as opt-in — fresh profiles start empty; --clone copies config; --clone-data copies memories/skills

Relationship to PR #896

PR #896 adds named agents within a single Hermes install (routing, tool policies, personas). This PR adds isolated profiles — separate installs that don't share anything. They compose naturally:

Profile "work" (HERMES_HOME=~/.hermes/profiles/work)
  ├── Agent "main" (Claude, full tools)
  └── Agent "coder" (Codex, coding tools only)

Profile "personal" (HERMES_HOME=~/.hermes/profiles/personal)
  └── Agent "assistant" (Gemini Flash)

…config, gateway, and data

Add profile management so users can run multiple fully isolated Hermes
instances on the same machine. Each profile gets its own HERMES_HOME
with independent config.yaml, .env, memory, sessions, skills, gateway,
cron, and logs.

New files:
- hermes_cli/profiles.py: profile CRUD (create, list, clone, delete)
- tests/hermes_cli/test_profiles.py: 40 tests covering profiles
- tests/hermes_cli/test_gateway_pid_scoping.py: 3 gateway isolation tests

Key changes:
- hermes_cli/main.py: --profile/-p flag pre-parsed before any module
  imports (critical: 30+ modules cache HERMES_HOME at import time).
  New 'profile' subcommand with create/list/delete/show actions.
- hermes_cli/gateway.py: find_gateway_pids() now uses the HERMES_HOME-
  scoped PID file instead of a greedy ps aux scan. This prevents
  'hermes gateway stop' for one profile from killing another profile's
  gateway.
- hermes_cli/banner.py: shows active profile name in CLI banner when
  not using the default profile.

Usage:
  hermes profile create work                 # new empty profile
  hermes profile create work --clone default # clone config+keys
  hermes -p work setup                       # configure the profile
  hermes -p work                             # chat in work profile
  hermes -p work gateway start               # isolated gateway
  hermes profile list                        # see all profiles

Design:
- Profiles live at ~/.hermes/profiles/<name>/ (separate from default)
- Default profile is ~/.hermes (backward compatible, zero migration)
- --profile sets HERMES_HOME before any imports, so all downstream
  code (config, memory, sessions, gateway PID, systemd service names,
  cron, etc.) naturally scopes to the profile
- Systemd service names are already hash-scoped per HERMES_HOME
- Token-scoped locks prevent two profiles from binding the same bot

Full suite: 5278 passed, 0 new failures.
@erosika

erosika commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

related: #2845 — also adds gateway env propagation, instances.json registry, HERMES_BASE_HOME, and .env identity protection

@teknium1 teknium1 closed this Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants