Merge commit '6110aed9b' into feat/whatsapp-cloud-api

This commit is contained in:
emozilla
2026-06-10 21:39:22 -04:00
3038 changed files with 499127 additions and 63840 deletions
@@ -38,6 +38,31 @@ BLUEBUBBLES_SERVER_URL=http://192.168.1.10:1234
BLUEBUBBLES_PASSWORD=your-server-password
```
#### Optional: Require mentions in group chats
By default, Hermes responds to every authorized BlueBubbles/iMessage DM or group message. To make group chats opt-in, enable mention gating:
```yaml
platforms:
bluebubbles:
enabled: true
extra:
require_mention: true
```
With `require_mention: true`, DMs still work normally, but group-chat messages are ignored unless they match a mention pattern. If you do not configure custom patterns, Hermes uses conservative defaults for `Hermes` and `@Hermes agent` variants.
For a custom agent name, set regex patterns:
```yaml
platforms:
bluebubbles:
extra:
require_mention: true
mention_patterns:
- '(?<![\w@])@?amos\b[,:\-]?'
```
### 4. Authorize Users
Choose one approach:
@@ -90,6 +115,8 @@ Hermes → BlueBubbles REST API → Messages.app → iMessage
| `BLUEBUBBLES_HOME_CHANNEL` | No | — | Phone/email for cron delivery |
| `BLUEBUBBLES_ALLOWED_USERS` | No | — | Comma-separated authorized users |
| `BLUEBUBBLES_ALLOW_ALL_USERS` | No | `false` | Allow all users |
| `BLUEBUBBLES_REQUIRE_MENTION` | No | `false` | Require a mention pattern before responding in group chats |
| `BLUEBUBBLES_MENTION_PATTERNS` | No | Hermes wake words | JSON array, newline-separated, or comma-separated regex patterns for group mention matching |
Auto-marking messages as read is controlled by the `send_read_receipts` key under `platforms.bluebubbles.extra` in `~/.hermes/config.yaml` (default: `true`). There is no corresponding environment variable.
+33 -2
View File
@@ -680,8 +680,39 @@ Hermes Agent supports Discord voice messages:
- **Discord voice channels**: Hermes can also join a voice channel, listen to users speaking, and talk back in the channel.
For the full setup and operational guide, see:
- [Voice Mode](/docs/user-guide/features/voice-mode)
- [Use Voice Mode with Hermes](/docs/guides/use-voice-mode-with-hermes)
- [Voice Mode](/user-guide/features/voice-mode)
- [Use Voice Mode with Hermes](/guides/use-voice-mode-with-hermes)
### Voice Channel Audio Effects (ambient + verbal acks)
When the bot is in a voice channel, you can give it a more conversational feel: a short verbal acknowledgement ("let me look into that") before it starts working, and a subtle ambient "thinking" bed that plays underneath while tools run — the speech ducks the ambient down and swells it back when finished, similar to Grok voice mode.
discord.py plays only one audio stream per connection, so Hermes installs a software mixer on the outgoing stream that sums an ambient loop, acknowledgements, and TTS replies into that single stream — they overlap instead of cutting each other off.
This is **off by default**. Enable it in `config.yaml`:
```yaml
discord:
voice_fx:
enabled: true # master switch
ambient_enabled: true # idle "thinking" bed while tools run
ambient_path: "" # custom loop file (any audio format); "" = built-in synthesised pad
ambient_gain: 0.18 # idle bed loudness (0.01.0)
duck_gain: 0.06 # ambient loudness while the bot is speaking
speech_gain: 1.0 # TTS / acknowledgement loudness
ack_enabled: true # speak a short phrase before the first tool call of a turn
ack_phrases: # picked at random; set to [] to disable the spoken ack
- "Let me look into that."
- "One moment."
- "Checking on that now."
```
Notes:
- The acknowledgement fires at most once per turn, only when the bot is in a voice channel and the mixer is active. It uses your configured TTS provider.
- `ambient_path` accepts any file `ffmpeg` can decode; it's looped seamlessly. Leave it empty to use the built-in synthesised pad (no asset needed).
- All settings live in `config.yaml` (not `.env`) — they're behavioral, not secrets.
- When `voice_fx.enabled` is `false`, voice playback uses the original one-shot path and nothing changes.
## Forum Channels
+9 -2
View File
@@ -8,10 +8,17 @@ description: "Set up Hermes Agent as an email assistant via IMAP/SMTP"
Hermes can receive and reply to emails using standard IMAP and SMTP protocols. Send an email to the agent's address and it replies in-thread — no special client or bot API needed. Works with Gmail, Outlook, Yahoo, Fastmail, or any provider that supports IMAP/SMTP.
:::info No External Dependencies
The Email adapter uses Python's built-in `imaplib`, `smtplib`, and `email` modules. No additional packages or external services are required.
:::info Gateway adapter only: no external dependencies
This page covers the Email gateway adapter, which uses Python's built-in `imaplib`, `smtplib`, and `email` modules. No additional packages or external services are required for this gateway path.
:::
This is separate from the bundled [Himalaya email skill](/docs/user-guide/skills/bundled/email/email-himalaya), which lets the agent manage email through terminal commands and requires the external `himalaya` CLI plus a Himalaya config file.
| Use case | What to configure | External dependency |
|---|---|---|
| Let people email the Hermes agent and receive replies | Email gateway adapter on this page | None beyond an IMAP/SMTP email account |
| Let the agent inspect, compose, move, and manage mailbox messages from terminal tools | Himalaya email skill | `himalaya` CLI and `~/.config/himalaya/config.toml` |
---
## Prerequisites
+58 -1
View File
@@ -55,6 +55,40 @@ If scan-to-create is not available, the wizard falls back to manual input:
Keep the App Secret private. Anyone with it can impersonate your app.
:::
### Configure Permissions
In the Feishu developer console, go to **Permission Management** and add the following scopes. You can bulk-import them in the permissions page.
**Required permissions:**
| Scope | Purpose |
|-------|---------|
| `im:message` | Receive and read messages |
| `im:message:send_as_bot` | Send messages as the bot |
| `im:resource` | Access images, files, and audio sent by users |
| `im:chat` | Access chat/group metadata |
| `im:chat:readonly` | Read chat list and membership |
**Recommended permissions (for full functionality):**
| Scope | Purpose |
|-------|---------|
| `im:message.reactions:readonly` | Receive emoji reaction events |
| `admin:app.info:readonly` | Auto-detect bot identity for @mention gating |
| `contact:user.id:readonly` | Resolve user IDs for allowlist matching |
### Configure Events
In **Events and Callbacks**:
1. Set the connection mode to **Long Connection (WebSocket)** (recommended) or configure a webhook URL
2. In the **Event Configuration** section, subscribe to:
- `im.message.receive_v1` — required for receiving messages
### Publish the App
After configuring permissions and events, go to **Version Management** and publish a new version of the app. The permissions won't take effect until a version is published and approved (for enterprise apps, this may require admin approval).
## Step 2: Choose a Connection Mode
### Recommended: WebSocket mode
@@ -93,7 +127,7 @@ FEISHU_WEBHOOK_PORT=8765 # default: 8765
FEISHU_WEBHOOK_PATH=/feishu/webhook # default: /feishu/webhook
```
When Feishu sends a URL verification challenge (`type: url_verification`), the webhook responds automatically so you can complete the subscription setup in the Feishu developer console.
When Feishu sends a URL verification challenge (`type: url_verification`), the webhook responds automatically so you can complete the subscription setup in the Feishu developer console. The challenge response is gated on `FEISHU_VERIFICATION_TOKEN` when set — challenge requests with a missing or mismatched token are rejected so an unauthenticated remote cannot prove endpoint control by echoing attacker-controlled challenge data.
## Step 3: Configure Hermes
@@ -320,6 +354,29 @@ On top of the chat/card permissions already granted, add the drive comment event
- Subscribe to `drive.notice.comment_add_v1` in **Event Subscriptions**.
- Grant the `docs:doc:readonly` and `drive:drive:readonly` scopes so the handler can read document content.
## Meeting Invitation Events
You can invite the Hermes Feishu/Lark bot into a video meeting the same way you invite a human participant. When the bot receives the meeting invitation event, Hermes can automatically start an agent turn that attempts to join the meeting.
Powered by the `vc.bot.meeting_invited_v1` event, the flow is:
- A user invites the bot to a Feishu/Lark video meeting.
- Feishu/Lark sends Hermes the meeting invitation event.
- Hermes extracts the inviter, meeting topic, and meeting number.
- If the inviter is authorized by the normal gateway allowlist or pairing policy, the agent receives the meeting number and tries to join automatically.
- If the invite is malformed, or the agent cannot join, Hermes drops the event or replies to the inviter with a concise explanation.
Malformed invitations that do not include both an inviter and a `meeting_no` are ignored.
### Required Feishu App Configuration
On top of the chat/card permissions already granted, add the video-meeting invitation event:
- Subscribe to `vc.bot.meeting_invited_v1` in **Event Subscriptions**.
- Enable the Video Conferencing permission scope prompted by the Feishu/Lark developer console for that event.
- Keep `im:message` and `im:message:send_as_bot` enabled so Hermes can reply to the inviter.
- Ensure the gateway user allowlist or pairing policy authorizes the inviter. Meeting invitations do not bypass normal gateway access checks.
## Media Support
### Inbound (receiving)
@@ -13,6 +13,8 @@ process does not need a public URL, a tunnel, or a TLS certificate. It connects,
authenticates, and listens on a subscription — the same way a Telegram bot listens
on a token.
> Run `hermes gateway setup` and pick **Google Chat** for a guided walk-through.
:::note Workspace edition
Google Chat is part of Google Workspace. You can use this integration with a
personal Workspace (`@yourdomain.com` registered through Google) or a work
@@ -229,21 +231,29 @@ There's no IAM role or scope that fixes this. The endpoint only accepts user
credentials. So the bot has to act *as a user* whenever it uploads a file —
specifically, as the user who asked for the file.
### One-time host setup
### One-time setup (per profile)
1. Go to **APIs & Services → Credentials** in the same GCP project.
2. **Create credentials → OAuth client ID → Desktop app**.
3. Download the JSON. Move it onto the host that runs Hermes.
4. On the host, register the client with Hermes:
4. Register the client with Hermes (run under the profile you want it scoped to):
```bash
python -m gateway.platforms.google_chat_user_oauth \
# Default profile:
python -m plugins.platforms.google_chat.oauth \
--client-secret /path/to/client_secret.json
# A named profile gets its own separate registration:
hermes -p <profile> python -m plugins.platforms.google_chat.oauth \
--client-secret /path/to/client_secret.json
```
That writes `~/.hermes/google_chat_user_client_secret.json`. This is shared
infrastructure — it identifies the OAuth *app*, not any individual user. One
file per host is enough no matter how many users authorize later.
That writes the client secret into the active profile's Hermes home (e.g.
`~/.hermes/google_chat_user_client_secret.json` for the default profile). The
client secret is **profile-scoped, not shared across profiles** — each profile
registers its own. This is deliberate: profiles are isolated auth boundaries, so
two profiles can point at different Google OAuth apps / accounts. Register it
once per profile that needs Google Chat attachment delivery.
### Per-user authorization (in chat)
@@ -324,13 +334,19 @@ The asker has no per-user OAuth token and there's no legacy fallback. Run
`/setup-files` in their DM and follow Step 10. After the exchange completes
the next file request uploads natively without a gateway restart.
**`/setup-files start` says "No client credentials stored on the host."**
**`/setup-files start` says "No client credentials stored."**
The one-time host setup wasn't done. From a terminal on the host that runs
Hermes:
The one-time setup wasn't done *for this profile* (the client secret is
profile-scoped, so a registration under one profile won't be seen by another).
From a terminal, run it under the profile the gateway uses:
```bash
python -m gateway.platforms.google_chat_user_oauth \
# Default profile:
python -m plugins.platforms.google_chat.oauth \
--client-secret /path/to/client_secret.json
# Named profile:
hermes -p <profile> python -m plugins.platforms.google_chat.oauth \
--client-secret /path/to/client_secret.json
```
@@ -250,3 +250,26 @@ Agent automatically:
entity_id="light.hallway")
3. Sends notification: "Front door opened. Hallway lights turned on."
```
## Troubleshooting
**Environment variables not picked up.**
The adapter reads credentials from `~/.hermes/.env` (auto-merged at startup) or
from `config.yaml`. Double-check the file lives under the active Hermes profile
home and that there's no stray quoting around the URL/token. Restart the gateway
after editing — env changes are only applied on process start.
**`conversation entity not found` / agent never replies.**
Home Assistant's conversation API requires a configured *Assist* conversation
agent. In HA, open **Settings → Voice assistants → Add assistant** and note the
resulting entity id (looks like `conversation.home_assistant` or
`conversation.openai_<name>`). Set that entity id in the adapter's
`conversation_entity` setting; the default may not exist on your instance.
**REST auth failing (`401 Unauthorized`).**
The token must be a *Long-Lived Access Token* created from your HA user profile
page (**Profile → Security → Long-lived access tokens**). Short-lived UI
session tokens won't work. Also verify the base URL includes the scheme and
port (e.g. `http://homeassistant.local:8123`) and is reachable from the host
running Hermes — `curl -H "Authorization: Bearer <token>" <url>/api/` should
return `{"message": "API running."}`.
+34 -5
View File
@@ -6,9 +6,13 @@ 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, Feishu/Lark, WeCom, Weixin, BlueBubbles (iMessage), QQ, Yuanbao, Microsoft Teams, LINE, 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, Weixin, BlueBubbles (iMessage), QQ, Yuanbao, Microsoft Teams, LINE, ntfy, 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).
For the full voice feature set — including CLI microphone mode, spoken replies in messaging, and Discord voice-channel conversations — see [Voice Mode](/user-guide/features/voice-mode) and [Use Voice Mode with Hermes](/guides/use-voice-mode-with-hermes).
:::tip
Bots need both a model provider and tool providers (TTS, web). A [Nous Portal](/integrations/nous-portal) subscription bundles all of them.
:::
## Platform Comparison
@@ -27,7 +31,7 @@ For the full voice feature set — including CLI microphone mode, spoken replies
| Matrix | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| DingTalk | — | ✅ | ✅ | — | ✅ | — | ✅ |
| Feishu/Lark | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| WeCom | ✅ | ✅ | ✅ | — | — | | |
| WeCom | ✅ | ✅ | ✅ | — | — | | |
| WeCom Callback | — | — | — | — | — | — | — |
| Weixin | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
| BlueBubbles | — | ✅ | ✅ | — | ✅ | ✅ | — |
@@ -35,6 +39,7 @@ For the full voice feature set — including CLI microphone mode, spoken replies
| Yuanbao | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
| Microsoft Teams | — | ✅ | — | ✅ | — | ✅ | — |
| LINE | — | ✅ | ✅ | — | — | ✅ | — |
| ntfy | — | — | — | — | — | — | — |
**Voice** = TTS audio replies and/or voice message transcription. **Images** = send/receive images. **Files** = send/receive file attachments. **Threads** = threaded conversations. **Reactions** = emoji reactions on messages. **Typing** = typing indicator while processing. **Streaming** = progressive message updates via editing.
@@ -256,7 +261,7 @@ gateway:
#### Inspecting your access
Use `/whoami` from any platform to see the active scope, your tier (admin / user / unrestricted), and which slash commands you can run. See the [Telegram](/docs/user-guide/messaging/telegram#slash-command-access-control) and [Discord](/docs/user-guide/messaging/discord#slash-command-access-control) pages for platform-specific examples.
Use `/whoami` from any platform to see the active scope, your tier (admin / user / unrestricted), and which slash commands you can run. See the [Telegram](/user-guide/messaging/telegram#slash-command-access-control) and [Discord](/user-guide/messaging/discord#slash-command-access-control) pages for platform-specific examples.
## Interrupting the Agent
@@ -507,9 +512,33 @@ Scheduled auto-resume for N restart-interrupted session(s)
No configuration is required. If you don't want the heads-up, set `gateway_restart_notification: false` on the platform.
### Mobile-friendly progress defaults
Telegram is usually a mobile inbox, so the defaults are tuned for that surface:
- **`tool_progress`** defaults to **`off`** — no per-tool breadcrumb stream filling up the chat.
- **`busy_ack_detail`** defaults to **`off`** — busy-state acknowledgments and long-running heartbeats stay terse (no `iteration 21/60` debug detail).
- **`interim_assistant_messages`** stays **on** — real mid-turn assistant commentary (the model literally telling you what it's about to do) is signal, not noise.
- **`long_running_notifications`** stays **on** — a single edit-in-place "⏳ Working — N min" bubble updates every few minutes so you have a heartbeat instead of staring at `typing…` for half an hour.
Opt out of either of the kept-on defaults or opt back into verbose progress per platform:
```yaml
display:
platforms:
telegram:
# Re-enable the tool-progress stream
tool_progress: new
# Show "iteration N/M, running: tool" in heartbeats and busy acks
busy_ack_detail: true
# Or quiet them entirely
interim_assistant_messages: false
long_running_notifications: false
```
### Progress bubble cleanup (opt-in)
Tool-progress messages, the "still working…" heartbeat, and status-callback bubbles can be auto-deleted after the final response lands. Enable per-platform via `display.platforms.<platform>.cleanup_progress`:
Tool-progress messages, the "still working…" heartbeat, and status-callback bubbles can also be auto-deleted after the final response lands. Enable per-platform via `display.platforms.<platform>.cleanup_progress`:
```yaml
display:
+3 -1
View File
@@ -10,6 +10,8 @@ Run Hermes Agent as a [LINE](https://line.me/) bot via the official LINE Messagi
LINE is the dominant messaging app in Japan, Taiwan, and Thailand. If your users live there, this is how they reach you.
> Run `hermes gateway setup` and pick **LINE** for a guided walk-through.
## How the bot responds
| Context | Behavior |
@@ -192,7 +194,7 @@ Cron jobs with `deliver: line` route to `LINE_HOME_CHANNEL`. The adapter ships a
## Limitations
* **Single bubble per chunk.** Each LINE text bubble is capped at 5000 characters, and at most 5 bubbles are sent per Reply/Push call. Longer responses are truncated with an ellipsis.
* **Bubble and length caps.** Each LINE text bubble is capped at 5000 characters. Longer responses are smart-chunked at ~4500 characters across up to 5 bubbles per Reply/Push call, splitting on natural boundaries where possible.
* **No native message editing.** LINE has no edit-message API — streaming responses always send fresh bubbles, never edit prior ones.
* **No Markdown rendering.** Bold (`**`), italics (`*`), code fences, and headings render as literal characters. The adapter strips them before sending; URLs are preserved (`[label](url)` becomes `label (url)`).
* **Loading indicator is DM-only.** LINE rejects the chat/loading API for groups and rooms, so the typing indicator only shows in 1:1 chats.
+26 -1
View File
@@ -19,7 +19,8 @@ Before setup, here's the part most people want to know: how Hermes behaves once
| **DMs** | Hermes responds to every message. No `@mention` needed. Each DM has its own session. Set `MATRIX_DM_MENTION_THREADS=true` to start a thread when the bot is `@mentioned` in a DM. |
| **Rooms** | By default, Hermes requires an `@mention` to respond. Set `MATRIX_REQUIRE_MENTION=false` or add room IDs to `MATRIX_FREE_RESPONSE_ROOMS` for free-response rooms. Room invites are auto-accepted. |
| **Threads** | Hermes supports Matrix threads (MSC3440). If you reply in a thread, Hermes keeps the thread context isolated from the main room timeline. Threads where the bot has already participated do not require a mention. |
| **Auto-threading** | By default, Hermes auto-creates a thread for each message it responds to in a room. This keeps conversations isolated. Set `MATRIX_AUTO_THREAD=false` to disable. |
| **Auto-threading** | By default, Hermes auto-creates a thread for each message it responds to in a room. This keeps conversations isolated. Set `MATRIX_AUTO_THREAD=false` to disable. Set `MATRIX_DM_AUTO_THREAD=true` (default false) to also auto-create threads for DM messages — this is distinct from `MATRIX_DM_MENTION_THREADS`, which only starts a thread when the bot is `@mentioned` in a DM. |
| **Commands** | Hermes accepts normal `/commands` when your Matrix client sends them. If your client reserves `/` for local commands, use `!commands` instead; Hermes normalizes known `!command` aliases to `/command`. |
| **Shared rooms with multiple users** | By default, Hermes isolates session history per user inside the room. Two people talking in the same room do not share one transcript unless you explicitly disable that. |
:::tip
@@ -336,6 +337,7 @@ You can designate a "home room" where the bot sends proactive messages (such as
### Using the Slash Command
Type `/sethome` in any Matrix room where the bot is present. That room becomes the home room.
If your Matrix client intercepts slash commands, type `!sethome` instead.
### Manual Configuration
@@ -377,6 +379,29 @@ See also: [admin/user slash command split](../../reference/slash-commands.md#per
To find a Room ID: in Element, go to the room → **Settings** → **Advanced** → the **Internal room ID** is shown there (starts with `!`).
:::
## Commands in Matrix
Hermes supports the same gateway commands in Matrix that it supports on other
messaging platforms, including `/commands`, `/model`, `/stop`, `/queue`,
`/steer`, `/goal`, `/subgoal`, `/background`, `/bg`, `/btw`, `/tasks`, and
`/yolo`.
Some Matrix clients reserve leading `/` for local client commands and may not
send unknown slash commands to the room. In that case, use `!` as a Matrix-safe
alias:
```text
!commands
!model
!model gpt-5.5 --provider openrouter
!queue continue with the next task
!stop
```
Hermes only normalizes `!command` when the command is known to the gateway, a
registered plugin command, or an installed skill command. Ordinary exclamations
such as `!important` remain normal chat messages.
## Troubleshooting
### Bot is not responding to messages
@@ -12,7 +12,7 @@ Right now the primary consumer is the Teams meeting summary pipeline: Graph noti
## Prerequisites
- Microsoft Graph application credentials — [Register a Microsoft Graph Application](/docs/guides/microsoft-graph-app-registration)
- Microsoft Graph application credentials — [Register a Microsoft Graph Application](/guides/microsoft-graph-app-registration)
- A **public HTTPS URL** that Microsoft Graph can reach (Graph does not call private endpoints). A dev tunnel works for testing; production needs a real domain with a valid certificate.
- A strong shared secret to use as the `clientState` value. Generate with `openssl rand -hex 32` and put it in `~/.hermes/.env` as `MSGRAPH_WEBHOOK_CLIENT_STATE`.
@@ -25,6 +25,7 @@ platforms:
msgraph_webhook:
enabled: true
extra:
host: 127.0.0.1
port: 8646
client_state: "replace-with-a-strong-secret"
accepted_resources:
@@ -40,6 +41,8 @@ MSGRAPH_WEBHOOK_CLIENT_STATE=<generate-with-openssl-rand-hex-32>
MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES=communications/onlineMeetings
```
Note: the bind host is read from `extra.host` in `config.yaml` (see the example above); there is no `MSGRAPH_WEBHOOK_HOST` env-var override.
Start the gateway: `hermes gateway run`. The listener exposes:
- `POST /msgraph/webhook` — change notifications from Graph
@@ -58,16 +61,16 @@ All settings go under `platforms.msgraph_webhook.extra`:
| Setting | Default | Description |
|---------|---------|-------------|
| `host` | `0.0.0.0` | Bind address for the HTTP listener. |
| `host` | `0.0.0.0` | Bind address for the HTTP listener. Non-loopback binds require `allowed_source_cidrs`; loopback (`127.0.0.1` / `::1`) is the easiest dev-tunnel / reverse-proxy setup. |
| `port` | `8646` | Bind port. |
| `webhook_path` | `/msgraph/webhook` | URL path Graph POSTs to. |
| `health_path` | `/health` | Readiness endpoint. |
| `client_state` | — | Shared secret Graph echoes in every notification. Compared with `hmac.compare_digest` — generate with `openssl rand -hex 32`. |
| `accepted_resources` | `[]` (accept all) | Allowlist of Graph resource paths/patterns. Trailing `*` acts as prefix match. Leading `/` is tolerated. Example: `["communications/onlineMeetings", "chats/*/messages"]`. |
| `max_seen_receipts` | `5000` | Dedupe cache size for notification IDs. Oldest entries evicted when the cap is hit. |
| `allowed_source_cidrs` | `[]` (allow all) | Optional source-IP allowlist. See below. |
| `allowed_source_cidrs` | `[]` | Required for non-loopback binds. Leave empty only when the listener is bound to loopback and fronted by a local tunnel / reverse proxy. |
Each setting also has an equivalent env var (`MSGRAPH_WEBHOOK_*`) that merges into the config at gateway startup — see the [environment variables reference](/docs/reference/environment-variables#microsoft-graph-teams-meetings).
Most settings also have an equivalent env var (`MSGRAPH_WEBHOOK_*`) that merges into the config at gateway startup (the exception is `host`, which is config-only — see the note above) — see the [environment variables reference](/reference/environment-variables#microsoft-graph-teams-meetings).
## Security Hardening
@@ -75,7 +78,7 @@ Each setting also has an equivalent env var (`MSGRAPH_WEBHOOK_*`) that merges in
Every Graph notification includes the `clientState` string your subscription registered with. The listener rejects any notification whose `clientState` doesn't match, using timing-safe comparison. This is Microsoft's documented mechanism — treat the value as a strong shared secret.
If `client_state` is unset, the listener accepts every well-formed POST. **Don't run without it in production.**
If `client_state` is unset, the listener refuses to start.
### Source-IP allowlisting (production deployments)
@@ -86,6 +89,7 @@ platforms:
msgraph_webhook:
enabled: true
extra:
host: 0.0.0.0
client_state: "..."
allowed_source_cidrs:
- "52.96.0.0/14"
@@ -99,7 +103,7 @@ Or as an env var:
MSGRAPH_WEBHOOK_ALLOWED_SOURCE_CIDRS="52.96.0.0/14,52.104.0.0/14"
```
Empty allowlist = accept from anywhere (default; preserves dev-tunnel workflows). Invalid CIDR strings log a warning and are ignored. **Review the Microsoft IP list quarterly** — it changes.
Binding a non-loopback host such as `0.0.0.0`, `::`, or a LAN IP without `allowed_source_cidrs` is refused at startup. If you're using a dev tunnel or reverse proxy on the same machine, bind Hermes to `127.0.0.1` or `::1` and leave the allowlist empty there. Invalid CIDR strings log a warning and are ignored. **Review the Microsoft IP list quarterly** — it changes.
### HTTPS termination
@@ -107,7 +111,7 @@ The listener speaks plain HTTP. Terminate TLS at your reverse proxy (Caddy, Ngin
### Response hygiene
On success the listener returns `202 Accepted` with an empty body — internal counters stay out of the wire response. Operators can observe counts via `/health`.
On success the listener returns `202 Accepted` with an empty body — internal counters stay out of the wire response. Operators can observe counts via `/health`, which is guarded by the same source-IP rules as the webhook path.
Status code table:
@@ -127,11 +131,12 @@ Status code table:
| Graph subscription validation fails | Public URL is reachable, `/msgraph/webhook` path matches, GET with `validationToken` echoes the token verbatim as `text/plain` within 10 seconds. |
| Notifications POST but nothing ingests | `client_state` matches what you registered the subscription with. Re-run `openssl rand -hex 32` and create a new subscription if the value drifted. Check `accepted_resources` includes the resource path Graph is sending. |
| Every notification 403s | `clientState` mismatch (forged, or subscription registered with a different value). Re-create the subscription with `hermes teams-pipeline subscribe --client-state "$MSGRAPH_WEBHOOK_CLIENT_STATE" ...` (ships with the pipeline runtime PR). |
| Listener refuses to start on `0.0.0.0` | Set `allowed_source_cidrs` to Microsoft's current webhook egress ranges, or bind Hermes to `127.0.0.1` / `::1` behind your tunnel or reverse proxy. |
| Listener starts but `curl http://localhost:8646/health` hangs | Port binding collision. Check `ss -tlnp \| grep 8646` and change `port:` if needed. |
| Real Graph requests from Microsoft get 403'd | Source IP allowlist is too narrow. Remove `allowed_source_cidrs` temporarily, confirm traffic flows, then widen the list to include the current Microsoft egress ranges. |
| Real Graph requests from Microsoft get 403'd | Source IP allowlist is too narrow. Widen the list to include the current Microsoft egress ranges. If you're still validating the tunnel path, bind Hermes to loopback and let the tunnel handle public exposure. |
## Related Docs
- [Register a Microsoft Graph Application](/docs/guides/microsoft-graph-app-registration) — Azure app registration prereq
- [Environment Variables → Microsoft Graph](/docs/reference/environment-variables#microsoft-graph-teams-meetings) — full env var list
- [Microsoft Teams bot setup](/docs/user-guide/messaging/teams) — the different platform that lets users chat with Hermes in Teams
- [Register a Microsoft Graph Application](/guides/microsoft-graph-app-registration) — Azure app registration prereq
- [Environment Variables → Microsoft Graph](/reference/environment-variables#microsoft-graph-teams-meetings) — full env var list
- [Microsoft Teams bot setup](/user-guide/messaging/teams) — the different platform that lets users chat with Hermes in Teams
+157
View File
@@ -0,0 +1,157 @@
# ntfy
[ntfy](https://ntfy.sh/) is a simple HTTP-based pub-sub notification service. It works with the free public server at `ntfy.sh` or any self-hosted instance, and supports any client that can make HTTP requests — phones, browsers, scripts, watches.
ntfy makes a great lightweight push channel for Hermes: subscribe to a topic from the [ntfy mobile app](https://ntfy.sh/docs/subscribe/phone/), send messages to the topic to talk to the agent, get the response back on your phone.
> Run `hermes gateway setup` and pick **ntfy** for a guided walk-through.
## Prerequisites
- A topic name (any unique string — `hermes-myname-2026` works fine)
- The [ntfy mobile app](https://ntfy.sh/docs/subscribe/phone/) installed and subscribed to that topic
- Optional: a self-hosted ntfy server, or an `ntfy.sh` account token for private/reserved topics
That's it. No SDK, no daemon, no Node.js. The adapter uses `httpx` which is already a Hermes dependency.
## Configure Hermes
### Via setup wizard
```bash
hermes gateway setup
```
Select **ntfy** and follow the prompts.
### Via environment variables
Add these to `~/.hermes/.env`:
```
NTFY_TOPIC=hermes-myname-2026
NTFY_ALLOWED_USERS=hermes-myname-2026
NTFY_HOME_CHANNEL=hermes-myname-2026
```
| Variable | Required | Description |
|---|---|---|
| `NTFY_TOPIC` | Yes | Topic to subscribe to (incoming messages) |
| `NTFY_SERVER_URL` | Optional | Server URL (default: `https://ntfy.sh`) — point to a self-hosted ntfy for privacy |
| `NTFY_TOKEN` | Optional | Bearer token (e.g. `tk_xyz`) or `user:pass` for Basic auth |
| `NTFY_PUBLISH_TOPIC` | Optional | Different topic for outgoing replies (defaults to `NTFY_TOPIC`) |
| `NTFY_MARKDOWN` | Optional | Set `true` to send replies with `X-Markdown: true` header |
| `NTFY_ALLOWED_USERS` | Recommended | Comma-separated topic names allowed (treated as user IDs; see below) |
| `NTFY_ALLOW_ALL_USERS` | Optional | Set `true` to allow every publisher — only safe for private topics with read tokens |
| `NTFY_HOME_CHANNEL` | Optional | Default topic for cron / notification delivery |
| `NTFY_HOME_CHANNEL_NAME` | Optional | Human label for the home channel |
## Identity model — read this before deploying
ntfy has no native authenticated user identity. The `title` field on a published message is **publisher-controlled** and can be anything the sender wants. The Hermes adapter does NOT use `title` for authorization — it would let any publisher who knows the topic spoof an allowed user.
Instead, **the topic name itself is the identity**. Every message published to the topic is treated as coming from the same logical user (the topic). `NTFY_ALLOWED_USERS` is therefore typically just the topic name itself — a single-entry allowlist that gates the whole channel.
This means **anyone who knows the topic can talk to the agent**. To make that a real trust boundary:
- **Self-host ntfy** and lock the topic down with [Access Control](https://docs.ntfy.sh/config/#access-control). Only authorized clients with the read/write token can publish.
- Or **use a private topic on ntfy.sh** ([reserved topics](https://docs.ntfy.sh/publish/#reserved-topics) require an account) and protect it with a `NTFY_TOKEN`.
- Or **pick a long, unguessable topic name** (`hermes-7d4f9c8b-2026`) and treat it as the shared secret. This is the lightest setup but the topic name leaks via any logs or screenshots.
In all cases, do not put sensitive data through ntfy unless the underlying topic is access-controlled.
## Quick start — talk to your agent from your phone
1. Pick a topic name: `hermes-myname-2026`
2. On your phone: install the [ntfy app](https://ntfy.sh/docs/subscribe/phone/), tap **+**, enter `hermes-myname-2026`
3. On the host:
```bash
echo 'NTFY_TOPIC=hermes-myname-2026' >> ~/.hermes/.env
echo 'NTFY_ALLOWED_USERS=hermes-myname-2026' >> ~/.hermes/.env
hermes gateway restart
```
4. From the ntfy app, send a message to the topic. The agent's reply lands as a push notification.
## Using ntfy with cron jobs
Once `NTFY_HOME_CHANNEL` is set, cron jobs can deliver to ntfy:
```python
cronjob(
action="create",
schedule="every 1h",
deliver="ntfy", # uses NTFY_HOME_CHANNEL
prompt="Check for alerts and summarise."
)
```
Or target a specific topic explicitly:
```python
send_message(target="ntfy:alerts-channel", message="Done!")
```
This works even when the cron runs out-of-process from the gateway — the plugin registers a `standalone_sender_fn` that opens its own HTTP connection.
## Self-hosting ntfy
If you want full control:
```bash
# Docker
docker run -p 80:80 -it binwiederhier/ntfy serve
# Native
go install heckel.io/ntfy/v2@latest
ntfy serve
```
Then point Hermes at it:
```
NTFY_SERVER_URL=https://ntfy.mydomain.com
NTFY_TOPIC=hermes
NTFY_TOKEN=tk_abc123 # if you've set up access control
```
Self-hosting gives you topic access control, message persistence policies, attachments, and emoji tags. See the [ntfy server docs](https://docs.ntfy.sh/install/).
## Markdown formatting
ntfy clients render markdown when the publisher sets the `X-Markdown: true` header. To enable for outgoing Hermes replies:
```
NTFY_MARKDOWN=true
```
Or in `config.yaml`:
```yaml
platforms:
ntfy:
extra:
markdown: true
```
The mobile app supports a subset of CommonMark — bold, italic, lists, links, fenced code blocks. See [ntfy's markdown docs](https://docs.ntfy.sh/publish/#markdown-formatting) for the exact set.
## Outgoing-only setup (notifications without inbound)
If you only want Hermes to *push* notifications to ntfy (cron summaries, alerts) and never accept messages back, set both `NTFY_TOPIC` and `NTFY_PUBLISH_TOPIC` to the same value and skip `NTFY_ALLOWED_USERS` entirely. With no allowlist, the agent never responds to inbound messages — your phone gets the pushes, but the conversation is one-way.
## Limits
- **Message size**: ntfy caps message bodies at 4096 chars. Hermes truncates with a warning when this is exceeded.
- **No typing indicators**: the protocol doesn't expose one; `send_typing` is a no-op.
- **No threads or attachments**: ntfy is plain push notifications. Long replies stay in the message body, no thread fanout.
- **No native user identity**: see the identity-model section above.
## Troubleshooting
**Auth failure / 401** — `NTFY_TOKEN` is wrong, or the token doesn't have publish/subscribe rights on this topic. The adapter halts its reconnect loop on 401 and the gateway runtime status will show `fatal: ntfy_unauthorized`. Fix the token and restart the gateway.
**Topic not found / 404** — `NTFY_TOPIC` doesn't exist on the configured server. For ntfy.sh, topics are auto-created on first publish, so a 404 means you're pointed at a self-hosted server that doesn't have the topic provisioned. The adapter halts its reconnect loop with `fatal: ntfy_topic_not_found`.
**Connected but no messages** — Check that `NTFY_ALLOWED_USERS` includes the topic name itself. With ntfy's identity model, the topic IS the user; leaving the allowlist empty rejects everything.
**Reconnects every 60s** — The stream keepalive default is 55s; ntfy may have intermittent network issues. The adapter applies exponential backoff (2 → 5 → 10 → 30 → 60s) and resets to 0 once a stream stays alive ≥60s.
@@ -271,7 +271,7 @@ Open WebUI persists OpenAI-compatible connection settings in its own database af
## Multi-User Setup with Profiles
To run separate Hermes instances per user — each with their own config, memory, and skills — use [profiles](/docs/user-guide/profiles). Each profile runs its own API server on a different port and automatically advertises the profile name as the model in Open WebUI.
To run separate Hermes instances per user — each with their own config, memory, and skills — use [profiles](/user-guide/profiles). Each profile runs its own API server on a different port and automatically advertises the profile name as the model in Open WebUI.
### 1. Create profiles and configure API servers
+228
View File
@@ -0,0 +1,228 @@
---
sidebar_position: 18
---
# Photon iMessage
Connect Hermes to **iMessage** through [Photon][photon], a managed
service that handles the Apple line allocation and abuse-prevention
layer so you don't have to run your own Mac relay.
The free tier uses Photon's shared iMessage line pool — different
recipients may see different sending numbers, but each conversation
stays stable. The paid Business tier gives every user the same
dedicated number; the plugin supports both, and the free tier is the
recommended starting point.
:::info Free to start
Photon's shared-line pool is free. No subscription is required to send
your first iMessage from Hermes — just a phone number we can bind to
your account.
:::
## Architecture
Photon is a **persistent-connection** channel, like Discord or Slack —
**no webhook, no public URL, no signing secret to manage.**
The `spectrum-ts` SDK holds a long-lived **gRPC stream** to Photon for
both directions. Because the SDK is TypeScript-only, Hermes runs it in a
small supervised **Node sidecar** and talks to it over loopback:
- **Inbound** — the sidecar consumes the SDK's `app.messages` gRPC
stream and forwards each message to the Python adapter over a loopback
`GET /inbound` (NDJSON). The adapter dedupes and dispatches it to the
agent, reconnecting automatically if the stream drops.
- **Outbound** — replies are loopback POSTs to the sidecar, which calls
`space.send(...)` on the SDK.
The Python plugin starts, supervises, and shuts down the sidecar
automatically.
## Prerequisites
- A Photon account — sign up at [app.photon.codes][app]
- **Node.js 18.17 or newer** on PATH (`node --version`)
- A phone number that can receive iMessage (used to bind your account)
That's it — there is no public URL or tunnel to set up.
## First-time setup
Either run the unified gateway wizard and pick **Photon iMessage**:
```bash
hermes gateway setup
```
…or run the Photon setup directly (the wizard calls the same flow):
```bash
# Device-code login + project + user + sidecar deps, all in one
hermes photon setup --phone +15551234567
```
The setup, in order:
1. **Device login** (`client_id=photon-cli`) — opens
`https://app.photon.codes/` for approval and stores the bearer token.
2. **Finds or creates** the `Hermes Agent` project on your account.
3. **Enables Spectrum**, reads the project's Spectrum id, and rotates
the project secret.
4. **Registers your phone number** as a Spectrum user — skipped if a
user with that number already exists, so re-running is safe.
5. **Prints your assigned iMessage line** — the number you text to reach
your agent.
6. **Runs `npm install`** inside the plugin's sidecar directory.
Runtime credentials are written to `~/.hermes/.env`
(`PHOTON_PROJECT_ID` = the Spectrum project id, `PHOTON_PROJECT_SECRET`),
the same place every other channel keeps its token. Management metadata
(device token, dashboard project id) lives in `~/.hermes/auth.json` under
`credential_pool.photon` / `credential_pool.photon_project`.
## Authorizing users
Photon uses the same authorization model as every other Hermes
channel. Choose one approach:
**DM pairing (default).** When an unknown number messages your Photon
line, Hermes replies with a pairing code. Approve it with:
```bash
hermes pairing approve photon <CODE>
```
Use `hermes pairing list` to see pending codes and approved users.
**Pre-authorize specific numbers** (in `~/.hermes/.env`):
```bash
PHOTON_ALLOWED_USERS=+15551234567,+15559876543
```
**Open access** (dev only, in `~/.hermes/.env`):
```bash
PHOTON_ALLOW_ALL_USERS=true
```
When `PHOTON_ALLOWED_USERS` is set, unknown senders are silently
ignored rather than offered a pairing code (the allowlist signals you
deliberately restricted access).
### Require mentions in group chats
By default Hermes responds to every authorized DM and group message.
To make group chats opt-in, enable mention gating (DMs still always
work):
```yaml
gateway:
platforms:
photon:
enabled: true
require_mention: true
```
With `require_mention: true`, group-chat messages are ignored unless
they match a wake-word pattern. The defaults match `Hermes` and
`@Hermes agent` variants. For a custom agent name, set regex patterns:
```yaml
gateway:
platforms:
photon:
require_mention: true
mention_patterns:
- '(?<![\w@])@?amos\b[,:\-]?'
```
Both keys also accept env vars (`PHOTON_REQUIRE_MENTION`,
`PHOTON_MENTION_PATTERNS`). This is the same mention-gating model the
BlueBubbles iMessage channel uses.
## Start the gateway
```bash
hermes gateway start --platform photon
```
You'll see something like:
```
[photon] connected — sidecar on 127.0.0.1:8789, streaming inbound over gRPC
```
Send an iMessage to your assigned number and Hermes will reply.
## Status & troubleshooting
```bash
hermes photon status
```
Prints saved credentials, sidecar health, your registered number, and the
assigned iMessage line Hermes uses. When a Photon token and dashboard project
are available, `status` refreshes missing number rows from the dashboard
without provisioning new lines.
```
Photon iMessage status
──────────────────────
device token : ✓ stored
dashboard project : 3c90c3cc-0d44-4b50-...
spectrum project id : sp-...
project secret : ✓ stored
my number : +15551234567
assigned number : +16282679185
node binary : /usr/bin/node
sidecar deps : ✓ installed
```
Common issues:
- **`sidecar deps : ✗ run hermes photon install-sidecar`** — Node is
installed but `spectrum-ts` isn't. Run the suggested command.
- **`device token : ✗ missing`** — run `hermes photon setup` to log in.
- **`No iMessage line assigned yet`** — Spectrum is enabled but no line
has been provisioned; re-run `hermes photon setup` or check the
[dashboard][app].
- **Sidecar won't start** — confirm `node --version` is 18.17+ and that
`hermes photon install-sidecar` completed without errors.
## Limits today
- **Inbound attachments are metadata-only.** Inbound events carry the
filename + MIME type; the agent sees a marker but can't yet read the
bytes. The SDK exposes attachment bytes via `content.read()`, so this
is a sidecar follow-up.
- **Outbound attachments are supported.** Hermes sends images, voice
notes, video, and documents through spectrum-ts' `attachment()` /
`voice()` content builders via the sidecar's `/send-attachment`
endpoint. Captions arrive as a separate iMessage bubble after the
media.
- **Photon's free quotas:** 5,000 messages per server per day,
50 new-conversation initiations per shared line per day. Increases
available — email `help@photon.codes`.
## Env vars
| Variable | Default | Notes |
|---------------------------|--------------------|--------------------------------------------|
| `PHOTON_PROJECT_ID` | from `.env` | Spectrum project id (the SDK's `projectId`); set by setup |
| `PHOTON_PROJECT_SECRET` | from `.env` | Project secret; set by setup |
| `PHOTON_SIDECAR_PORT` | `8789` | Loopback port for the sidecar control + inbound channel |
| `PHOTON_SIDECAR_AUTOSTART`| `true` | Whether the adapter spawns the sidecar |
| `PHOTON_NODE_BIN` | `which node` | Override the Node binary path |
| `PHOTON_HOME_CHANNEL` | (unset) | Default space id for cron / notifications |
| `PHOTON_HOME_CHANNEL_NAME`| (unset) | Human label for the home channel |
| `PHOTON_ALLOWED_USERS` | (unset) | Comma-separated E.164 allowlist |
| `PHOTON_ALLOW_ALL_USERS` | `false` | Dev only — accept any sender |
| `PHOTON_REQUIRE_MENTION` | `false` | Require a wake word before responding in groups |
| `PHOTON_MENTION_PATTERNS` | Hermes wake words | JSON list / comma / newline regex patterns for group mentions |
| `PHOTON_DASHBOARD_HOST` | `app.photon.codes` | Override the dashboard / device-login host |
| `PHOTON_SPECTRUM_HOST` | `spectrum.photon.codes` | Override the Spectrum API host |
[photon]: https://photon.codes/
[app]: https://app.photon.codes/
@@ -185,6 +185,12 @@ None of this requires additional config — it ships on by default in recent sig
The bot sends typing indicators while processing messages, refreshing every 8 seconds.
### Tool Progress Display
Signal does not support editing already-sent messages. Hermes therefore suppresses gateway tool-progress bubbles on Signal, even when `/verbose` is enabled and saves a non-`off` mode for the platform.
You can still see tool activity in the CLI, and final Signal replies can include normal assistant output. If you need live per-tool progress in chat, use a messaging platform with message editing support.
### Phone Number Redaction
All phone numbers are automatically redacted in logs:
+47 -12
View File
@@ -2,6 +2,8 @@
[SimpleX Chat](https://simplex.chat/) is a private, decentralised messaging platform where users own their contacts and groups. Unlike other platforms, SimpleX assigns no persistent user IDs — every contact is identified by an opaque internal ID generated at connection time, which makes it one of the most private messengers available.
> Run `hermes gateway setup` and pick **SimpleX** for a guided walk-through.
## Prerequisites
- The **simplex-chat** CLI installed and running as a daemon
@@ -9,17 +11,16 @@
## Install simplex-chat
Download the latest release from the [simplex-chat GitHub releases](https://github.com/simplex-chat/simplex-chat/releases) page, or via Docker:
Download the latest release from the [simplex-chat GitHub releases](https://github.com/simplex-chat/simplex-chat/releases) page:
```bash
# Linux / macOS binary
curl -L https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-chat-ubuntu-22_04-x86-64 -o simplex-chat
curl -L https://github.com/simplex-chat/simplex-chat/releases/latest/download/simplex-chat-ubuntu-22_04-x86_64 -o simplex-chat
chmod +x simplex-chat
# Or Docker
docker run -p 5225:5225 simplexchat/simplex-chat -p 5225
```
The SimpleX Chat project does not publish a prebuilt Docker image for the chat client; to run it under Docker, build from source from the [simplex-chat repository](https://github.com/simplex-chat/simplex-chat).
## Start the daemon
```bash
@@ -33,7 +34,7 @@ The daemon listens on WebSocket at `ws://127.0.0.1:5225` by default.
### Via setup wizard
```bash
hermes setup gateway
hermes gateway setup
```
Select **SimpleX Chat** and follow the prompts.
@@ -51,21 +52,55 @@ SIMPLEX_HOME_CHANNEL=<contact-id>
| Variable | Required | Description |
|---|---|---|
| `SIMPLEX_WS_URL` | Yes | WebSocket URL of the simplex-chat daemon |
| `SIMPLEX_ALLOWED_USERS` | Recommended | Comma-separated contact IDs allowed to use the agent |
| `SIMPLEX_ALLOWED_USERS` | Recommended | Comma-separated allowlist. Each entry can be a numeric `contactId` **or** a display name — both forms work. |
| `SIMPLEX_ALLOW_ALL_USERS` | Optional | Set `true` to allow every contact (use carefully) |
| `SIMPLEX_HOME_CHANNEL` | Optional | Default contact ID for cron job delivery |
| `SIMPLEX_AUTO_ACCEPT` | Optional | Auto-accept incoming contact requests (default: `true`) |
| `SIMPLEX_GROUP_ALLOWED` | Optional | Comma-separated group IDs the bot participates in, or `*` for any group. Omit to ignore group messages entirely |
| `SIMPLEX_HOME_CHANNEL` | Optional | Default contact/group ID for cron job delivery |
| `SIMPLEX_HOME_CHANNEL_NAME` | Optional | Human label for the home channel |
| `HERMES_SIMPLEX_TEXT_BATCH_DELAY` | Optional | Quiet-period seconds (default: `0.8`) used to concatenate rapid-fire inbound text messages into one event |
## Find your contact ID
## Find your contact ID or display name
After starting the daemon, open a conversation with your agent contact. The contact ID will appear in session logs or via `hermes send_message action=list`.
After starting the daemon, open a conversation with your agent contact. The numeric `contactId` appears in session logs or via `hermes send_message action=list`. If you'd rather use the display name shown in the SimpleX UI, that works too — `SIMPLEX_ALLOWED_USERS` accepts either form.
## Authorization
By default **all contacts are denied**. You must either:
1. Set `SIMPLEX_ALLOWED_USERS` to a comma-separated list of contact IDs, or
2. Use **DM pairing** — send any message to the bot and it will reply with a pairing code. Enter that code via `hermes gateway pair`.
1. Set `SIMPLEX_ALLOWED_USERS` to a comma-separated list of `contactId`s and/or display names (e.g. `SIMPLEX_ALLOWED_USERS=4,alice` matches either contactId 4 or the contact whose display name is "alice"), or
2. Use **DM pairing** — send any message to the bot and it will reply with a pairing code. Enter that code via `hermes pairing approve simplex <CODE>`.
## Group chats
By default the adapter ignores group messages — a bot in a group otherwise
processes every member's traffic. Opt-in explicitly:
```
SIMPLEX_GROUP_ALLOWED=12,34 # specific group IDs
# or
SIMPLEX_GROUP_ALLOWED=* # any group the bot is in
```
Address groups by prefixing the chat ID with `group:`, e.g.
`simplex:group:12` in `send_message` or as a cron `deliver=` target.
## Attachments
The adapter supports native SimpleX attachments in both directions:
- **Inbound** — incoming images, voice notes, and files are accepted via
the daemon's XFTP flow (`rcvFileDescrReady``/freceive` → wait for
`rcvFileComplete`) and surfaced as `MessageEvent.media_urls` with the
appropriate `MessageType` (`PHOTO`, `VOICE`, `TEXT` + document).
- **Outbound** — `send_image_file`, `send_voice`, `send_document`, and
`send_video` all use the structured `/_send` form with `filePath`, so
the receiving SimpleX client renders images inline and plays voice
notes inline rather than offering them as downloads.
Agent replies can also embed `MEDIA:/path/to/file` tags in plain text —
the adapter strips the tag from the body and sends the file as either a
voice note (audio extensions) or a document.
## Using SimpleX with cron jobs
@@ -280,6 +280,11 @@ thread.
Only the first token is checked against the known command list, so
casual messages like `!nice work` pass through to the agent unchanged.
Approval prompts (dangerous command / `execute_code` approval) normally
render as interactive buttons. When buttons can't be delivered and
Hermes falls back to a text prompt, the prompt instructs you to reply
with `!approve` / `!deny` — the form that works inside threads.
### Advanced: emit only the slash-commands array
If you maintain your Slack manifest by hand and just want the slash
+2 -2
View File
@@ -10,7 +10,7 @@ description: "Set up Hermes Agent as an SMS chatbot via Twilio"
Hermes connects to SMS through the [Twilio](https://www.twilio.com/) API. People text your Twilio phone number and get AI responses back — same conversational experience as Telegram or Discord, but over standard text messages.
:::info Shared Credentials
The SMS gateway shares credentials with the optional [telephony skill](/docs/reference/skills-catalog). If you've already set up Twilio for voice calls or one-off SMS, the gateway works with the same `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, and `TWILIO_PHONE_NUMBER`.
The SMS gateway shares credentials with the optional [telephony skill](/reference/skills-catalog). If you've already set up Twilio for voice calls or one-off SMS, the gateway works with the same `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, and `TWILIO_PHONE_NUMBER`.
:::
---
@@ -126,7 +126,7 @@ Text your Twilio number — Hermes will respond via SMS.
| `TWILIO_PHONE_NUMBER` | Yes | Your Twilio phone number (E.164 format) |
| `SMS_WEBHOOK_URL` | Yes | Public URL for Twilio signature validation — must match the webhook URL in your Twilio Console |
| `SMS_WEBHOOK_PORT` | No | Webhook listener port (default: `8080`) |
| `SMS_WEBHOOK_HOST` | No | Webhook bind address (default: `0.0.0.0`) |
| `SMS_WEBHOOK_HOST` | No | Webhook bind address (default: `127.0.0.1`) |
| `SMS_INSECURE_NO_SIGNATURE` | No | Set to `true` to disable signature validation (local dev only — **not for production**) |
| `SMS_ALLOWED_USERS` | No | Comma-separated E.164 phone numbers allowed to chat |
| `SMS_ALLOW_ALL_USERS` | No | Set to `true` to allow anyone (not recommended) |
@@ -8,13 +8,17 @@ description: "Set up the Microsoft Teams meeting summary pipeline with Microsoft
Use the Teams meeting pipeline when you want Hermes to ingest Microsoft Graph meeting events, fetch transcripts first, fall back to recordings plus STT when needed, and deliver a structured summary to downstream sinks.
Prerequisites: see [Microsoft Teams](./teams.md) for the underlying bot/credential setup.
> Run `hermes gateway setup` and pick **Teams Meetings** for a guided walk-through.
This page focuses on setup and enablement:
- Graph credentials
- webhook listener configuration
- Teams delivery modes
- pipeline config shape
For day-2 operations, go-live checks, and the operator worksheet, use the dedicated guide: [Operate the Teams Meeting Pipeline](/docs/guides/operate-teams-meeting-pipeline).
For day-2 operations, go-live checks, and the operator worksheet, use the dedicated guide: [Operate the Teams Meeting Pipeline](/guides/operate-teams-meeting-pipeline).
## What This Feature Does
@@ -38,7 +42,7 @@ hermes teams-pipeline maintain-subscriptions
Before enabling the meetings pipeline, make sure you have:
- a working Hermes install
- the existing [Microsoft Teams bot setup](/docs/user-guide/messaging/teams) if you want Teams outbound delivery
- the existing [Microsoft Teams bot setup](/user-guide/messaging/teams) if you want Teams outbound delivery
- Microsoft Graph application credentials with the permissions required for the meeting resources you plan to subscribe to
- a public HTTPS URL that Microsoft Graph can call for webhook delivery
- `ffmpeg` installed if you want recording-plus-STT fallback
@@ -65,6 +69,7 @@ The webhook listener is a gateway platform named `msgraph_webhook`. At minimum,
```bash
MSGRAPH_WEBHOOK_ENABLED=true
MSGRAPH_WEBHOOK_HOST=127.0.0.1
MSGRAPH_WEBHOOK_PORT=8646
MSGRAPH_WEBHOOK_CLIENT_STATE=<random-shared-secret>
MSGRAPH_WEBHOOK_ACCEPTED_RESOURCES=communications/onlineMeetings
@@ -91,6 +96,7 @@ platforms:
msgraph_webhook:
enabled: true
extra:
host: 127.0.0.1
port: 8646
client_state: "replace-me"
accepted_resources:
@@ -120,6 +126,8 @@ platforms:
enabled: false
```
If you bind the listener to a non-loopback host such as `0.0.0.0`, you must also set `allowed_source_cidrs` to Microsoft's webhook egress ranges. Loopback binds (`127.0.0.1` / `::1`) are the intended dev-tunnel and local reverse-proxy setup.
## Teams Delivery Modes
The pipeline supports two Teams summary-delivery modes inside the existing Teams plugin.
@@ -196,11 +204,11 @@ hermes teams-pipeline subscribe \
:::warning Graph subscriptions expire in 72 hours
Microsoft Graph caps webhook subscriptions at 72 hours and will not auto-renew them. You MUST schedule `hermes teams-pipeline maintain-subscriptions` before going live, or notifications will silently stop three days after any manual subscription creation. See [Automating subscription renewal](/docs/guides/operate-teams-meeting-pipeline#automating-subscription-renewal-required-for-production) in the operator runbook — three options (Hermes cron, systemd timer, plain crontab).
Microsoft Graph caps webhook subscriptions at 72 hours and will not auto-renew them. You MUST schedule `hermes teams-pipeline maintain-subscriptions` before going live, or notifications will silently stop three days after any manual subscription creation. See [Automating subscription renewal](/guides/operate-teams-meeting-pipeline#automating-subscription-renewal-required-for-production) in the operator runbook — three options (Hermes cron, systemd timer, plain crontab).
:::
For subscription maintenance and day-2 operator flows, continue with the guide: [Operate the Teams Meeting Pipeline](/docs/guides/operate-teams-meeting-pipeline).
For subscription maintenance and day-2 operator flows, continue with the guide: [Operate the Teams Meeting Pipeline](/guides/operate-teams-meeting-pipeline).
## Validation
@@ -229,5 +237,5 @@ hermes teams-pipeline subscriptions
## Related Docs
- [Microsoft Teams bot setup](/docs/user-guide/messaging/teams)
- [Operate the Teams Meeting Pipeline](/docs/guides/operate-teams-meeting-pipeline)
- [Microsoft Teams bot setup](/user-guide/messaging/teams)
- [Operate the Teams Meeting Pipeline](/guides/operate-teams-meeting-pipeline)
+7 -5
View File
@@ -8,7 +8,9 @@ description: "Set up Hermes Agent as a Microsoft Teams bot"
Connect Hermes Agent to Microsoft Teams as a bot. Unlike Slack's Socket Mode, Teams delivers messages by calling a **public HTTPS webhook**, so your instance needs a publicly reachable endpoint — either a dev tunnel (local dev) or a real domain (production).
Need meeting summaries from Microsoft Graph events rather than normal bot conversations? Use the dedicated setup page: [Teams Meetings](/docs/user-guide/messaging/teams-meetings).
Need meeting summaries from Microsoft Graph events rather than normal bot conversations? Use the dedicated setup page: [Teams Meetings](/user-guide/messaging/teams-meetings).
> Run `hermes gateway setup` and pick **Microsoft Teams** for a guided walk-through.
## How the Bot Responds
@@ -168,7 +170,7 @@ Clicking a button resolves the approval inline and replaces the card with the de
### Meeting Summary Delivery (Teams Meeting Pipeline)
When the [Teams meeting pipeline plugin](/docs/user-guide/messaging/msgraph-webhook) is enabled, this adapter also handles outbound delivery of meeting summaries — one Teams integration surface, not two. After a meeting's transcript is summarized, the writer posts the summary into your chosen Teams target.
When the [Teams meeting pipeline plugin](/user-guide/messaging/msgraph-webhook) is enabled, this adapter also handles outbound delivery of meeting summaries — one Teams integration surface, not two. After a meeting's transcript is summarized, the writer posts the summary into your chosen Teams target.
Pipeline summary delivery is configured under the `teams` platform entry alongside the bot config:
@@ -193,7 +195,7 @@ platforms:
| Mode | Use when | Trade-off |
|------|----------|-----------|
| `incoming_webhook` | Simple "post a summary into this channel" with a static Teams-generated URL. | No reply threading, no reactions, shows as the webhook's configured identity. |
| `graph` | Threaded channel posts or 1:1/group chat posts under the bot's identity via Microsoft Graph. | Requires the [Graph app registration](/docs/guides/microsoft-graph-app-registration) with `ChannelMessage.Send` (channel) or `Chat.ReadWrite.All` (chat) application permissions. |
| `graph` | Threaded channel posts or 1:1/group chat posts under the bot's identity via Microsoft Graph. | Requires the [Graph app registration](/guides/microsoft-graph-app-registration) with `ChannelMessage.Send` (channel) or `Chat.ReadWrite.All` (chat) application permissions. |
If the `teams_pipeline` plugin is **not** enabled, these settings are inert — they only wire up when the pipeline runtime binds to the Graph webhook ingress.
@@ -248,5 +250,5 @@ Treat `TEAMS_CLIENT_SECRET` like a password — rotate it periodically via the A
## Related Docs
- [Teams Meetings](/docs/user-guide/messaging/teams-meetings)
- [Operate the Teams Meeting Pipeline](/docs/guides/operate-teams-meeting-pipeline)
- [Teams Meetings](/user-guide/messaging/teams-meetings)
- [Operate the Teams Meeting Pipeline](/guides/operate-teams-meeting-pipeline)
+12 -4
View File
@@ -319,7 +319,7 @@ With STT disabled, the gateway still downloads the voice/audio attachment into H
Your tools or skills can then read that path directly (e.g., hand it off to a local diarization pipeline, a richer transcription model, or upload it to long-term storage). The file extension reflects the original format Telegram delivered (`.ogg` for voice notes, `.mp3`/`.m4a`/etc. for audio attachments).
This pairs naturally with the [local Bot API server](#large-files-20mb--via-local-bot-api-server) section below, which lifts Telegram's 20MB getFile ceiling to 2GB — useful when the recordings you want to process are longer than a couple of minutes.
This pairs naturally with the [local Bot API server](#large-files-20mb-via-local-bot-api-server) section below, which lifts Telegram's 20MB getFile ceiling to 2GB — useful when the recordings you want to process are longer than a couple of minutes.
### Outgoing Voice (Text-to-Speech)
@@ -876,9 +876,9 @@ When streaming is enabled (`gateway.streaming.enabled: true`), Hermes picks one
| Value | Behaviour |
|---|---|
| `auto` | Native draft streaming on supported chats (currently Telegram DMs); legacy edit-based path otherwise. Falls back gracefully if a draft frame fails. |
| `auto` (default) | Native draft streaming on supported chats (currently Telegram DMs); legacy edit-based path otherwise. Falls back gracefully if a draft frame fails. |
| `draft` | Force native drafts. Logs a downgrade and falls back to edit if the chat doesn't support drafts (e.g. groups/topics). |
| `edit` (default) | Legacy progressive `editMessageText` polling for every chat type. |
| `edit` | Legacy progressive `editMessageText` polling for every chat type. |
| `off` | Disable streaming entirely (final reply only, no progressive updates). |
In `~/.hermes/config.yaml`:
@@ -887,7 +887,7 @@ In `~/.hermes/config.yaml`:
gateway:
streaming:
enabled: true
transport: edit # edit | auto | draft | off
transport: auto # auto | draft | edit | off
```
**What you'll see in DMs with `edit` (default)** — the gateway sends a normal preview message and progressively updates it via `editMessageText`, avoiding Telegram's draft-preview collapse/rollback effect.
@@ -1233,6 +1233,14 @@ HERMES_TELEGRAM_NOTIFICATIONS=all
Unknown values log a warning and fall back to `important`.
## Status messages edited in place
The Telegram adapter routes recurring agent status callbacks (e.g. "Compressing context…", "Calling tool…") through `send_or_update_status()`, which keeps a `{(chat_id, status_key) → message_id}` cache and **edits the existing bubble** on subsequent emits instead of appending a new one each time. Distinct `status_key` values get their own messages; distinct chats never collide. If the edit fails (e.g. the user deleted the message, or it's older than Telegram allows for edits), the cache entry is dropped and the next emit posts a fresh message and re-caches its ID. No config required — this is the default Telegram behavior. Other adapters that don't implement `send_or_update_status` fall through to plain `send()` unchanged.
## Pin incoming user message during agent turn
When a user sends a message that triggers an agent turn, the Telegram adapter pins that incoming message for the duration of the turn and unpins it when the response is finished — a lightweight visual indicator that the bot is actively working on the message rather than ignoring it. The pin uses `disable_notification=true` to avoid extra pings. No config required.
## Security
:::warning
@@ -12,6 +12,10 @@ Hermes supports two WeCom integration modes:
- **WeCom Callback** (this page) — self-built app, receives encrypted XML callbacks. Shows as a first-class app in users' WeCom sidebar. Supports multi-corp routing.
:::
See also: [WeCom Bot](./wecom.md) for the bot-style integration.
> Run `hermes gateway setup` and pick **WeCom Callback** for a guided walk-through.
## How It Works
1. You register a self-built application in the WeCom Admin Console
@@ -147,3 +151,28 @@ The crypto implementation is compatible with Tencent's official WXBizMsgCrypt SD
- **No typing indicators** — the callback model doesn't support typing status
- **Text only** — currently supports text messages for input; image/file/voice input not yet implemented. The agent is aware of outbound media capabilities via the WeCom platform hint (images, documents, video, voice).
- **Response latency** — agent sessions take 330 minutes; users see the reply when processing completes
## Troubleshooting
**Signature verification failing.**
WeCom signs every request with the **Token** you registered in the admin
console. A mismatch between the token configured in Hermes and the token the
admin console expects is the most common cause. Re-copy both the **Token** and
**EncodingAESKey** from the admin console — they're easy to truncate. Whitespace
in `~/.hermes/.env` values around `=` will also break signature checks. After
fixing, restart `hermes gateway run`.
**Callback URL not reachable / verification step fails.**
WeCom hits the public URL you registered. Confirm:
1. Your reverse proxy / tunnel forwards `/wecom/callback` to the gateway's port.
2. The URL in the admin console is HTTPS (WeCom rejects plain HTTP).
3. From outside your network, `curl -i https://<your-domain>/wecom/callback`
returns something other than a timeout (a 4xx without query params is fine —
it just means the listener is reachable).
**Port not reachable / listener not bound.**
Check `hermes gateway run` logs for the bound host/port. If the adapter bound to
`127.0.0.1` you must front it with a reverse proxy or tunnel — WeCom's servers
can't reach loopback. Set `extra.host: 0.0.0.0` in `config.yaml` (plus
`allowed_source_cidrs` if exposing directly) or keep loopback and use a tunnel
such as Cloudflare Tunnel / nginx.
+13 -4
View File
@@ -8,6 +8,8 @@ description: "Connect Hermes Agent to WeCom via the AI Bot WebSocket gateway"
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.
See also: [WeCom Callback](./wecom-callback.md) for inbound webhook setup.
## Prerequisites
- A WeCom organization account
@@ -90,9 +92,16 @@ hermes gateway
- **AES-encrypted media** — automatic decryption for inbound attachments
- **Quote context** — preserves reply threading
- **Markdown rendering** — rich text responses
- **Reply-mode streaming** — correlates responses to inbound message context
- **Reply correlation** — responses are correlated to the inbound message context
- **Auto-reconnect** — exponential backoff on connection drops
:::note Streaming and typing indicators
The WeCom adapter delivers each response as a single complete message — it does
**not** stream responses token-by-token, and it does **not** show a typing
indicator. "Reply correlation" (below) only threads a response to its inbound
request; it is not live streaming.
:::
## Configuration Options
Set these in `config.yaml` under `platforms.wecom.extra`:
@@ -224,11 +233,11 @@ No configuration is needed — decryption happens transparently when encrypted m
Files exceeding the absolute 20 MB limit are rejected with an informational message sent to the chat.
## Reply-Mode Stream Responses
## Reply-Mode Responses
When the bot receives a message via the WeCom callback, the adapter remembers the inbound request ID. If a response is sent while the request context is still active, the adapter uses WeCom's reply-mode (`aibot_respond_msg`) with streaming to correlate the response directly to the inbound message. This provides a more natural conversation experience in the WeCom client.
When the bot receives a message via the WeCom callback, the adapter remembers the inbound request ID. If a response is sent while the request context is still active, the adapter uses WeCom's reply-mode (`aibot_respond_msg`) to correlate the response directly to the inbound message. This provides a more natural conversation experience in the WeCom client.
If the inbound request context has expired or is unavailable, the adapter falls back to proactive message sending via `aibot_send_msg`.
The full response is delivered as a single message — the adapter does not stream tokens incrementally. If the inbound request context has expired or is unavailable, the adapter falls back to proactive message sending via `aibot_send_msg`.
Reply-mode also works for media: uploaded media can be sent as a reply to the originating message.
@@ -123,6 +123,8 @@ Set these in `config.yaml` under `platforms.weixin.extra`:
| `allow_from` | `[]` | User IDs allowed for DMs (when dm_policy=allowlist) |
| `group_allow_from` | `[]` | Group IDs allowed (when group_policy=allowlist) |
| `split_multiline_messages` | `false` | When `true`, split multi-line replies into multiple chat messages (legacy behavior). When `false`, keep multi-line replies as one message unless they exceed the length limit. |
| `text_batch_delay_seconds` | `3.0` | Quiet period (seconds) before a buffered burst of rapid text messages is flushed as one combined request. iLink delivers messages individually, so this debounce avoids one agent invocation per fragment. Set `0` to dispatch each message immediately. |
| `text_batch_split_delay_seconds` | `5.0` | Extended flush delay used when the latest fragment is near the split threshold (long messages iLink may have chunked). |
## Access Policies
@@ -142,6 +144,25 @@ WEIXIN_DM_POLICY=allowlist
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2
```
`WEIXIN_ALLOWED_USERS` is an **inbound filter**, not an invitation system. QR
login connects one iLink bot identity to Hermes. Other people do not scan the
Hermes QR code with their own accounts; they must message the connected iLink
bot/contact through WeChat, and Hermes will process the DM only if the sender's
Weixin user ID is present in `WEIXIN_ALLOWED_USERS`.
A practical setup flow is:
1. Pair Hermes once with `hermes gateway setup` and note the connected iLink bot
account.
2. Have each allowed user send a direct message to that bot/contact.
3. Read the sender/user ID from the gateway logs or the inbound event payload.
4. Add those IDs to `WEIXIN_ALLOWED_USERS`, then restart the gateway.
If only the account that scanned the QR code can talk to Hermes, verify that the
other users are messaging the iLink bot identity itself, not the personal WeChat
account that performed the QR login. The iLink bot is a separate identity, and
ordinary WeChat contact/group routing can be limited by Tencent's iLink behavior.
### Group Policy
Controls which groups the bot responds in **when iLink delivers group events for the connected identity**. For QR-login iLink bot identities (e.g. `...@im.bot`), group events are typically not delivered at all, so this policy may have no effect — see the iLink bot limitation warning at the top of the page.
+20 -2
View File
@@ -8,6 +8,8 @@ description: "Set up Hermes Agent as a WhatsApp bot via the built-in Baileys bri
Hermes connects to WhatsApp through a built-in bridge based on **Baileys**. This works by emulating a WhatsApp Web session — **not** through the official WhatsApp Business API. No Meta developer account or Business verification is required.
> Run `hermes gateway setup` and pick **WhatsApp** for a guided walk-through.
:::tip Two WhatsApp integrations
This page is for the **Baileys bridge** — quick to set up, personal accounts, no public URL needed, ban risk.
@@ -111,9 +113,9 @@ WHATSAPP_ALLOWED_USERS=15551234567 # Comma-separated phone numbers (with
:::tip Allow-all shorthand
Setting `WHATSAPP_ALLOWED_USERS=*` allows **all** senders (equivalent to `WHATSAPP_ALLOW_ALL_USERS=true`).
This is consistent with [Signal group allowlists](/docs/reference/environment-variables).
This is consistent with [Signal group allowlists](/reference/environment-variables).
To use the pairing flow instead, remove both variables and rely on the
[DM pairing system](/docs/user-guide/security#dm-pairing-system).
[DM pairing system](/user-guide/security#dm-pairing-system).
:::
Optional behavior settings in `~/.hermes/config.yaml`:
@@ -207,6 +209,22 @@ Code blocks and inline code are preserved as-is since WhatsApp supports triple-b
When the agent calls tools (web search, file operations, etc.), WhatsApp displays real-time progress indicators showing which tool is running. This is enabled by default — no configuration needed.
### Message Batching (Debounce)
WhatsApp delivers each message individually, so a rapid burst (forwarded batches, paste-splits, multi-line text) would otherwise trigger a separate agent invocation per fragment — wasting tokens and producing several disjointed replies. The adapter buffers successive text messages from the same chat and dispatches them as one combined request after a short quiet period (default **5s**, extended to **10s** for very long fragments). Tune via `config.yaml`:
```yaml
# ~/.hermes/config.yaml
gateway:
platforms:
whatsapp:
extra:
text_batch_delay_seconds: 5.0 # quiet period before flushing a batch
text_batch_split_delay_seconds: 10.0 # extended delay near the split threshold
```
Set `text_batch_delay_seconds: 0` to dispatch each message immediately (disables batching).
---
## Troubleshooting
+3 -3
View File
@@ -336,6 +336,6 @@ hermes chat -q "Send 'Hello from CLI' to yuanbao:group:group_code"
## Related Documentation
- [Messaging Gateway Overview](./index.md)
- [Slash Commands Reference](/docs/reference/slash-commands.md)
- [Cron Jobs](/docs/user-guide/features/cron.md)
- [Background Sessions](/docs/user-guide/cli#background-sessions)
- [Slash Commands Reference](/reference/slash-commands)
- [Cron Jobs](/user-guide/features/cron)
- [Background Sessions](/user-guide/cli#background-sessions)