Merge branch 'main' of github.com:NousResearch/hermes-agent into bb/gui
This commit is contained in:
@@ -17,6 +17,9 @@ Cron jobs can:
|
||||
- attach zero, one, or multiple skills to a job
|
||||
- deliver results back to the origin chat, local files, or configured platform targets
|
||||
- run in fresh agent sessions with the normal static tool list
|
||||
- run in **no-agent mode** — a script on a schedule, its stdout delivered verbatim, zero LLM involvement (see the [no-agent mode](#no-agent-mode-script-only-jobs) section below)
|
||||
|
||||
All of this is available to Hermes itself through the `cronjob` tool, so you can create, pause, edit, and remove jobs by asking in plain language — no CLI required.
|
||||
|
||||
:::warning
|
||||
Cron-run sessions cannot recursively create more cron jobs. Hermes disables cron management tools inside cron executions to prevent runaway scheduling loops.
|
||||
@@ -286,6 +289,48 @@ cron:
|
||||
|
||||
Or set the `HERMES_CRON_SCRIPT_TIMEOUT` environment variable. The resolution order is: env var → config.yaml → 120s default.
|
||||
|
||||
## No-agent mode (script-only jobs)
|
||||
|
||||
For recurring jobs that don't need LLM reasoning — classic watchdogs, disk/memory alerts, heartbeats, CI pings — pass `no_agent=True` at creation time. The scheduler runs your script on schedule and delivers its stdout directly, skipping the agent entirely:
|
||||
|
||||
```bash
|
||||
hermes cron create "every 5m" \
|
||||
--no-agent \
|
||||
--script memory-watchdog.sh \
|
||||
--deliver telegram \
|
||||
--name "memory-watchdog"
|
||||
```
|
||||
|
||||
Semantics:
|
||||
|
||||
- Script stdout (trimmed) → delivered verbatim as the message.
|
||||
- **Empty stdout → silent tick**, no delivery. This is the watchdog pattern: "only say something when something is wrong".
|
||||
- Non-zero exit or timeout → an error alert is delivered, so a broken watchdog can't fail silently.
|
||||
- `{"wakeAgent": false}` on the last line → silent tick (same gate LLM jobs use).
|
||||
- No tokens, no model, no provider fallback — the job never touches the inference layer.
|
||||
|
||||
`.sh` / `.bash` files run under `/bin/bash`; anything else under the current Python interpreter (`sys.executable`). Scripts must live in `~/.hermes/scripts/` (same sandboxing rule as the pre-run script gate).
|
||||
|
||||
### The agent sets these up for you
|
||||
|
||||
The `cronjob` tool's schema exposes `no_agent` to Hermes directly, so you can describe a watchdog in chat and let the agent wire it up:
|
||||
|
||||
```text
|
||||
Ping me on Telegram if RAM is over 85%, every 5 minutes.
|
||||
```
|
||||
|
||||
Hermes will write the check script to `~/.hermes/scripts/` via `write_file`, then call:
|
||||
|
||||
```python
|
||||
cronjob(action="create", schedule="every 5m",
|
||||
script="memory-watchdog.sh", no_agent=True,
|
||||
deliver="telegram", name="memory-watchdog")
|
||||
```
|
||||
|
||||
It picks `no_agent=True` automatically when the message content is fully determined by the script (watchdogs, threshold alerts, heartbeats). The same tool also lets the agent pause, resume, edit, and remove jobs — so the whole lifecycle is chat-driven without anyone touching the CLI.
|
||||
|
||||
See the [Script-Only Cron Jobs guide](/docs/guides/cron-script-only) for worked examples.
|
||||
|
||||
## Provider recovery
|
||||
|
||||
Cron jobs inherit your configured fallback providers and credential pool rotation. If the primary API key is rate-limited or the provider returns an error, the cron agent can:
|
||||
|
||||
@@ -396,6 +396,130 @@ For example, a topic with `skill: arxiv` will have the arxiv skill pre-loaded wh
|
||||
Topics created outside of the config (e.g., by manually calling the Telegram API) are discovered automatically when a `forum_topic_created` service message arrives. You can also add topics to the config while the gateway is running — they'll be picked up on the next cache miss.
|
||||
:::
|
||||
|
||||
## Multi-session DM mode (`/topic`)
|
||||
|
||||
A ChatGPT-style multi-session DM — one bot, many parallel conversations. Unlike the operator-curated `extra.dm_topics` above, this mode is **user-driven**: no config, no pre-declared topic names. The end user flips it on with `/topic`, then taps the Telegram **+** button to create as many topics as they want, each one a fully independent Hermes session.
|
||||
|
||||
### `/topic` subcommands
|
||||
|
||||
| Form | Context | Effect |
|
||||
|------|---------|--------|
|
||||
| `/topic` | Root DM, not yet enabled | Check BotFather capabilities, enable multi-session mode, create pinned System topic |
|
||||
| `/topic` | Root DM, already enabled | Show status: unlinked sessions available for restore |
|
||||
| `/topic` | Inside a topic | Show the current topic's session binding |
|
||||
| `/topic help` | Any | Inline usage |
|
||||
| `/topic off` | Root DM | Disable multi-session mode and clear all topic bindings for this chat |
|
||||
| `/topic <session-id>` | Inside a topic | Restore a previous Telegram session into the current topic |
|
||||
|
||||
Only authorized users (allowlist via `TELEGRAM_ALLOWED_USERS` / platform auth config) can run `/topic`. An unauthorized sender gets a refusal instead of activation.
|
||||
|
||||
### DM Topics vs Multi-session DM mode
|
||||
|
||||
| | `extra.dm_topics` (config-driven) | `/topic` (user-driven) |
|
||||
|---|---|---|
|
||||
| Who activates it | Operator, in `config.yaml` | End user, by sending `/topic` |
|
||||
| Topic list | Fixed set declared in config | User creates/deletes topics freely |
|
||||
| Topic names | Chosen by operator | Chosen by user; auto-renamed to match Hermes session title |
|
||||
| Root DM behavior | Unchanged — normal chat | Becomes a system lobby (non-command messages are rejected) |
|
||||
| Primary use case | Permanent workspaces with optional skill binding | Ad-hoc parallel sessions |
|
||||
| Persistence | `extra.dm_topics` in config | `telegram_dm_topic_mode` + `telegram_dm_topic_bindings` SQLite tables |
|
||||
|
||||
Both features can coexist on the same bot — you'd run `/topic` from a user's DM, and `extra.dm_topics` continues to manage operator-declared topics for other chats.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
In **@BotFather**, open your bot → **Bot Settings → Threads Settings**:
|
||||
|
||||
1. Turn on **Threaded Mode** (enables `has_topics_enabled`)
|
||||
2. Do **not** disable users creating topics (keeps `allows_users_to_create_topics` on)
|
||||
|
||||
When the user first runs `/topic`, Hermes calls `getMe` to verify both flags. If either is off, Hermes sends a screenshot of the BotFather Threads Settings page and explains what to toggle — no activation happens until prerequisites are met.
|
||||
|
||||
### Activation flow
|
||||
|
||||
From the root DM, send:
|
||||
|
||||
```
|
||||
/topic
|
||||
```
|
||||
|
||||
Hermes will:
|
||||
|
||||
1. Check `getMe().has_topics_enabled` and `allows_users_to_create_topics`
|
||||
2. If both are true, enable multi-session topic mode for this DM
|
||||
3. Create and pin a **System** topic for status/commands (best-effort)
|
||||
4. Reply with a list of previous unlinked Telegram sessions the user can restore
|
||||
|
||||
After activation, the **root DM is a lobby**: normal prompts are rejected with guidance pointing at **All Messages**. System commands (`/status`, `/sessions`, `/usage`, `/help`, etc.) still work in the root.
|
||||
|
||||
### Creating a new topic (end-user flow)
|
||||
|
||||
1. Open the bot DM in Telegram
|
||||
2. Tap **All Messages** at the top of the bot interface, then send any message
|
||||
3. Telegram creates a new topic for that message
|
||||
4. Hermes responds inside that topic — the topic is now a standalone session
|
||||
|
||||
Every topic gets its own conversation history, model state, tool execution, and session ID. The isolation key is `agent:main:telegram:dm:{chat_id}:{thread_id}` — identical to the config-driven DM topics isolation.
|
||||
|
||||
### Auto-renamed topics
|
||||
|
||||
When Hermes generates a session title for a topic (via the auto-title pipeline, after the first exchange), the Telegram topic itself is renamed to match — e.g. "New Topic" becomes "Database migration plan". The rename is best-effort: failures are logged but don't break the session.
|
||||
|
||||
### `/new` inside a topic
|
||||
|
||||
Resets the current topic's session (new session ID, fresh history) without touching other topics. Hermes replies with a reminder that for parallel work, creating another topic (via **All Messages**) is usually what you want.
|
||||
|
||||
### Restoring a previous session
|
||||
|
||||
Inside a topic, send:
|
||||
|
||||
```
|
||||
/topic <session-id>
|
||||
```
|
||||
|
||||
This binds the current topic to an existing Hermes session instead of starting fresh. Useful for continuing a conversation that started before topic mode was enabled. Restrictions:
|
||||
|
||||
- The target session must belong to the same Telegram user
|
||||
- The target session must not already be bound to another topic
|
||||
|
||||
Hermes confirms with the session title and replays the last assistant message for context.
|
||||
|
||||
To discover session IDs, send `/topic` (no argument) in the root DM — Hermes lists the user's unlinked Telegram sessions.
|
||||
|
||||
### `/topic` inside a topic (no argument)
|
||||
|
||||
Shows the current topic's binding: session title, session ID, and hints for `/new` vs creating another topic.
|
||||
|
||||
### Under the hood
|
||||
|
||||
- Activation persists to `telegram_dm_topic_mode(chat_id, user_id, enabled, ...)` in `state.db`
|
||||
- Each topic binding persists to `telegram_dm_topic_bindings(chat_id, thread_id, session_id, ...)` with `ON DELETE CASCADE` on `session_id` — pruning a session automatically clears its topic binding
|
||||
- The topic-mode SQLite migration is **opt-in**: it runs on the first `/topic` call, never on gateway startup. Until a user runs `/topic` in this profile, `state.db` is unchanged
|
||||
- Each inbound DM message looks up its `(chat_id, thread_id)` binding. If present, the lookup routes the message to the bound session via `SessionStore.switch_session()` so the session-key-to-session-id mapping stays consistent on disk
|
||||
- `/new` inside a topic rewrites the binding row to point at the new session ID, so the next message stays on the fresh session
|
||||
- Topics declared in `extra.dm_topics` are **never auto-renamed** — the operator-chosen name is preserved even when multi-session mode is enabled
|
||||
- The General (pinned top) topic in a forum-enabled DM is treated as the root lobby, regardless of whether Telegram delivers its messages with `message_thread_id=1` or with no thread_id
|
||||
- Root-lobby reminders are rate-limited to one message per 30 seconds per chat — a user who forgets topic mode is on and types ten prompts in the root won't get ten replies
|
||||
- BotFather setup screenshots are rate-limited to one send per 5 minutes per chat — repeated `/topic` attempts while Threads Settings are still disabled won't re-upload the same image
|
||||
- `/background <prompt>` started inside a topic delivers its result back to the same topic; background sessions don't trigger auto-rename of the owning topic
|
||||
- `/topic` itself is gated by the bot's user authorization check — unauthorized DMs get a refusal instead of activation
|
||||
|
||||
### Disabling multi-session mode
|
||||
|
||||
Send `/topic off` in the root DM. Hermes flips the row off, clears the chat's `(thread_id → session_id)` bindings, and the root DM reverts to a normal Hermes chat. Existing topics in Telegram aren't deleted — they just stop being gated as independent sessions. Re-run `/topic` later to turn it back on.
|
||||
|
||||
If you need to clean up by hand (e.g. a bulk reset across many chats), remove the rows directly:
|
||||
|
||||
```bash
|
||||
sqlite3 ~/.hermes/state.db \
|
||||
"UPDATE telegram_dm_topic_mode SET enabled = 0 WHERE chat_id = '<your_chat_id>'; \
|
||||
DELETE FROM telegram_dm_topic_bindings WHERE chat_id = '<your_chat_id>';"
|
||||
```
|
||||
|
||||
### Downgrading Hermes
|
||||
|
||||
If you downgrade to a Hermes version that predates `/topic`, the feature simply stops working — the `telegram_dm_topic_mode` and `telegram_dm_topic_bindings` tables remain in `state.db` but are ignored by older code. DMs revert to the native per-thread isolation (each `message_thread_id` still gets its own session via `build_session_key`), so your existing Telegram topics keep working as parallel sessions. The root DM is no longer a lobby — messages there go into the agent like they used to. Re-upgrading reactivates multi-session mode exactly where it was.
|
||||
|
||||
## Group Forum Topic Skill Binding
|
||||
|
||||
Supergroups with **Topics mode** enabled (also called "forum topics") already get session isolation per topic — each `thread_id` maps to its own conversation. But you may want to **auto-load a skill** when messages arrive in a specific group topic, just like DM topic skill binding works.
|
||||
@@ -463,7 +587,7 @@ To find a topic's `thread_id`, open the topic in Telegram Web or Desktop and loo
|
||||
|
||||
## Recent Bot API Features
|
||||
|
||||
- **Bot API 9.4 (Feb 2026):** Private Chat Topics — bots can create forum topics in 1-on-1 DM chats via `createForumTopic`. See [Private Chat Topics](#private-chat-topics-bot-api-94) above.
|
||||
- **Bot API 9.4 (Feb 2026):** Private Chat Topics — bots can create forum topics in 1-on-1 DM chats via `createForumTopic`. Hermes uses this for two distinct features: operator-curated [Private Chat Topics](#private-chat-topics-bot-api-94) (config-driven, fixed topic list) and user-driven [Multi-session DM mode](#multi-session-dm-mode-topic) (activated by `/topic`, unlimited user-created topics).
|
||||
- **Privacy policy:** Telegram now requires bots to have a privacy policy. Set one via BotFather with `/setprivacy_policy`, or Telegram may auto-generate a placeholder. This is particularly important if your bot is public-facing.
|
||||
- **Message streaming:** Bot API 9.x added support for streaming long responses, which can improve perceived latency for lengthy agent replies.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user