Merge branch 'main' into rewbs/tool-use-charge-to-subscription

This commit is contained in:
Robin Fernandes
2026-03-31 08:48:54 +09:00
269 changed files with 33678 additions and 2273 deletions

View File

@@ -92,8 +92,13 @@ You need at least one way to connect to an LLM. Use `hermes model` to switch pro
| **Kilo Code** | `KILOCODE_API_KEY` in `~/.hermes/.env` (provider: `kilocode`) |
| **OpenCode Zen** | `OPENCODE_ZEN_API_KEY` in `~/.hermes/.env` (provider: `opencode-zen`) |
| **OpenCode Go** | `OPENCODE_GO_API_KEY` in `~/.hermes/.env` (provider: `opencode-go`) |
| **Hugging Face** | `HF_TOKEN` in `~/.hermes/.env` (provider: `huggingface`, aliases: `hf`) |
| **Custom Endpoint** | `hermes model` (saved in `config.yaml`) or `OPENAI_BASE_URL` + `OPENAI_API_KEY` in `~/.hermes/.env` |
:::tip Model key alias
In the `model:` config section, you can use either `default:` or `model:` as the key name for your model ID. Both `model: { default: my-model }` and `model: { model: my-model }` work identically.
:::
:::info Codex Note
The OpenAI Codex provider authenticates via device code (open a URL, enter a code). Hermes stores the resulting credentials in its own auth store under `~/.hermes/auth.json` and can import existing Codex CLI credentials from `~/.codex/auth.json` when present. No Codex CLI installation is required.
:::
@@ -211,7 +216,7 @@ hermes chat --provider minimax-cn --model MiniMax-M2.7
# Requires: MINIMAX_CN_API_KEY in ~/.hermes/.env
# Alibaba Cloud / DashScope (Qwen models)
hermes chat --provider alibaba --model qwen-plus
hermes chat --provider alibaba --model qwen3.5-plus
# Requires: DASHSCOPE_API_KEY in ~/.hermes/.env
```
@@ -224,6 +229,32 @@ model:
Base URLs can be overridden with `GLM_BASE_URL`, `KIMI_BASE_URL`, `MINIMAX_BASE_URL`, `MINIMAX_CN_BASE_URL`, or `DASHSCOPE_BASE_URL` environment variables.
### Hugging Face Inference Providers
[Hugging Face Inference Providers](https://huggingface.co/docs/inference-providers) routes to 20+ open models through a unified OpenAI-compatible endpoint (`router.huggingface.co/v1`). Requests are automatically routed to the fastest available backend (Groq, Together, SambaNova, etc.) with automatic failover.
```bash
# Use any available model
hermes chat --provider huggingface --model Qwen/Qwen3-235B-A22B-Thinking-2507
# Requires: HF_TOKEN in ~/.hermes/.env
# Short alias
hermes chat --provider hf --model deepseek-ai/DeepSeek-V3.2
```
Or set it permanently in `config.yaml`:
```yaml
model:
provider: "huggingface"
default: "Qwen/Qwen3-235B-A22B-Thinking-2507"
```
Get your token at [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) — make sure to enable the "Make calls to Inference Providers" permission. Free tier included ($0.10/month credit, no markup on provider rates).
You can append routing suffixes to model names: `:fastest` (default), `:cheapest`, or `:provider_name` to force a specific backend.
The base URL can be overridden with `HF_BASE_URL`.
## Custom & Self-Hosted LLM Providers
Hermes Agent works with **any OpenAI-compatible API endpoint**. If a server implements `/v1/chat/completions`, you can point Hermes at it. This means you can use local models, GPU inference servers, multi-provider routers, or any third-party API.
@@ -627,7 +658,7 @@ fallback_model:
When activated, the fallback swaps the model and provider mid-session without losing your conversation. It fires **at most once** per session.
Supported providers: `openrouter`, `nous`, `openai-codex`, `copilot`, `anthropic`, `zai`, `kimi-coding`, `minimax`, `minimax-cn`, `custom`.
Supported providers: `openrouter`, `nous`, `openai-codex`, `copilot`, `anthropic`, `huggingface`, `zai`, `kimi-coding`, `minimax`, `minimax-cn`, `custom`.
:::tip
Fallback is configured exclusively through `config.yaml` — there are no environment variables for it. For full details on when it triggers, supported providers, and how it interacts with auxiliary tasks and delegation, see [Fallback Providers](/docs/user-guide/features/fallback-providers).
@@ -998,6 +1029,7 @@ auxiliary:
model: "" # e.g. "google/gemini-2.5-flash"
base_url: ""
api_key: ""
timeout: 30 # seconds
# Dangerous command approval classifier
approval:
@@ -1005,8 +1037,17 @@ auxiliary:
model: ""
base_url: ""
api_key: ""
timeout: 30 # seconds
# Context compression timeout (separate from compression.* config)
compression:
timeout: 120 # seconds — compression summarizes long conversations, needs more time
```
:::tip
Each auxiliary task has a configurable `timeout` (in seconds). Defaults: vision 30s, web_extract 30s, approval 30s, compression 120s. Increase these if you use slow local models for auxiliary tasks.
:::
:::info
Context compression has its own top-level `compression:` block with `summary_provider`, `summary_model`, and `summary_base_url` — see [Context Compression](#context-compression) above. The fallback model uses a `fallback_model:` block — see [Fallback Model](#fallback-model) above. All three follow the same provider/model/base_url pattern.
:::
@@ -1138,6 +1179,24 @@ You can also change the reasoning effort at runtime with the `/reasoning` comman
/reasoning hide # Hide model thinking
```
## Tool-Use Enforcement
Some models (especially GPT-family) occasionally describe intended actions as text instead of making tool calls. Tool-use enforcement injects guidance that steers the model back to actually calling tools.
```yaml
agent:
tool_use_enforcement: "auto" # "auto" | true | false | ["model-substring", ...]
```
| Value | Behavior |
|-------|----------|
| `"auto"` (default) | Enabled for GPT models (`gpt-`, `openai/gpt-`) and disabled for all others. |
| `true` | Always enabled for all models. |
| `false` | Always disabled. |
| `["gpt-", "o1-", "custom-model"]` | Enabled only for models whose name contains one of the listed substrings. |
When enabled, the system prompt includes guidance reminding the model to make actual tool calls rather than describing what it would do. This is transparent to the user and has no effect on models that already use tools reliably.
## TTS Configuration
```yaml

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

@@ -154,7 +154,7 @@ Lists `hermes-agent` as an available model. Required by most frontends for model
### GET /health
Health check. Returns `{"status": "ok"}`.
Health check. Returns `{"status": "ok"}`. Also available at **GET /v1/health** for OpenAI-compatible clients that expect the `/v1/` prefix.
## System Prompt Handling
@@ -199,6 +199,12 @@ The default bind address (`127.0.0.1`) is for local-only use. Browser access is
# config.yaml support coming in a future release.
```
## Security Headers
All responses include security headers:
- `X-Content-Type-Options: nosniff` — prevents MIME type sniffing
- `Referrer-Policy: no-referrer` — prevents referrer leakage
## CORS
The API server does **not** enable browser CORS by default.
@@ -209,6 +215,11 @@ For direct browser access, set an explicit allowlist:
API_SERVER_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
```
When CORS is enabled:
- **Preflight responses** include `Access-Control-Max-Age: 600` (10 minute cache)
- **SSE streaming responses** include CORS headers so browser EventSource clients work correctly
- **`Idempotency-Key`** is an allowed request header — clients can send it for deduplication (responses are cached by key for 5 minutes)
Most documented frontends such as Open WebUI connect server-to-server and do not need CORS at all.
## Compatible Frontends

View File

@@ -44,6 +44,8 @@ Both `provider` and `model` are **required**. If either is missing, the fallback
| MiniMax | `minimax` | `MINIMAX_API_KEY` |
| MiniMax (China) | `minimax-cn` | `MINIMAX_CN_API_KEY` |
| Kilo Code | `kilocode` | `KILOCODE_API_KEY` |
| Alibaba / DashScope | `alibaba` | `DASHSCOPE_API_KEY` |
| Hugging Face | `huggingface` | `HF_TOKEN` |
| Custom endpoint | `custom` | `base_url` + `api_key_env` (see below) |
### Custom Endpoint Fallback
@@ -161,7 +163,7 @@ When a task's provider is set to `"auto"` (the default), Hermes tries providers
```text
OpenRouter → Nous Portal → Custom endpoint → Codex OAuth →
API-key providers (z.ai, Kimi, MiniMax, Anthropic) → give up
API-key providers (z.ai, Kimi, MiniMax, Hugging Face, Anthropic) → give up
```
**For vision tasks:**

View File

@@ -88,6 +88,26 @@ Handlers registered for `command:*` fire for any `command:` event (`command:mode
### Examples
#### Boot Checklist (BOOT.md) — Built-in
The gateway ships with a built-in `boot-md` hook that looks for `~/.hermes/BOOT.md` on every startup. If the file exists, the agent runs its instructions in a background session. No installation needed — just create the file.
**Create `~/.hermes/BOOT.md`:**
```markdown
# Startup Checklist
1. Check if any cron jobs failed overnight — run `hermes cron list`
2. Send a message to Discord #general saying "Gateway restarted, all systems go"
3. Check if /opt/app/deploy.log has any errors from the last 24 hours
```
The agent runs these instructions in a background thread so it doesn't block gateway startup. If nothing needs attention, the agent replies with `[SILENT]` and no message is delivered.
:::tip
No BOOT.md? The hook silently skips — zero overhead. Create the file whenever you need startup automation, delete it when you don't.
:::
#### Telegram Alert on Long Tasks
Send yourself a message when the agent takes more than 10 steps:
@@ -209,10 +229,10 @@ def register(ctx):
|------|-----------|-------------------|
| `pre_tool_call` | Before any tool executes | `tool_name`, `args`, `task_id` |
| `post_tool_call` | After any tool returns | `tool_name`, `args`, `result`, `task_id` |
| `pre_llm_call` | Before LLM API request | *(planned — not yet wired)* |
| `post_llm_call` | After LLM API response | *(planned — not yet wired)* |
| `on_session_start` | Session begins | *(planned — not yet wired)* |
| `on_session_end` | Session ends | *(planned — not yet wired)* |
| `pre_llm_call` | Before LLM API request | `session_id`, `user_message`, `conversation_history`, `is_first_turn`, `model`, `platform` |
| `post_llm_call` | After LLM API response | `session_id`, `user_message`, `assistant_response`, `conversation_history`, `model`, `platform` |
| `on_session_start` | Session begins | `session_id`, `model`, `platform` |
| `on_session_end` | Session ends | `session_id`, `completed`, `interrupted`, `model`, `platform` |
Callbacks receive keyword arguments matching the columns above:

View File

@@ -403,6 +403,105 @@ Because Hermes now only registers those wrappers when both are true:
This is intentional and keeps the tool list honest.
## Running Hermes as an MCP server
In addition to connecting **to** MCP servers, Hermes can also **be** an MCP server. This lets other MCP-capable agents (Claude Code, Cursor, Codex, or any MCP client) use Hermes's messaging capabilities — list conversations, read message history, and send messages across all your connected platforms.
### When to use this
- You want Claude Code, Cursor, or another coding agent to send and read Telegram/Discord/Slack messages through Hermes
- You want a single MCP server that bridges to all of Hermes's connected messaging platforms at once
- You already have a running Hermes gateway with connected platforms
### Quick start
```bash
hermes mcp serve
```
This starts a stdio MCP server. The MCP client (not you) manages the process lifecycle.
### MCP client configuration
Add Hermes to your MCP client config. For example, in Claude Code's `~/.claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"hermes": {
"command": "hermes",
"args": ["mcp", "serve"]
}
}
}
```
Or if you installed Hermes in a specific location:
```json
{
"mcpServers": {
"hermes": {
"command": "/home/user/.hermes/hermes-agent/venv/bin/hermes",
"args": ["mcp", "serve"]
}
}
}
```
### Available tools
The MCP server exposes 10 tools, matching OpenClaw's channel bridge surface plus a Hermes-specific channel browser:
| Tool | Description |
|------|-------------|
| `conversations_list` | List active messaging conversations. Filter by platform or search by name. |
| `conversation_get` | Get detailed info about one conversation by session key. |
| `messages_read` | Read recent message history for a conversation. |
| `attachments_fetch` | Extract non-text attachments (images, media) from a specific message. |
| `events_poll` | Poll for new conversation events since a cursor position. |
| `events_wait` | Long-poll / block until the next event arrives (near-real-time). |
| `messages_send` | Send a message through a platform (e.g. `telegram:123456`, `discord:#general`). |
| `channels_list` | List available messaging targets across all platforms. |
| `permissions_list_open` | List pending approval requests observed during this bridge session. |
| `permissions_respond` | Allow or deny a pending approval request. |
### Event system
The MCP server includes a live event bridge that polls Hermes's session database for new messages. This gives MCP clients near-real-time awareness of incoming conversations:
```
# Poll for new events (non-blocking)
events_poll(after_cursor=0)
# Wait for next event (blocks up to timeout)
events_wait(after_cursor=42, timeout_ms=30000)
```
Event types: `message`, `approval_requested`, `approval_resolved`
The event queue is in-memory and starts when the bridge connects. Older messages are available through `messages_read`.
### Options
```bash
hermes mcp serve # Normal mode
hermes mcp serve --verbose # Debug logging on stderr
```
### How it works
The MCP server reads conversation data directly from Hermes's session store (`~/.hermes/sessions/sessions.json` and the SQLite database). A background thread polls the database for new messages and maintains an in-memory event queue. For sending messages, it uses the same `send_message` infrastructure as the Hermes agent itself.
The gateway does NOT need to be running for read operations (listing conversations, reading history, polling events). It DOES need to be running for send operations, since the platform adapters need active connections.
### Current limits
- Stdio transport only (no HTTP MCP transport yet)
- Event polling at ~200ms intervals via mtime-optimized DB polling (skips work when files are unchanged)
- No `claude/channel` push notification protocol yet
- Text-only sends (no media/attachment sending through `messages_send`)
## Related docs
- [Use MCP with Hermes](/docs/guides/use-mcp-with-hermes)

View File

@@ -52,10 +52,10 @@ Plugins can register callbacks for these lifecycle events. See the **[Event Hook
|------|-----------|
| `pre_tool_call` | Before any tool executes |
| `post_tool_call` | After any tool returns |
| `pre_llm_call` | Before LLM API request *(planned)* |
| `post_llm_call` | After LLM API response *(planned)* |
| `on_session_start` | Session begins *(planned)* |
| `on_session_end` | Session ends *(planned)* |
| `pre_llm_call` | Once per turn, before the LLM loop — can return `{"context": "..."}` to inject into the system prompt |
| `post_llm_call` | Once per turn, after the LLM loop completes |
| `on_session_start` | New session created (first turn only) |
| `on_session_end` | End of every `run_conversation` call |
## Slash commands
@@ -87,9 +87,26 @@ The handler receives the argument string (everything after `/greet`) and returns
## Managing plugins
```
/plugins # list loaded plugins in a session
hermes config set display.show_cost true # show cost in status bar
```bash
hermes plugins # interactive toggle UI — enable/disable with checkboxes
hermes plugins list # table view with enabled/disabled status
hermes plugins install user/repo # install from Git
hermes plugins update my-plugin # pull latest
hermes plugins remove my-plugin # uninstall
hermes plugins enable my-plugin # re-enable a disabled plugin
hermes plugins disable my-plugin # disable without removing
```
Running `hermes plugins` with no arguments launches an interactive curses checklist (same UI as `hermes tools`) where you can toggle plugins on/off with arrow keys and space.
Disabled plugins remain installed but are skipped during loading. The disabled list is stored in `config.yaml` under `plugins.disabled`:
```yaml
plugins:
disabled:
- my-noisy-plugin
```
In a running session, `/plugins` shows which plugins are currently loaded.
See the **[full guide](/docs/guides/build-a-hermes-plugin)** for handler contracts, schema format, hook behavior, error handling, and common mistakes.

View File

@@ -8,7 +8,9 @@ description: "On-demand knowledge documents — progressive disclosure, agent-ma
Skills are on-demand knowledge documents the agent can load when needed. They follow a **progressive disclosure** pattern to minimize token usage and are compatible with the [agentskills.io](https://agentskills.io/specification) open standard.
All skills live in **`~/.hermes/skills/`** — a single directory that serves as the source of truth. On fresh install, bundled skills are copied from the repo. Hub-installed and agent-created skills also go here. The agent can modify or delete any skill.
All skills live in **`~/.hermes/skills/`** — the primary directory and source of truth. On fresh install, bundled skills are copied from the repo. Hub-installed and agent-created skills also go here. The agent can modify or delete any skill.
You can also point Hermes at **external skill directories** — additional folders scanned alongside the local one. See [External Skill Directories](#external-skill-directories) below.
See also:
@@ -164,6 +166,47 @@ Once set, declared env vars are **automatically passed through** to `execute_cod
└── .bundled_manifest # Tracks seeded bundled skills
```
## External Skill Directories
If you maintain skills outside of Hermes — for example, a shared `~/.agents/skills/` directory used by multiple AI tools — you can tell Hermes to scan those directories too.
Add `external_dirs` under the `skills` section in `~/.hermes/config.yaml`:
```yaml
skills:
external_dirs:
- ~/.agents/skills
- /home/shared/team-skills
- ${SKILLS_REPO}/skills
```
Paths support `~` expansion and `${VAR}` environment variable substitution.
### How it works
- **Read-only**: External dirs are only scanned for skill discovery. When the agent creates or edits a skill, it always writes to `~/.hermes/skills/`.
- **Local precedence**: If the same skill name exists in both the local dir and an external dir, the local version wins.
- **Full integration**: External skills appear in the system prompt index, `skills_list`, `skill_view`, and as `/skill-name` slash commands — no different from local skills.
- **Non-existent paths are silently skipped**: If a configured directory doesn't exist, Hermes ignores it without errors. Useful for optional shared directories that may not be present on every machine.
### Example
```text
~/.hermes/skills/ # Local (primary, read-write)
├── devops/deploy-k8s/
│ └── SKILL.md
└── mlops/axolotl/
└── SKILL.md
~/.agents/skills/ # External (read-only, shared)
├── my-custom-workflow/
│ └── SKILL.md
└── team-conventions/
└── SKILL.md
```
All four skills appear in your skill index. If you create a new skill called `my-custom-workflow` locally, it shadows the external version.
## Agent-Managed Skills (skill_manage tool)
The agent can create, update, and delete its own skills via the `skill_manage` tool. This is the agent's **procedural memory** — when it figures out a non-trivial workflow, it saves the approach as a skill for future reuse.
@@ -277,9 +320,12 @@ hermes skills install well-known:https://mintlify.com/docs/.well-known/skills/mi
Hermes can install directly from GitHub repositories and GitHub-based taps. This is useful when you already know the repo/path or want to add your own custom source repo.
- OpenAI skills: [openai/skills](https://github.com/openai/skills)
- Anthropic skills: [anthropics/skills](https://github.com/anthropics/skills)
- Example community tap source: [VoltAgent/awesome-agent-skills](https://github.com/VoltAgent/awesome-agent-skills)
Default taps (browsable without any setup):
- [openai/skills](https://github.com/openai/skills)
- [anthropics/skills](https://github.com/anthropics/skills)
- [VoltAgent/awesome-agent-skills](https://github.com/VoltAgent/awesome-agent-skills)
- [garrytan/gstack](https://github.com/garrytan/gstack)
- Example:
```bash

View File

@@ -104,7 +104,7 @@ hermes config set terminal.singularity_image ~/python.sif
### Modal (Serverless Cloud)
```bash
uv pip install "swe-rex[modal]"
uv pip install modal
modal setup
hermes config set terminal.backend modal
```

View File

@@ -95,13 +95,17 @@ You'll land on the **General Information** page. Note the **Application ID** —
1. In the left sidebar, click **Bot**.
2. Discord automatically creates a bot user for your application. You'll see the bot's username, which you can customize.
3. Under **Authorization Flow**:
- Set **Public Bot** to **OFF**this prevents other people from inviting your bot to their servers.
- Set **Public Bot** to **ON**required to use the Discord-provided invite link (recommended). This allows the Installation tab to generate a default authorization URL.
- Leave **Require OAuth2 Code Grant** set to **OFF**.
:::tip
You can set a custom avatar and banner for your bot on this page. This is what users will see in Discord.
:::
:::info[Private Bot Alternative]
If you prefer to keep your bot private (Public Bot = OFF), you **must** use the **Manual URL** method in Step 5 instead of the Installation tab. The Discord-provided link requires Public Bot to be enabled.
:::
## Step 3: Enable Privileged Gateway Intents
This is the most critical step in the entire setup. Without the correct intents enabled, your bot will connect to Discord but **will not be able to read message content**.
@@ -149,6 +153,10 @@ You need an OAuth2 URL to invite the bot to your server. There are two ways to d
### Option A: Using the Installation Tab (Recommended)
:::note[Requires Public Bot]
This method requires **Public Bot** to be set to **ON** in Step 2. If you set Public Bot to OFF, use the Manual URL method below instead.
:::
1. In the left sidebar, click **Installation**.
2. Under **Installation Contexts**, enable **Guild Install**.
3. For **Install Link**, select **Discord Provided Link**.
@@ -361,3 +369,6 @@ Always set `DISCORD_ALLOWED_USERS` to restrict who can interact with the bot. Wi
:::
For more information on securing your Hermes Agent deployment, see the [Security Guide](../security.md).

View File

@@ -104,6 +104,7 @@ The adapter polls the IMAP inbox for UNSEEN messages at a configurable interval
- Documents (PDF, ZIP, etc.) → available for file access
- **HTML-only emails** have tags stripped for plain text extraction
- **Self-messages** are filtered out to prevent reply loops
- **Automated/noreply senders** are silently ignored — `noreply@`, `mailer-daemon@`, `bounce@`, `no-reply@`, and emails with `Auto-Submitted`, `Precedence: bulk`, or `List-Unsubscribe` headers
### Sending Replies

View File

@@ -0,0 +1,129 @@
---
sidebar_position: 11
title: "Feishu / Lark"
description: "Set up Hermes Agent as a Feishu or Lark bot"
---
# Feishu / Lark Setup
Hermes Agent integrates with Feishu and Lark as a full-featured bot. Once connected, you can chat with the agent in direct messages or group chats, receive cron job results in a home chat, and send text, images, audio, and file attachments through the normal gateway flow.
The integration supports both connection modes:
- `websocket` — recommended; Hermes opens the outbound connection and you do not need a public webhook endpoint
- `webhook` — useful when you want Feishu/Lark to push events into your gateway over HTTP
## How Hermes Behaves
| Context | Behavior |
|---------|----------|
| Direct messages | Hermes responds to every message. |
| Group chats | Hermes responds when the bot is addressed in the chat. |
| Shared group chats | By default, session history is isolated per user inside a shared chat. |
This shared-chat behavior is controlled by `config.yaml`:
```yaml
group_sessions_per_user: true
```
Set it to `false` only if you explicitly want one shared conversation per chat.
## Step 1: Create a Feishu / Lark App
1. Open the Feishu or Lark developer console:
- Feishu: <https://open.feishu.cn/>
- Lark: <https://open.larksuite.com/>
2. Create a new app.
3. In **Credentials & Basic Info**, copy the **App ID** and **App Secret**.
4. Enable the **Bot** capability for the app.
:::warning
Keep the App Secret private. Anyone with it can impersonate your app.
:::
## Step 2: Choose a Connection Mode
### Recommended: WebSocket mode
Use WebSocket mode when Hermes runs on your laptop, workstation, or a private server. No public URL is required.
```bash
FEISHU_CONNECTION_MODE=websocket
```
### Optional: Webhook mode
Use webhook mode only when you already run Hermes behind a reachable HTTP endpoint.
```bash
FEISHU_CONNECTION_MODE=webhook
```
In webhook mode, Hermes serves a Feishu endpoint at:
```text
/feishu/webhook
```
## Step 3: Configure Hermes
### Option A: Interactive Setup
```bash
hermes gateway setup
```
Select **Feishu / Lark** and fill in the prompts.
### Option B: Manual Configuration
Add the following to `~/.hermes/.env`:
```bash
FEISHU_APP_ID=cli_xxx
FEISHU_APP_SECRET=secret_xxx
FEISHU_DOMAIN=feishu
FEISHU_CONNECTION_MODE=websocket
# Optional but strongly recommended
FEISHU_ALLOWED_USERS=ou_xxx,ou_yyy
FEISHU_HOME_CHANNEL=oc_xxx
```
`FEISHU_DOMAIN` accepts:
- `feishu` for Feishu China
- `lark` for Lark international
## Step 4: Start the Gateway
```bash
hermes gateway
```
Then message the bot from Feishu/Lark to confirm that the connection is live.
## Home Chat
Use `/set-home` in a Feishu/Lark chat to mark it as the home channel for cron job results and cross-platform notifications.
You can also preconfigure it:
```bash
FEISHU_HOME_CHANNEL=oc_xxx
```
## Security
For production use, set an allowlist:
```bash
FEISHU_ALLOWED_USERS=ou_xxx,ou_yyy
```
If you leave the allowlist empty, anyone who can reach the bot may be able to use it.
## Toolset
Feishu / Lark uses the `hermes-feishu` platform preset, which includes the same core tools as Telegram and other gateway-based messaging platforms.

View File

@@ -6,7 +6,7 @@ description: "Chat with Hermes from Telegram, Discord, Slack, WhatsApp, Signal,
# Messaging Gateway
Chat with Hermes from Telegram, Discord, Slack, WhatsApp, Signal, SMS, Email, Home Assistant, Mattermost, Matrix, DingTalk, or your browser. The gateway is a single background process that connects to all your configured platforms, handles sessions, runs cron jobs, and delivers voice messages.
Chat with Hermes from Telegram, Discord, Slack, WhatsApp, Signal, SMS, Email, Home Assistant, Mattermost, Matrix, DingTalk, Feishu/Lark, WeCom, or your browser. The gateway is a single background process that connects to all your configured platforms, handles sessions, runs cron jobs, and delivers voice messages.
For the full voice feature set — including CLI microphone mode, spoken replies in messaging, and Discord voice-channel conversations — see [Voice Mode](/docs/user-guide/features/voice-mode) and [Use Voice Mode with Hermes](/docs/guides/use-voice-mode-with-hermes).
@@ -27,6 +27,8 @@ flowchart TB
mm[Mattermost]
mx[Matrix]
dt[DingTalk]
fs[Feishu/Lark]
wc[WeCom]
api["API Server<br/>(OpenAI-compatible)"]
wh[Webhooks]
end
@@ -289,12 +291,27 @@ If you run multiple Hermes installations on the same machine (with different `HE
### macOS (launchd)
```bash
hermes gateway install
launchctl start ai.hermes.gateway
launchctl stop ai.hermes.gateway
tail -f ~/.hermes/logs/gateway.log
hermes gateway install # Install as launchd agent
hermes gateway start # Start the service
hermes gateway stop # Stop the service
hermes gateway status # Check status
tail -f ~/.hermes/logs/gateway.log # View logs
```
The generated plist lives at `~/Library/LaunchAgents/ai.hermes.gateway.plist`. It includes three environment variables:
- **PATH** — your full shell PATH at install time, with the venv `bin/` and `node_modules/.bin` prepended. This ensures user-installed tools (Node.js, ffmpeg, etc.) are available to gateway subprocesses like the WhatsApp bridge.
- **VIRTUAL_ENV** — points to the Python virtualenv so tools can resolve packages correctly.
- **HERMES_HOME** — scopes the gateway to your Hermes installation.
:::tip PATH changes after install
launchd plists are static — if you install new tools (e.g. a new Node.js version via nvm, or ffmpeg via Homebrew) after setting up the gateway, run `hermes gateway install` again to capture the updated PATH. The gateway will detect the stale plist and reload automatically.
:::
:::info Multiple installations
Like the Linux systemd service, each `HERMES_HOME` directory gets its own launchd label. The default `~/.hermes` uses `ai.hermes.gateway`; other installations use `ai.hermes.gateway-<suffix>`.
:::
## Platform-Specific Toolsets
Each platform has its own toolset:
@@ -313,6 +330,8 @@ Each platform has its own toolset:
| Mattermost | `hermes-mattermost` | Full tools including terminal |
| Matrix | `hermes-matrix` | Full tools including terminal |
| DingTalk | `hermes-dingtalk` | Full tools including terminal |
| Feishu/Lark | `hermes-feishu` | Full tools including terminal |
| WeCom | `hermes-wecom` | Full tools including terminal |
| API Server | `hermes` (default) | Full tools including terminal |
| Webhooks | `hermes-webhook` | Full tools including terminal |
@@ -329,5 +348,7 @@ Each platform has its own toolset:
- [Mattermost Setup](mattermost.md)
- [Matrix Setup](matrix.md)
- [DingTalk Setup](dingtalk.md)
- [Feishu/Lark Setup](feishu.md)
- [WeCom Setup](wecom.md)
- [Open WebUI + API Server](open-webui.md)
- [Webhooks](webhooks.md)

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

@@ -36,22 +36,6 @@ brew install signal-cli
# Extract and add to PATH
```
### Alternative: Docker (signal-cli-rest-api)
If you prefer Docker, use the [signal-cli-rest-api](https://github.com/bbernhard/signal-cli-rest-api) container:
```bash
docker run -d --name signal-cli \
-p 8080:8080 \
-v $HOME/.local/share/signal-cli:/home/.local/share/signal-cli \
-e MODE=json-rpc \
bbernhard/signal-cli-rest-api
```
:::tip
Use `MODE=json-rpc` for best performance. The `normal` mode spawns a JVM per request and is much slower.
:::
---
## Step 1: Link Your Signal Account

View File

@@ -114,7 +114,22 @@ Without these events, Slack simply never delivers channel messages to the bot.
---
## Step 5: Install App to Workspace
## Step 5: Enable the Messages Tab
This step enables direct messages to the bot. Without it, users see **"Sending messages to this app has been turned off"** when trying to DM the bot.
1. In the sidebar, go to **Features → App Home**
2. Scroll to **Show Tabs**
3. Toggle **Messages Tab** to ON
4. Check **"Allow users to send Slash commands and messages from the messages tab"**
:::danger Without this step, DMs are completely blocked
Even with all the correct scopes and event subscriptions, Slack will not allow users to send direct messages to the bot unless the Messages Tab is enabled. This is a Slack platform requirement, not a Hermes configuration issue.
:::
---
## Step 6: Install App to Workspace
1. In the sidebar, go to **Settings → Install App**
2. Click **Install to Workspace**
@@ -129,7 +144,7 @@ to take effect. The Install App page will show a banner prompting you to do so.
---
## Step 6: Find User IDs for the Allowlist
## Step 7: Find User IDs for the Allowlist
Hermes uses Slack **Member IDs** (not usernames or display names) for the allowlist.
@@ -144,7 +159,7 @@ Member IDs look like `U01ABC2DEF3`. You need your own Member ID at minimum.
---
## Step 7: Configure Hermes
## Step 8: Configure Hermes
Add the following to your `~/.hermes/.env` file:
@@ -175,7 +190,7 @@ sudo hermes gateway install --system # Linux only: boot-time system service
---
## Step 8: Invite the Bot to Channels
## Step 9: Invite the Bot to Channels
After starting the gateway, you need to **invite the bot** to any channel where you want it to respond:
@@ -239,6 +254,7 @@ Hermes supports voice on Slack:
| Bot works in DMs but not in channels | **Most common issue.** Add `message.channels` and `message.groups` to event subscriptions, reinstall the app, and invite the bot to the channel with `/invite @Hermes Agent` |
| Bot doesn't respond to @mentions in channels | 1) Check `message.channels` event is subscribed. 2) Bot must be invited to the channel. 3) Ensure `channels:history` scope is added. 4) Reinstall the app after scope/event changes |
| Bot ignores messages in private channels | Add both the `message.groups` event subscription and `groups:history` scope, then reinstall the app and `/invite` the bot |
| "Sending messages to this app has been turned off" in DMs | Enable the **Messages Tab** in App Home settings (see Step 5) |
| "not_authed" or "invalid_auth" errors | Regenerate your Bot Token and App Token, update `.env` |
| Bot responds but can't post in a channel | Invite the bot to the channel with `/invite @Hermes Agent` |
| "missing_scope" error | Add the required scope in OAuth & Permissions, then **reinstall** the app |

View File

@@ -15,7 +15,7 @@ The agent processes the event and can respond by posting comments on PRs, sendin
## Quick Start
1. Enable via `hermes gateway setup` or environment variables
2. Define webhook routes in `config.yaml`
2. Define routes in `config.yaml` **or** create them dynamically with `hermes webhook subscribe`
3. Point your service at `http://your-server:8644/webhooks/<route-name>`
---
@@ -205,6 +205,56 @@ For cross-platform delivery (telegram, discord, slack, signal, sms), the target
---
## Dynamic Subscriptions (CLI) {#dynamic-subscriptions}
In addition to static routes in `config.yaml`, you can create webhook subscriptions dynamically using the `hermes webhook` CLI command. This is especially useful when the agent itself needs to set up event-driven triggers.
### Create a subscription
```bash
hermes webhook subscribe github-issues \
--events "issues" \
--prompt "New issue #{issue.number}: {issue.title}\nBy: {issue.user.login}\n\n{issue.body}" \
--deliver telegram \
--deliver-chat-id "-100123456789" \
--description "Triage new GitHub issues"
```
This returns the webhook URL and an auto-generated HMAC secret. Configure your service to POST to that URL.
### List subscriptions
```bash
hermes webhook list
```
### Remove a subscription
```bash
hermes webhook remove github-issues
```
### Test a subscription
```bash
hermes webhook test github-issues
hermes webhook test github-issues --payload '{"issue": {"number": 42, "title": "Test"}}'
```
### How dynamic subscriptions work
- Subscriptions are stored in `~/.hermes/webhook_subscriptions.json`
- The webhook adapter hot-reloads this file on each incoming request (mtime-gated, negligible overhead)
- Static routes from `config.yaml` always take precedence over dynamic ones with the same name
- Dynamic subscriptions use the same route format and capabilities as static routes (events, prompt templates, skills, delivery)
- No gateway restart required — subscribe and it's immediately live
### Agent-driven subscriptions
The agent can create subscriptions via the terminal tool when guided by the `webhook-subscriptions` skill. Ask the agent to "set up a webhook for GitHub issues" and it will run the appropriate `hermes webhook subscribe` command.
---
## Security {#security}
The webhook adapter includes multiple layers of security:

View File

@@ -0,0 +1,86 @@
---
sidebar_position: 14
title: "WeCom (Enterprise WeChat)"
description: "Connect Hermes Agent to WeCom via the AI Bot WebSocket gateway"
---
# WeCom (Enterprise WeChat)
Connect Hermes to [WeCom](https://work.weixin.qq.com/) (企业微信), Tencent's enterprise messaging platform. The adapter uses WeCom's AI Bot WebSocket gateway for real-time bidirectional communication — no public endpoint or webhook needed.
## Prerequisites
- A WeCom organization account
- An AI Bot created in the WeCom Admin Console
- The Bot ID and Secret from the bot's credentials page
## Setup
### 1. Create an AI Bot
1. Log in to the [WeCom Admin Console](https://work.weixin.qq.com/wework_admin/frame)
2. Navigate to **Applications****Create Application****AI Bot**
3. Configure the bot name and description
4. Copy the **Bot ID** and **Secret** from the credentials page
### 2. Configure Hermes
Run the interactive setup:
```bash
hermes gateway setup
```
Select **WeCom** and enter your Bot ID and Secret.
Or set environment variables in `~/.hermes/.env`:
```bash
WECOM_BOT_ID=your-bot-id
WECOM_SECRET=your-secret
# Optional: restrict access
WECOM_ALLOWED_USERS=user_id_1,user_id_2
# Optional: home channel for cron/notifications
WECOM_HOME_CHANNEL=chat_id
```
### 3. Start the gateway
```bash
hermes gateway start
```
## Features
- **WebSocket transport** — persistent connection, no public endpoint needed
- **DM and group messaging** — configurable access policies
- **Media support** — images, files, voice, video upload and download
- **AES-encrypted media** — automatic decryption for inbound attachments
- **Quote context** — preserves reply threading
- **Markdown rendering** — rich text responses
- **Auto-reconnect** — exponential backoff on connection drops
## Configuration Options
Set these in `config.yaml` under `platforms.wecom.extra`:
| Key | Default | Description |
|-----|---------|-------------|
| `bot_id` | — | WeCom AI Bot ID (required) |
| `secret` | — | WeCom AI Bot Secret (required) |
| `websocket_url` | `wss://openws.work.weixin.qq.com` | WebSocket gateway URL |
| `dm_policy` | `open` | DM access: `open`, `allowlist`, `disabled`, `pairing` |
| `group_policy` | `open` | Group access: `open`, `allowlist`, `disabled` |
| `allow_from` | `[]` | User IDs allowed for DMs (when dm_policy=allowlist) |
| `group_allow_from` | `[]` | Group IDs allowed (when group_policy=allowlist) |
## Troubleshooting
| Problem | Fix |
|---------|-----|
| "WECOM_BOT_ID and WECOM_SECRET are required" | Set both env vars or configure in setup wizard |
| "invalid secret (errcode=40013)" | Verify the secret matches your bot's credentials |
| "Timed out waiting for subscribe acknowledgement" | Check network connectivity to `openws.work.weixin.qq.com` |
| Bot doesn't respond in groups | Check `group_policy` setting and group allowlist |

View File

@@ -173,6 +173,7 @@ whatsapp:
| **Logged out unexpectedly** | WhatsApp unlinks devices after long inactivity. Keep the phone on and connected to the network, then re-pair with `hermes whatsapp` if needed. |
| **Bridge crashes or reconnect loops** | Restart the gateway, update Hermes, and re-pair if the session was invalidated by a WhatsApp protocol change. |
| **Bot stops working after WhatsApp update** | Update Hermes to get the latest bridge version, then re-pair. |
| **macOS: "Node.js not installed" but node works in terminal** | launchd services don't inherit your shell PATH. Run `hermes gateway install` to re-snapshot your current PATH into the plist, then `hermes gateway start`. See the [Gateway Service docs](./index.md#macos-launchd) for details. |
| **Messages not being received** | Verify `WHATSAPP_ALLOWED_USERS` includes the sender's number (with country code, no `+` or spaces). |
| **Bot replies to strangers with a pairing code** | Set `whatsapp.unauthorized_dm_behavior: ignore` in `~/.hermes/config.yaml` if you want unauthorized DMs to be silently ignored instead. |

View File

@@ -0,0 +1,202 @@
---
sidebar_position: 2
---
# Profiles: Running Multiple Agents
Run multiple independent Hermes agents on the same machine — each with its own config, API keys, memory, sessions, skills, and gateway.
## What are profiles?
A profile is a fully isolated Hermes environment. Each profile gets its own directory containing its own `config.yaml`, `.env`, `SOUL.md`, memories, sessions, skills, cron jobs, and state database. Profiles let you run separate agents for different purposes — a coding assistant, a personal bot, a research agent — without any cross-contamination.
When you create a profile, it automatically becomes its own command. Create a profile called `coder` and you immediately have `coder chat`, `coder setup`, `coder gateway start`, etc.
## Quick start
```bash
hermes profile create coder # creates profile + "coder" command alias
coder setup # configure API keys and model
coder chat # start chatting
```
That's it. `coder` is now a fully independent agent. It has its own config, its own memory, its own everything.
## Creating a profile
### Blank profile
```bash
hermes profile create mybot
```
Creates a fresh profile with bundled skills seeded. Run `mybot setup` to configure API keys, model, and gateway tokens.
### Clone config only (`--clone`)
```bash
hermes profile create work --clone
```
Copies your current profile's `config.yaml`, `.env`, and `SOUL.md` into the new profile. Same API keys and model, but fresh sessions and memory. Edit `~/.hermes/profiles/work/.env` for different API keys, or `~/.hermes/profiles/work/SOUL.md` for a different personality.
### Clone everything (`--clone-all`)
```bash
hermes profile create backup --clone-all
```
Copies **everything** — config, API keys, personality, all memories, full session history, skills, cron jobs, plugins. A complete snapshot. Useful for backups or forking an agent that already has context.
### Clone from a specific profile
```bash
hermes profile create work --clone --clone-from coder
```
## Using profiles
### Command aliases
Every profile automatically gets a command alias at `~/.local/bin/<name>`:
```bash
coder chat # chat with the coder agent
coder setup # configure coder's settings
coder gateway start # start coder's gateway
coder doctor # check coder's health
coder skills list # list coder's skills
coder config set model.model anthropic/claude-sonnet-4
```
The alias works with every hermes subcommand — it's just `hermes -p <name>` under the hood.
### The `-p` flag
You can also target a profile explicitly with any command:
```bash
hermes -p coder chat
hermes --profile=coder doctor
hermes chat -p coder -q "hello" # works in any position
```
### Sticky default (`hermes profile use`)
```bash
hermes profile use coder
hermes chat # now targets coder
hermes tools # configures coder's tools
hermes profile use default # switch back
```
Sets a default so plain `hermes` commands target that profile. Like `kubectl config use-context`.
### Knowing where you are
The CLI always shows which profile is active:
- **Prompt**: `coder ` instead of ``
- **Banner**: Shows `Profile: coder` on startup
- **`hermes profile`**: Shows current profile name, path, model, gateway status
## Running gateways
Each profile runs its own gateway as a separate process with its own bot token:
```bash
coder gateway start # starts coder's gateway
assistant gateway start # starts assistant's gateway (separate process)
```
### Different bot tokens
Each profile has its own `.env` file. Configure a different Telegram/Discord/Slack bot token in each:
```bash
# Edit coder's tokens
nano ~/.hermes/profiles/coder/.env
# Edit assistant's tokens
nano ~/.hermes/profiles/assistant/.env
```
### Safety: token locks
If two profiles accidentally use the same bot token, the second gateway will be blocked with a clear error naming the conflicting profile. Supported for Telegram, Discord, Slack, WhatsApp, and Signal.
### Persistent services
```bash
coder gateway install # creates hermes-gateway-coder systemd/launchd service
assistant gateway install # creates hermes-gateway-assistant service
```
Each profile gets its own service name. They run independently.
## Configuring profiles
Each profile has its own:
- **`config.yaml`** — model, provider, toolsets, all settings
- **`.env`** — API keys, bot tokens
- **`SOUL.md`** — personality and instructions
```bash
coder config set model.model anthropic/claude-sonnet-4
echo "You are a focused coding assistant." > ~/.hermes/profiles/coder/SOUL.md
```
## Updating
`hermes update` pulls code once (shared) and syncs new bundled skills to **all** profiles automatically:
```bash
hermes update
# → Code updated (12 commits)
# → Skills synced: default (up to date), coder (+2 new), assistant (+2 new)
```
User-modified skills are never overwritten.
## Managing profiles
```bash
hermes profile list # show all profiles with status
hermes profile show coder # detailed info for one profile
hermes profile rename coder dev-bot # rename (updates alias + service)
hermes profile export coder # export to coder.tar.gz
hermes profile import coder.tar.gz # import from archive
```
## Deleting a profile
```bash
hermes profile delete coder
```
This stops the gateway, removes the systemd/launchd service, removes the command alias, and deletes all profile data. You'll be asked to type the profile name to confirm.
Use `--yes` to skip confirmation: `hermes profile delete coder --yes`
:::note
You cannot delete the default profile (`~/.hermes`). To remove everything, use `hermes uninstall`.
:::
## Tab completion
```bash
# Bash
eval "$(hermes completion bash)"
# Zsh
eval "$(hermes completion zsh)"
```
Add the line to your `~/.bashrc` or `~/.zshrc` for persistent completion. Completes profile names after `-p`, profile subcommands, and top-level commands.
## How it works
Profiles use the `HERMES_HOME` environment variable. When you run `coder chat`, the wrapper script sets `HERMES_HOME=~/.hermes/profiles/coder` before launching hermes. Since 119+ files in the codebase resolve paths via `get_hermes_home()`, everything automatically scopes to the profile's directory — config, sessions, memory, skills, state database, gateway PID, logs, and cron jobs.
The default profile is simply `~/.hermes` itself. No migration needed — existing installs work identically.

View File

@@ -43,6 +43,8 @@ The following patterns trigger approval prompts (defined in `tools/approval.py`)
| `bash -c`, `python -e` | Shell/script execution via flags |
| `find -exec rm`, `find -delete` | Find with destructive actions |
| Fork bomb patterns | Fork bombs |
| `pkill`/`killall` hermes/gateway | Self-termination prevention |
| `gateway run` with `&`/`disown`/`nohup` | Prevents starting gateway outside service manager |
:::info
**Container bypass**: When running in `docker`, `singularity`, `modal`, or `daytona` backends, dangerous command checks are **skipped** because the container itself is the security boundary. Destructive commands inside a container can't harm the host.
@@ -276,7 +278,11 @@ required_environment_variables:
help: Get a key from https://developers.google.com/tenor
```
After loading this skill, `TENOR_API_KEY` passes through to both `execute_code` and `terminal` subprocesses — no manual configuration needed.
After loading this skill, `TENOR_API_KEY` passes through to `execute_code`, `terminal` (local), **and remote backends (Docker, Modal)** — no manual configuration needed.
:::info Docker & Modal
Prior to v0.5.1, Docker's `forward_env` was a separate system from the skill passthrough. They are now merged — skill-declared env vars are automatically forwarded into Docker containers and Modal sandboxes without needing to add them to `docker_forward_env` manually.
:::
**2. Config-based passthrough (manual)**
@@ -289,17 +295,49 @@ terminal:
- ANOTHER_TOKEN
```
### Credential File Passthrough (OAuth tokens, etc.) {#credential-file-passthrough}
Some skills need **files** (not just env vars) in the sandbox — for example, Google Workspace stores OAuth tokens as `google_token.json` in `~/.hermes/`. Skills declare these in frontmatter:
```yaml
required_credential_files:
- path: google_token.json
description: Google OAuth2 token (created by setup script)
- path: google_client_secret.json
description: Google OAuth2 client credentials
```
When loaded, Hermes checks if these files exist in `~/.hermes/` and registers them for mounting:
- **Docker**: Read-only bind mounts (`-v host:container:ro`)
- **Modal**: Mounted at sandbox creation + synced before each command (handles mid-session OAuth setup)
- **Local**: No action needed (files already accessible)
You can also list credential files manually in `config.yaml`:
```yaml
terminal:
credential_files:
- google_token.json
- my_custom_oauth_token.json
```
Paths are relative to `~/.hermes/`. Files are mounted to `/root/.hermes/` inside the container.
### What Each Sandbox Filters
| Sandbox | Default Filter | Passthrough Override |
|---------|---------------|---------------------|
| **execute_code** | Blocks vars containing `KEY`, `TOKEN`, `SECRET`, `PASSWORD`, `CREDENTIAL`, `PASSWD`, `AUTH` in name; only allows safe-prefix vars through | ✅ Passthrough vars bypass both checks |
| **terminal** (local) | Blocks explicit Hermes infrastructure vars (provider keys, gateway tokens, tool API keys) | ✅ Passthrough vars bypass the blocklist |
| **terminal** (Docker) | No host env vars by default | ✅ Passthrough vars + `docker_forward_env` forwarded via `-e` |
| **terminal** (Modal) | No host env/files by default | ✅ Credential files mounted; env passthrough via sync |
| **MCP** | Blocks everything except safe system vars + explicitly configured `env` | ❌ Not affected by passthrough (use MCP `env` config instead) |
### Security Considerations
- The passthrough only affects vars you or your skills explicitly declare — the default security posture is unchanged for arbitrary LLM-generated code
- Credential files are mounted **read-only** into Docker containers
- Skills Guard scans skill content for suspicious env access patterns before installation
- Missing/unset vars are never registered (you can't leak what doesn't exist)
- Hermes infrastructure secrets (provider API keys, gateway tokens) should never be added to `env_passthrough` — they have dedicated mechanisms
@@ -392,7 +430,7 @@ security:
When `tirith_fail_open` is `true` (default), commands proceed if tirith is not installed or times out. Set to `false` in high-security environments to block commands when tirith is unavailable.
Tirith's verdict integrates with the approval flow: safe commands pass through, suspicious commands trigger user approval, and dangerous commands are blocked.
Tirith's verdict integrates with the approval flow: safe commands pass through, while both suspicious and blocked commands trigger user approval with the full tirith findings (severity, title, description, safer alternatives). Users can approve or deny — the default choice is deny to keep unattended scenarios secure.
### Context File Injection Protection