name: hermes-profile-management description: Manage and consolidate multiple Hermes profiles into a monorepo structure with proper symlinking and Git protection. Also covers SOUL.md identity architecture, per-profile personality design, orphan profile consolidation, Telegram thread mapping, and profile creation/deletion patterns. category: devops version: 1.0.0
This skill provides the workflow and constraints for consolidating multiple isolated Hermes profiles (e.g., it-ai, r-and-d, writers, etc.) into a single Git Monorepo. This approach centralizes configurations, custom skills, and memories while preventing runtime database locks, cache inflation, and credential leaks.
Understanding this boundary is essential for all profile operations:
| Layer | Path | Purpose | Git-tracked? |
|---|---|---|---|
| Runtime | ~/.hermes/ |
Hermes Agent config, secrets (.env), transient state (sessions/, memories/, state.db), cron jobs |
❌ Never |
| Product Source | /opt/ai-os/products/ceo/ |
Code, research content, scripts, identity files (SOUL.md), department mapping | ✅ Yes (ceo-ai-os.git) |
Rule: Identity files (SOUL.md) live in the repo so they are version-controlled and auto-synced via git cron. The runtime directory holds symlinks pointing back to the repo. Secrets (tokens, credentials) and transient state (session databases) stay in runtime only — never in the repo.
Every profile's SOUL.md should live in the repo and be symlinked from runtime:
Runtime (~/.hermes/) Repo (/opt/ai-os/products/ceo/)
───────────────────── ─────────────────────────────────
SOUL.md ──symlink──→ SOUL.md (default profile)
profiles/
writers/
SOUL.md ──symlink──→ profiles/writers/SOUL.md
it-ai/
SOUL.md ──symlink──→ profiles/it-ai/SOUL.md
grill-qa/
SOUL.md ──symlink──→ profiles/grill-qa/SOUL.md
bd-mkt/
SOUL.md ──symlink──→ profiles/bd-mkt/SOUL.md
r-and-d/
SOUL.md ──symlink──→ profiles/r-and-d/SOUL.md
reviewer/
SOUL.md ──symlink──→ profiles/reviewer/SOUL.md
Setup command pattern:
# Copy SOUL.md from runtime to repo, then symlink back
cp ~/.hermes/profiles/<name>/SOUL.md /opt/ai-os/products/ceo/profiles/<name>/SOUL.md
ln -sf /opt/ai-os/products/ceo/profiles/<name>/SOUL.md ~/.hermes/profiles/<name>/SOUL.md
Benefits: Edit SOUL.md in repo → git cron auto-commits and pushes → runtime reads through symlink. No manual sync needed.
Create a directory under your product root to house the configuration profiles:
mkdir -p /opt/ai-os/products/ceo/profiles/it-ai
mkdir -p /opt/ai-os/products/ceo/profiles/r-and-d
# ... for each profile
Only migrate configuration files. Leave database and runtime caches local to the system to avoid locking issues and repository bloating.
- FILES TO MOVE:
- config.yaml (Active model and tool configurations)
- skills/ (Custom skills - ensure you strip nested node_modules or local binaries)
- memories/ (Durable facts)
- SOUL.md (Identity — belongs in repo via symlink)
- 🚩 CRITICAL: Per-profile SOUL.md mapping:
- Hermes supports isolated SOUL.md files inside each profile directory (e.g. ~/.hermes/profiles/<name>/SOUL.md). This is the primary baseline voice and identity for that specific profile/agent.
- The profile-specific SOUL.md acts as Slot #1 in the system prompt for that profile, replacing the global ~/.hermes/SOUL.md.
- Always use SOUL.md for durable personality, communication style, level of directness, default interaction style, what to avoid, and handling of uncertainty.
- Do not use SOUL.md for project instructions or temporary workflows (those belong in AGENTS.md).
- Personalities (defined in config.yaml or built-in via /personality) should strictly be used as temporary, session-level tone overlays/modifiers rather than defining core job roles. Avoid defining complete job descriptions inside personalities (e.g. ai_advisor or policy_researcher containing comprehensive JDs should be refactored into the respective profile's SOUL.md instead).
- FILES TO IGNORE/LEAVE LOCAL:
- state.db* & kanban.db* (Local databases - let Hermes generate them dynamically)
- logs/, audio_cache/, image_cache/
- lsp/, chrome-profiles/, home/ (Runtime MCP dependencies)
- .env (API Keys - keep them local or use .env.example)
- sessions/, memories/ (Transient runtime state)
Ensure Hermes can still read the profiles from its default directory (~/.hermes/profiles/) by creating absolute symlinks pointing to the Monorepo:
# === config.yaml ===
# Move to monorepo and link back
cp ~/.hermes/profiles/it-ai/config.yaml /opt/ai-os/products/ceo/profiles/it-ai/config.yaml
ln -sf /opt/ai-os/products/ceo/profiles/it-ai/config.yaml ~/.hermes/profiles/it-ai/config.yaml
# === SOUL.md (same pattern, but SOUL.md belongs in repo for versioning) ===
cp ~/.hermes/profiles/it-ai/SOUL.md /opt/ai-os/products/ceo/profiles/it-ai/SOUL.md
ln -sf /opt/ai-os/products/ceo/profiles/it-ai/SOUL.md ~/.hermes/profiles/it-ai/SOUL.md
Important: Never leave config.yaml as a self-referential symlink (broken loop). After linking, verify with:
readlink -f ~/.hermes/profiles/<name>/SOUL.md # Should point into repo
.gitignore)Always enforce a strict ignore list in the monorepo root to block profile runtime artifacts:
# Block Hermes profile runtime and caches
**/node_modules/
**/lsp/
**/chrome-profiles/
**/logs/
**/*.db
**/*.db-wal
**/*.db-shm
**/.env
**/.update_check
# Runtime transient state
sessions/
memories/
**/memories/
**/sessions/
When setting up automated git synchronization (e.g., cron-driven git push), follow these strict rules to prevent credential leakage and system corruption:
git add -A or git add . in automated scripts. Explicitly add allowed directories or use specific file patterns. Using -A risks staging untracked secrets, environment variables (.env), or large databases (state.db) that haven't been added to .gitignore yet.git pull --rebase in unattended crons. If a conflict occurs, rebase leaves the repository in a "dirty" state that can crash runtime tools. Prefer git fetch followed by a check for divergence, or a simple git pull --ff-only..githooks/pre-commit để quét tự động các pattern nhạy cảm (API Keys, Google Tokens, Lark Base Tokens) và chặn commit trước khi chúng được đẩy lên Git.When managing shared skills across multiple profiles:
skills.external_dirs in config.yaml. This is a native Hermes feature that allows a profile to load skills from any directory (including a monorepo) without creating brittle symlink chains in the filesystem.SOUL.md. Tránh symlink các thư mục động như memories/ hay skills/ của profile con; thay vào đó hãy để memories/ là thư mục thực tế tại runtime và dùng skills.external_dirs cho skills.find ~/.hermes -xtype l to check for broken symlinks.cron_kanban_watcher.py) are not duplicated across ~/.hermes/scripts/ and the project repo. Duplicate names lead to non-deterministic execution.git ls-files | grep -iE 'token|key|auth|secret|credential' regularly to ensure no credentials have slipped into the index.skills/ directory, ensure those files are not just symlinks to a generic set, unless that generic set is the intended master./kanban-create) if the native Hermes core already supports it (like /kanban create). Adding custom wrappers introduces parsing overhead and syntax drift. Instead, document the correct usage of the native subcommand.config.yaml as a self-referential symlink. Broken loops can crash profile workers before they start. If a profile needs regeneration, write a fresh file, then verify readlink -f and hermes --profile <name> config path.SOUL.md is profile-specific: Keep profile-specific SOUL.md inside its corresponding profile directory to serve as the baseline voice. Ensure JDs and personas are not stored as session-level personalities in config.yaml to prevent identity dilution. Use personalities only for lightweight style switches.git status or subagent summaries are not proof. After sync, verify GitHub raw URLs return the expected HTTP codes and archive or delete old repos only after confirming the remote state.Before the first monorepo commit, run a cleanup sweep:
# Remove Apple Double metadata files (from macOS transfers)
find /opt/ai-os/products/ceo/ -name '._*' -delete
# Remove runtime databases and caches
find /opt/ai-os/products/ceo/profiles/ -name '*.db*' -delete
# Remove old standalone .git repos inside profiles (no longer needed)
find /opt/ai-os/products/ceo/profiles/ -name '.git' -type d -exec rm -rf {} + 2>/dev/null || true
Note: DO NOT delete SOUL.md files from profiles/ — they belong in the repo via symlink. Only delete stale interal .git dirs, Apple Double files, and runtime DB files.
After configs are synced and committed to the monorepo, the old standalone repos must be archived (or deleted if CEO confirms):
# Archive on GitHub (read-only, keeps history)
for repo in ceo-ai-os-profile-it-ai ceo-ai-os-profile-r-and-d ceo-ai-os-profile-writers ceo-ai-os-profile-str-mkt ceo-ai-os-profile-grill-qa; do
gh api -X PATCH "repos/nhivo2504/$repo" -f archived=true --silent && echo "OK: $repo"
done
Local success ≠ Remote success. After committing and pushing changes to the monorepo, verify that the remote GitHub state matches expectations:
# Verify a file was deleted on remote
curl -s -o /dev/null -w "%{http_code}" "https://raw.githubusercontent.com/nhivo2504/ceo-ai-os/main/profiles/it-ai/SOUL.md"
# Should return 404 if correctly deleted
# Verify a file was added/updated on remote
curl -s -o /dev/null -w "%{http_code}" "https://raw.githubusercontent.com/nhivo2504/ceo-ai-os/main/SOUL.md"
# Should return 200
Do not rely solely on git status or sub-agent summaries. Raw GitHub API/URL verification is the only reliable proof.