Compare commits

...

5 Commits

Author SHA1 Message Date
Teknium
e13b632435 docs: update skills catalog — add red-teaming category and all 16 optional skills
The skills catalog was missing:
- red-teaming category with the godmode jailbreaking skill
- The entire optional skills section (16 skills across 10 categories)

Added both with descriptions sourced from each SKILL.md frontmatter.
Verified against the actual skills/ and optional-skills/ directories.
2026-03-29 09:19:45 -07:00
Teknium
67c8e26704 fix(discord): clean up deferred "thinking..." after slash commands complete
After a slash command is deferred (interaction.response.defer), the
"thinking..." indicator persisted indefinitely because the code used
followup.send() which creates a separate message instead of replacing
or removing the deferred response.

Fix: use edit_original_response() to replace "thinking..." with the
confirmation text when provided, or delete_original_response() to
remove it when there is no confirmation. Also consolidated /reasoning
and /voice handlers to use _run_simple_slash instead of duplicating
the defer+dispatch pattern.

Fixes #3595.
2026-03-28 23:40:49 -07:00
Teknium
c6e3084baf fix(gateway): replace print() with logger calls in BasePlatformAdapter (#3669)
Salvage of PR #3616 (memosr). Replaces 6 print() calls with proper logger calls in BasePlatformAdapter + removes redundant traceback.print_exc().

Co-Authored-By: memosr <memosr@users.noreply.github.com>
2026-03-28 22:25:35 -07:00
Teknium
dcbdfdbb2b feat(docker): add Docker container for the agent (salvage #1841) (#3668)
Adds a complete Docker packaging for Hermes Agent:
- Dockerfile based on debian:13.4 with all deps
- Entrypoint that bootstraps .env, config.yaml, SOUL.md on first run
- CI workflow to build, test, and push to DockerHub
- Documentation for interactive, gateway, and upgrade workflows

Closes #850, #913.

Changes vs original PR:
- Removed pre-created legacy cache/platform dirs from entrypoint
  (image_cache, audio_cache, pairing, whatsapp/session) — these are
  now created on demand by the application using the consolidated
  layout from get_hermes_dir()
- Moved docs from docs/docker.md to website/docs/user-guide/docker.md
  and added to Docusaurus sidebar

Co-authored-by: benbarclay <benbarclay@users.noreply.github.com>
2026-03-28 22:21:48 -07:00
Teknium
91b881f931 feat(mattermost): configurable mention behavior — respond without @mention (#3664)
Adds MATTERMOST_REQUIRE_MENTION and MATTERMOST_FREE_RESPONSE_CHANNELS
env vars, matching Discord's existing mention gating pattern.

- MATTERMOST_REQUIRE_MENTION=false: respond to all channel messages
- MATTERMOST_FREE_RESPONSE_CHANNELS=id1,id2: specific channels where
  bot responds without @mention even when require_mention is true
- DMs always respond regardless of mention settings
- @mention is now stripped from message text (clean agent input)

7 new tests for mention gating, free-response channels, DM bypass,
and mention stripping. Updated existing test for mention stripping.

Docs: updated mattermost.md with Mention Behavior section,
environment-variables.md with new vars, config.py with metadata.
2026-03-28 22:17:43 -07:00
15 changed files with 452 additions and 24 deletions

13
.dockerignore Normal file
View File

@@ -0,0 +1,13 @@
# Git
.git
.gitignore
.gitmodules
# Dependencies
node_modules
# CI/CD
.github
# Environment files
.env

61
.github/workflows/docker-publish.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: Docker Build and Publish
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: docker-${{ github.ref }}
cancel-in-progress: true
jobs:
build-and-push:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
load: true
tags: nousresearch/hermes-agent:test
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Test image starts
run: |
docker run --rm \
-v /tmp/hermes-test:/opt/data \
--entrypoint /opt/hermes/docker/entrypoint.sh \
nousresearch/hermes-agent:test --help
- name: Log in to Docker Hub
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push image
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: true
tags: |
nousresearch/hermes-agent:latest
nousresearch/hermes-agent:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

20
Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
FROM debian:13.4
RUN apt-get update
RUN apt-get install -y nodejs npm python3 python3-pip ripgrep ffmpeg gcc python3-dev libffi-dev
COPY . /opt/hermes
WORKDIR /opt/hermes
RUN pip install -e ".[all]" --break-system-packages
RUN npm install
RUN npx playwright install --with-deps chromium
WORKDIR /opt/hermes/scripts/whatsapp-bridge
RUN npm install
WORKDIR /opt/hermes
RUN chmod +x /opt/hermes/docker/entrypoint.sh
ENV HERMES_HOME=/opt/data
VOLUME [ "/opt/data" ]
ENTRYPOINT [ "/opt/hermes/docker/entrypoint.sh" ]

15
docker/SOUL.md Normal file
View File

@@ -0,0 +1,15 @@
# Hermes Agent Persona
<!--
This file defines the agent's personality and tone.
The agent will embody whatever you write here.
Edit this to customize how Hermes communicates with you.
Examples:
- "You are a warm, playful assistant who uses kaomoji occasionally."
- "You are a concise technical expert. No fluff, just facts."
- "You speak like a friendly coworker who happens to know everything."
This file is loaded fresh each message -- no restart needed.
Delete the contents (or this file) to use the default personality.
-->

34
docker/entrypoint.sh Normal file
View File

@@ -0,0 +1,34 @@
#!/bin/bash
# Docker entrypoint: bootstrap config files into the mounted volume, then run hermes.
set -e
HERMES_HOME="/opt/data"
INSTALL_DIR="/opt/hermes"
# Create essential directory structure. Cache and platform directories
# (cache/images, cache/audio, platforms/whatsapp, etc.) are created on
# demand by the application — don't pre-create them here so new installs
# get the consolidated layout from get_hermes_dir().
mkdir -p "$HERMES_HOME"/{cron,sessions,logs,hooks,memories,skills}
# .env
if [ ! -f "$HERMES_HOME/.env" ]; then
cp "$INSTALL_DIR/.env.example" "$HERMES_HOME/.env"
fi
# config.yaml
if [ ! -f "$HERMES_HOME/config.yaml" ]; then
cp "$INSTALL_DIR/cli-config.yaml.example" "$HERMES_HOME/config.yaml"
fi
# SOUL.md
if [ ! -f "$HERMES_HOME/SOUL.md" ]; then
cp "$INSTALL_DIR/docker/SOUL.md" "$HERMES_HOME/SOUL.md"
fi
# Sync bundled skills (manifest-based so user edits are preserved)
if [ -d "$INSTALL_DIR/skills" ]; then
python3 "$INSTALL_DIR/tools/skills_sync.py"
fi
exec hermes "$@"

View File

@@ -1005,7 +1005,7 @@ class BasePlatformAdapter(ABC):
# simultaneous messages. Queue them without interrupting the active run,
# then process them immediately after the current task finishes.
if event.message_type == MessageType.PHOTO:
print(f"[{self.name}] 🖼️ Queuing photo follow-up for session {session_key} without interrupt")
logger.debug("[%s] Queuing photo follow-up for session %s without interrupt", self.name, session_key)
existing = self._pending_messages.get(session_key)
if existing and existing.message_type == MessageType.PHOTO:
existing.media_urls.extend(event.media_urls)
@@ -1020,7 +1020,7 @@ class BasePlatformAdapter(ABC):
return # Don't interrupt now - will run after current task completes
# Default behavior for non-photo follow-ups: interrupt the running agent
print(f"[{self.name}] New message while session {session_key} is active - triggering interrupt")
logger.debug("[%s] New message while session %s is active triggering interrupt", self.name, session_key)
self._pending_messages[session_key] = event
# Signal the interrupt (the processing task checks this)
self._active_sessions[session_key].set()
@@ -1206,9 +1206,9 @@ class BasePlatformAdapter(ABC):
)
if not media_result.success:
print(f"[{self.name}] Failed to send media ({ext}): {media_result.error}")
logger.warning("[%s] Failed to send media (%s): %s", self.name, ext, media_result.error)
except Exception as media_err:
print(f"[{self.name}] Error sending media: {media_err}")
logger.warning("[%s] Error sending media: %s", self.name, media_err)
# Send auto-detected local files as native attachments
for file_path in local_files:
@@ -1240,7 +1240,7 @@ class BasePlatformAdapter(ABC):
# Check if there's a pending message that was queued during our processing
if session_key in self._pending_messages:
pending_event = self._pending_messages.pop(session_key)
print(f"[{self.name}] 📨 Processing queued message from interrupt")
logger.debug("[%s] Processing queued message from interrupt", self.name)
# Clean up current session before processing pending
if session_key in self._active_sessions:
del self._active_sessions[session_key]
@@ -1254,9 +1254,7 @@ class BasePlatformAdapter(ABC):
return # Already cleaned up
except Exception as e:
print(f"[{self.name}] Error handling message: {e}")
import traceback
traceback.print_exc()
logger.error("[%s] Error handling message: %s", self.name, e, exc_info=True)
# Send the error to the user so they aren't left with radio silence
try:
error_type = type(e).__name__

View File

@@ -1429,15 +1429,23 @@ class DiscordAdapter(BasePlatformAdapter):
command_text: str,
followup_msg: str | None = None,
) -> None:
"""Common handler for simple slash commands that dispatch a command string."""
"""Common handler for simple slash commands that dispatch a command string.
Defers the interaction (shows "thinking..."), dispatches the command,
then cleans up the deferred response. If *followup_msg* is provided
the "thinking..." indicator is replaced with that text; otherwise it
is deleted so the channel isn't cluttered.
"""
await interaction.response.defer(ephemeral=True)
event = self._build_slash_event(interaction, command_text)
await self.handle_message(event)
if followup_msg:
try:
await interaction.followup.send(followup_msg, ephemeral=True)
except Exception as e:
logger.debug("Discord followup failed: %s", e)
try:
if followup_msg:
await interaction.edit_original_response(content=followup_msg)
else:
await interaction.delete_original_response()
except Exception as e:
logger.debug("Discord interaction cleanup failed: %s", e)
def _register_slash_commands(self) -> None:
"""Register Discord slash commands on the command tree."""
@@ -1462,9 +1470,7 @@ class DiscordAdapter(BasePlatformAdapter):
@tree.command(name="reasoning", description="Show or change reasoning effort")
@discord.app_commands.describe(effort="Reasoning effort: xhigh, high, medium, low, minimal, or none.")
async def slash_reasoning(interaction: discord.Interaction, effort: str = ""):
await interaction.response.defer(ephemeral=True)
event = self._build_slash_event(interaction, f"/reasoning {effort}".strip())
await self.handle_message(event)
await self._run_simple_slash(interaction, f"/reasoning {effort}".strip())
@tree.command(name="personality", description="Set a personality")
@discord.app_commands.describe(name="Personality name. Leave empty to list available.")
@@ -1537,9 +1543,7 @@ class DiscordAdapter(BasePlatformAdapter):
discord.app_commands.Choice(name="status — show current mode", value="status"),
])
async def slash_voice(interaction: discord.Interaction, mode: str = ""):
await interaction.response.defer(ephemeral=True)
event = self._build_slash_event(interaction, f"/voice {mode}".strip())
await self.handle_message(event)
await self._run_simple_slash(interaction, f"/voice {mode}".strip())
@tree.command(name="update", description="Update Hermes Agent to the latest version")
async def slash_update(interaction: discord.Interaction):

View File

@@ -603,9 +603,19 @@ class MattermostAdapter(BasePlatformAdapter):
# For DMs, user_id is sufficient. For channels, check for @mention.
message_text = post.get("message", "")
# Mention-only mode: skip channel messages that don't @mention the bot.
# DMs (type "D") are always processed.
# Mention-gating for non-DM channels.
# Config (env vars):
# MATTERMOST_REQUIRE_MENTION: Require @mention in channels (default: true)
# MATTERMOST_FREE_RESPONSE_CHANNELS: Channel IDs where bot responds without mention
if channel_type_raw != "D":
require_mention = os.getenv(
"MATTERMOST_REQUIRE_MENTION", "true"
).lower() not in ("false", "0", "no")
free_channels_raw = os.getenv("MATTERMOST_FREE_RESPONSE_CHANNELS", "")
free_channels = {ch.strip() for ch in free_channels_raw.split(",") if ch.strip()}
is_free_channel = channel_id in free_channels
mention_patterns = [
f"@{self._bot_username}",
f"@{self._bot_user_id}",
@@ -614,13 +624,21 @@ class MattermostAdapter(BasePlatformAdapter):
pattern.lower() in message_text.lower()
for pattern in mention_patterns
)
if not has_mention:
if require_mention and not is_free_channel and not has_mention:
logger.debug(
"Mattermost: skipping non-DM message without @mention (channel=%s)",
channel_id,
)
return
# Strip @mention from the message text so the agent sees clean input.
if has_mention:
for pattern in mention_patterns:
message_text = re.sub(
re.escape(pattern), "", message_text, flags=re.IGNORECASE
).strip()
# Resolve sender info.
sender_id = post.get("user_id", "")
sender_name = data.get("sender_name", "").lstrip("@") or sender_id

View File

@@ -817,6 +817,20 @@ OPTIONAL_ENV_VARS = {
"password": False,
"category": "messaging",
},
"MATTERMOST_REQUIRE_MENTION": {
"description": "Require @mention in Mattermost channels (default: true). Set to false to respond to all messages.",
"prompt": "Require @mention in channels",
"url": None,
"password": False,
"category": "messaging",
},
"MATTERMOST_FREE_RESPONSE_CHANNELS": {
"description": "Comma-separated Mattermost channel IDs where bot responds without @mention",
"prompt": "Free-response channel IDs (comma-separated)",
"url": None,
"password": False,
"category": "messaging",
},
"MATRIX_HOMESERVER": {
"description": "Matrix homeserver URL (e.g. https://matrix.example.org)",
"prompt": "Matrix homeserver URL",

View File

@@ -1,5 +1,6 @@
"""Tests for Mattermost platform adapter."""
import json
import os
import time
import pytest
from unittest.mock import MagicMock, patch, AsyncMock
@@ -269,6 +270,7 @@ class TestMattermostWebSocketParsing:
def setup_method(self):
self.adapter = _make_adapter()
self.adapter._bot_user_id = "bot_user_id"
self.adapter._bot_username = "hermes-bot"
# Mock handle_message to capture the MessageEvent without processing
self.adapter.handle_message = AsyncMock()
@@ -293,7 +295,8 @@ class TestMattermostWebSocketParsing:
await self.adapter._handle_ws_event(event)
assert self.adapter.handle_message.called
msg_event = self.adapter.handle_message.call_args[0][0]
assert msg_event.text == "@bot_user_id Hello from Matrix!"
# @mention is stripped from the message text
assert msg_event.text == "Hello from Matrix!"
assert msg_event.message_id == "post_abc"
@pytest.mark.asyncio
@@ -410,6 +413,87 @@ class TestMattermostWebSocketParsing:
assert not self.adapter.handle_message.called
# ---------------------------------------------------------------------------
# Mention behavior (require_mention + free_response_channels)
# ---------------------------------------------------------------------------
class TestMattermostMentionBehavior:
def setup_method(self):
self.adapter = _make_adapter()
self.adapter._bot_user_id = "bot_user_id"
self.adapter._bot_username = "hermes-bot"
self.adapter.handle_message = AsyncMock()
def _make_event(self, message, channel_type="O", channel_id="chan_456"):
post_data = {
"id": "post_mention",
"user_id": "user_123",
"channel_id": channel_id,
"message": message,
}
return {
"event": "posted",
"data": {
"post": json.dumps(post_data),
"channel_type": channel_type,
"sender_name": "@alice",
},
}
@pytest.mark.asyncio
async def test_require_mention_true_skips_without_mention(self):
"""Default: messages without @mention in channels are skipped."""
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("MATTERMOST_REQUIRE_MENTION", None)
os.environ.pop("MATTERMOST_FREE_RESPONSE_CHANNELS", None)
await self.adapter._handle_ws_event(self._make_event("hello"))
assert not self.adapter.handle_message.called
@pytest.mark.asyncio
async def test_require_mention_false_responds_to_all(self):
"""MATTERMOST_REQUIRE_MENTION=false: respond to all channel messages."""
with patch.dict(os.environ, {"MATTERMOST_REQUIRE_MENTION": "false"}):
await self.adapter._handle_ws_event(self._make_event("hello"))
assert self.adapter.handle_message.called
@pytest.mark.asyncio
async def test_free_response_channel_responds_without_mention(self):
"""Messages in free-response channels don't need @mention."""
with patch.dict(os.environ, {"MATTERMOST_FREE_RESPONSE_CHANNELS": "chan_456,chan_789"}):
os.environ.pop("MATTERMOST_REQUIRE_MENTION", None)
await self.adapter._handle_ws_event(self._make_event("hello", channel_id="chan_456"))
assert self.adapter.handle_message.called
@pytest.mark.asyncio
async def test_non_free_channel_still_requires_mention(self):
"""Channels NOT in free-response list still require @mention."""
with patch.dict(os.environ, {"MATTERMOST_FREE_RESPONSE_CHANNELS": "chan_789"}):
os.environ.pop("MATTERMOST_REQUIRE_MENTION", None)
await self.adapter._handle_ws_event(self._make_event("hello", channel_id="chan_456"))
assert not self.adapter.handle_message.called
@pytest.mark.asyncio
async def test_dm_always_responds(self):
"""DMs (channel_type=D) always respond regardless of mention settings."""
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("MATTERMOST_REQUIRE_MENTION", None)
await self.adapter._handle_ws_event(self._make_event("hello", channel_type="D"))
assert self.adapter.handle_message.called
@pytest.mark.asyncio
async def test_mention_stripped_from_text(self):
"""@mention is stripped from message text."""
with patch.dict(os.environ, {}, clear=False):
os.environ.pop("MATTERMOST_REQUIRE_MENTION", None)
await self.adapter._handle_ws_event(
self._make_event("@hermes-bot what is 2+2")
)
assert self.adapter.handle_message.called
msg = self.adapter.handle_message.call_args[0][0]
assert "@hermes-bot" not in msg.text
assert "2+2" in msg.text
# ---------------------------------------------------------------------------
# File upload (send_image)
# ---------------------------------------------------------------------------

View File

@@ -200,6 +200,8 @@ For native Anthropic auth, Hermes prefers Claude Code's own credential files whe
| `MATTERMOST_TOKEN` | Bot token or personal access token for Mattermost |
| `MATTERMOST_ALLOWED_USERS` | Comma-separated Mattermost user IDs allowed to message the bot |
| `MATTERMOST_HOME_CHANNEL` | Channel ID for proactive message delivery (cron, notifications) |
| `MATTERMOST_REQUIRE_MENTION` | Require `@mention` in channels (default: `true`). Set to `false` to respond to all messages. |
| `MATTERMOST_FREE_RESPONSE_CHANNELS` | Comma-separated channel IDs where bot responds without `@mention` |
| `MATTERMOST_REPLY_MODE` | Reply style: `thread` (threaded replies) or `off` (flat messages, default) |
| `MATRIX_HOMESERVER` | Matrix homeserver URL (e.g. `https://matrix.org`) |
| `MATRIX_ACCESS_TOKEN` | Matrix access token for bot authentication |

View File

@@ -258,6 +258,14 @@ Skills for academic research, paper discovery, literature review, domain reconna
| `ml-paper-writing` | Write publication-ready ML/AI papers for NeurIPS, ICML, ICLR, ACL, AAAI, COLM. Use when drafting papers from research repos, structuring arguments, verifying citations, or preparing camera-ready submissions. Includes LaTeX templates, reviewer guidelines, and citation verificatio… | `research/ml-paper-writing` |
| `polymarket` | Query Polymarket prediction market data — search markets, get prices, orderbooks, and price history. Read-only via public REST APIs, no API key needed. | `research/polymarket` |
## red-teaming
Skills for LLM red-teaming, jailbreaking, and safety filter bypass research.
| Skill | Description | Path |
|-------|-------------|------|
| `godmode` | Jailbreak API-served LLMs using G0DM0D3 techniques — Parseltongue input obfuscation (33 techniques), GODMODE CLASSIC system prompt templates, ULTRAPLINIAN multi-model racing, encoding escalation, and Hermes-native prefill/system prompt integration. Works on any model accessible via API including closed-source models. | `red-teaming/godmode` |
## smart-home
Skills for controlling smart home devices — lights, switches, sensors, and home automation systems.
@@ -285,3 +293,84 @@ Skills for interacting with social platforms — posting, reading, monitoring, a
| `systematic-debugging` | Use when encountering any bug, test failure, or unexpected behavior. 4-phase root cause investigation — NO fixes without understanding the problem first. | `software-development/systematic-debugging` |
| `test-driven-development` | Use when implementing any feature or bugfix, before writing implementation code. Enforces RED-GREEN-REFACTOR cycle with test-first approach. | `software-development/test-driven-development` |
| `writing-plans` | Use when you have a spec or requirements for a multi-step task. Creates comprehensive implementation plans with bite-sized tasks, exact file paths, and complete code examples. | `software-development/writing-plans` |
---
# Optional Skills
Optional skills ship with the repository under `optional-skills/` but are **not active by default**. They cover heavier or niche use cases. Install them with:
```bash
hermes skills install official/<category>/<skill>
```
## autonomous-ai-agents
| Skill | Description | Path |
|-------|-------------|------|
| `blackbox` | Delegate coding tasks to Blackbox AI CLI agent. Multi-model agent with built-in judge that runs tasks through multiple LLMs and picks the best result. Requires the blackbox CLI and a Blackbox AI API key. | `autonomous-ai-agents/blackbox` |
## blockchain
| Skill | Description | Path |
|-------|-------------|------|
| `base` | Query Base (Ethereum L2) blockchain data with USD pricing — wallet balances, token info, transaction details, gas analysis, contract inspection, whale detection, and live network stats. Uses Base RPC + CoinGecko. No API key required. | `blockchain/base` |
| `solana` | Query Solana blockchain data with USD pricing — wallet balances, token portfolios with values, transaction details, NFTs, whale detection, and live network stats. Uses Solana RPC + CoinGecko. No API key required. | `blockchain/solana` |
## creative
| Skill | Description | Path |
|-------|-------------|------|
| `blender-mcp` | Control Blender directly from Hermes via socket connection to the blender-mcp addon. Create 3D objects, materials, animations, and run arbitrary Blender Python (bpy) code. | `creative/blender-mcp` |
| `meme-generation` | Generate real meme images by picking a template and overlaying text with Pillow. Produces actual .png meme files. | `creative/meme-generation` |
## devops
| Skill | Description | Path |
|-------|-------------|------|
| `docker-management` | Manage Docker containers, images, volumes, networks, and Compose stacks — lifecycle ops, debugging, cleanup, and Dockerfile optimization. | `devops/docker-management` |
## email
| Skill | Description | Path |
|-------|-------------|------|
| `agentmail` | Give the agent its own dedicated email inbox via AgentMail. Send, receive, and manage email autonomously using agent-owned email addresses (e.g. hermes-agent@agentmail.to). | `email/agentmail` |
## health
| Skill | Description | Path |
|-------|-------------|------|
| `neuroskill-bci` | Connect to a running NeuroSkill instance and incorporate the user's real-time cognitive and emotional state (focus, relaxation, mood, cognitive load, drowsiness, heart rate, HRV, sleep staging, and 40+ derived EXG scores) into responses. Requires a BCI wearable (Muse 2/S or OpenBCI) and the NeuroSkill desktop app. | `health/neuroskill-bci` |
## mcp
| Skill | Description | Path |
|-------|-------------|------|
| `fastmcp` | Build, test, inspect, install, and deploy MCP servers with FastMCP in Python. Use when creating a new MCP server, wrapping an API or database as MCP tools, exposing resources or prompts, or preparing a FastMCP server for HTTP deployment. | `mcp/fastmcp` |
## migration
| Skill | Description | Path |
|-------|-------------|------|
| `openclaw-migration` | Migrate a user's OpenClaw customization footprint into Hermes Agent. Imports Hermes-compatible memories, SOUL.md, command allowlists, user skills, and selected workspace assets from ~/.openclaw, then reports what could not be migrated and why. | `migration/openclaw-migration` |
## productivity
| Skill | Description | Path |
|-------|-------------|------|
| `telephony` | Give Hermes phone capabilities — provision and persist a Twilio number, send and receive SMS/MMS, make direct calls, and place AI-driven outbound calls through Bland.ai or Vapi. | `productivity/telephony` |
## research
| Skill | Description | Path |
|-------|-------------|------|
| `bioinformatics` | Gateway to 400+ bioinformatics skills from bioSkills and ClawBio. Covers genomics, transcriptomics, single-cell, variant calling, pharmacogenomics, metagenomics, structural biology, and more. | `research/bioinformatics` |
| `qmd` | Search personal knowledge bases, notes, docs, and meeting transcripts locally using qmd — a hybrid retrieval engine with BM25, vector search, and LLM reranking. Supports CLI and MCP integration. | `research/qmd` |
## security
| Skill | Description | Path |
|-------|-------------|------|
| `1password` | Set up and use 1Password CLI (op). Use when installing the CLI, enabling desktop app integration, signing in, and reading/injecting secrets for commands. | `security/1password` |
| `oss-forensics` | Supply chain investigation, evidence recovery, and forensic analysis for GitHub repositories. Covers deleted commit recovery, force-push detection, IOC extraction, multi-source evidence collection, and structured forensic reporting. | `security/oss-forensics` |
| `sherlock` | OSINT username search across 400+ social networks. Hunt down social media accounts by username. | `security/sherlock` |

View File

@@ -0,0 +1,56 @@
# Hermes Agent — Docker
Want to run Hermes Agent, but without installing packages on your host? This'll sort you out.
This will let you run the agent in a container, with the most relevant modes outlined below.
The container stores all user data (config, API keys, sessions, skills, memories) in a single directory mounted from the host at `/opt/data`. The image itself is stateless and can be upgraded by pulling a new version without losing any configuration.
## Quick start
If this is your first time running Hermes Agent, create a data directory on the host and start the container interactively to run the setup wizard:
```sh
mkdir -p ~/.hermes
docker run -it --rm \
-v ~/.hermes:/opt/data \
nousresearch/hermes-agent
```
This drops you into the setup wizard, which will prompt you for your API keys and write them to `~/.hermes/.env`. You only need to do this once. It is highly recommended to set up a chat system for the gateway to work with at this point.
## Running in gateway mode
Once configured, run the container in the background as a persistent gateway (Telegram, Discord, Slack, WhatsApp, etc.):
```sh
docker run -d \
--name hermes \
--restart unless-stopped \
-v ~/.hermes:/opt/data \
nousresearch/hermes-agent gateway run
```
## Running interactively (CLI chat)
To open an interactive chat session against a running data directory:
```sh
docker run -it --rm \
-v ~/.hermes:/opt/data \
nousresearch/hermes-agent
```
## Upgrading
Pull the latest image and recreate the container. Your data directory is untouched.
```sh
docker pull nousresearch/hermes-agent:latest
docker rm -f hermes
docker run -d \
--name hermes \
--restart unless-stopped \
-v ~/.hermes:/opt/data \
nousresearch/hermes-agent
```

View File

@@ -149,6 +149,12 @@ MATTERMOST_ALLOWED_USERS=3uo8dkh1p7g1mfk49ear5fzs5c
# Optional: reply mode (thread or off, default: off)
# MATTERMOST_REPLY_MODE=thread
# Optional: respond without @mention (default: true = require mention)
# MATTERMOST_REQUIRE_MENTION=false
# Optional: channels where bot responds without @mention (comma-separated channel IDs)
# MATTERMOST_FREE_RESPONSE_CHANNELS=channel_id_1,channel_id_2
```
Optional behavior settings in `~/.hermes/config.yaml`:
@@ -206,6 +212,19 @@ Set it in your `~/.hermes/.env`:
MATTERMOST_REPLY_MODE=thread
```
## Mention Behavior
By default, the bot only responds in channels when `@mentioned`. You can change this:
| Variable | Default | Description |
|----------|---------|-------------|
| `MATTERMOST_REQUIRE_MENTION` | `true` | Set to `false` to respond to all messages in channels (DMs always work). |
| `MATTERMOST_FREE_RESPONSE_CHANNELS` | _(none)_ | Comma-separated channel IDs where the bot responds without `@mention`, even when require_mention is true. |
To find a channel ID in Mattermost: open the channel, click the channel name header, and look for the ID in the URL or channel details.
When the bot is `@mentioned`, the mention is automatically stripped from the message before processing.
## Troubleshooting
### Bot is not responding to messages

View File

@@ -37,6 +37,7 @@ const sidebars: SidebarsConfig = {
'user-guide/configuration',
'user-guide/sessions',
'user-guide/security',
'user-guide/docker',
{
type: 'category',
label: 'Messaging Gateway',