diff --git a/website/docs/developer-guide/adding-platform-adapters.md b/website/docs/developer-guide/adding-platform-adapters.md index 51b3476094e..db2b9e988b5 100644 --- a/website/docs/developer-guide/adding-platform-adapters.md +++ b/website/docs/developer-guide/adding-platform-adapters.md @@ -77,7 +77,7 @@ class MyPlatformAdapter(BasePlatformAdapter): extra = config.extra or {} self.token = os.getenv("MY_PLATFORM_TOKEN") or extra.get("token", "") - async def connect(self) -> bool: + async def connect(self, *, is_reconnect: bool = False) -> bool: # Connect to the platform API, start listeners self._mark_connected() return True @@ -469,7 +469,7 @@ This checklist is for adding a platform directly to the Hermes core codebase — Add your platform to the `Platform` enum in `gateway/config.py`: ```python -class Platform(str, Enum): +class Platform(Enum): # ... existing platforms ... NEWPLAT = "newplat" ``` @@ -495,7 +495,7 @@ class NewPlatAdapter(BasePlatformAdapter): extra = config.extra or {} self._api_key = extra.get("api_key") or os.getenv("NEWPLAT_API_KEY", "") - async def connect(self) -> bool: + async def connect(self, *, is_reconnect: bool = False) -> bool: # Set up connection, start polling/webhook self._mark_connected() return True @@ -541,7 +541,7 @@ Three touchpoints: ### 4. Gateway Runner (`gateway/run.py`) -Five touchpoints: +Six touchpoints: 1. **`_create_adapter()`** — Add an `elif platform == Platform.NEWPLAT:` branch 2. **`_is_user_authorized()` allowed_users map** — `Platform.NEWPLAT: "NEWPLAT_ALLOWED_USERS"` @@ -576,10 +576,10 @@ Five touchpoints: ### 9. Optional: Platform Hints -**`agent/prompt_builder.py`** — If your platform has specific rendering limitations (no markdown, message length limits, etc.), add an entry to the `_PLATFORM_HINTS` dict. This injects platform-specific guidance into the system prompt: +**`agent/prompt_builder.py`** — If your platform has specific rendering limitations (no markdown, message length limits, etc.), add an entry to the `PLATFORM_HINTS` dict. This injects platform-specific guidance into the system prompt: ```python -_PLATFORM_HINTS = { +PLATFORM_HINTS = { # ... "newplat": ( "You are chatting via NewPlat. It supports markdown formatting " @@ -672,8 +672,9 @@ If the adapter holds a persistent connection with a unique credential, add a sco ```python from gateway.status import acquire_scoped_lock, release_scoped_lock -async def connect(self): - if not acquire_scoped_lock("newplat", self._token): +async def connect(self, *, is_reconnect: bool = False): + acquired, _existing = acquire_scoped_lock("newplat", self._token) + if not acquired: logger.error("Token already in use by another profile") return False # ... connect @@ -688,5 +689,5 @@ async def disconnect(self): |---------|---------|------------|-------------------| | `bluebubbles.py` | REST + webhook | Medium | Simple REST API integration | | `weixin.py` | Long-poll + CDN | High | Media handling, encryption | -| `wecom_callback.py` | Callback/webhook | Medium | HTTP server, AES crypto, multi-app | +| `plugins/platforms/wecom/callback_adapter.py` | Callback/webhook | Medium | HTTP server, AES crypto, multi-app | | `plugins/platforms/irc/adapter.py` | Long-poll + IRC protocol | High | Full-featured plugin adapter with scoped token lock | diff --git a/website/docs/developer-guide/architecture.md b/website/docs/developer-guide/architecture.md index 6d6ec8695ae..a235f886d5b 100644 --- a/website/docs/developer-guide/architecture.md +++ b/website/docs/developer-guide/architecture.md @@ -117,11 +117,13 @@ hermes-agent/ │ ├── mirror.py # Cross-session message mirroring │ ├── status.py # Token locks, profile-scoped process tracking │ ├── builtin_hooks/ # Extension point for always-registered hooks (none shipped) -│ └── platforms/ # 20 adapters: telegram, discord, slack, whatsapp, -│ # signal, matrix, mattermost, email, sms, -│ # dingtalk, feishu, wecom, wecom_callback, weixin, -│ # bluebubbles, qqbot, homeassistant, webhook, api_server, -│ # yuanbao +│ └── platforms/ # Built-in adapters: signal, weixin, bluebubbles, +│ # qqbot, whatsapp_cloud, yuanbao, webhook, api_server +│ +├── plugins/platforms/ # Bundled platform plugins: telegram, discord, slack, +│ # whatsapp, matrix, mattermost, email, sms, dingtalk, +│ # feishu, wecom, homeassistant, irc, line, teams, +│ # google_chat, buzz, ntfy, photon, raft, simplex │ ├── acp_adapter/ # ACP server (VS Code / Zed / JetBrains) ├── cron/ # Scheduler (jobs.py, scheduler.py) @@ -223,7 +225,7 @@ SQLite-based session storage with FTS5 full-text search. Sessions have lineage t ### Messaging Gateway -Long-running process with 20 platform adapters, unified session routing, user authorization (allowlists + DM pairing), slash command dispatch, hook system, cron ticking, and background maintenance. +Long-running process with 25+ platform adapters (built-in + bundled plugins), unified session routing, user authorization (allowlists + DM pairing), slash command dispatch, hook system, cron ticking, and background maintenance. → [Gateway Internals](./gateway-internals.md) diff --git a/website/docs/developer-guide/context-compression-and-caching.md b/website/docs/developer-guide/context-compression-and-caching.md index c27f371c396..0b71f4eb6d1 100644 --- a/website/docs/developer-guide/context-compression-and-caching.md +++ b/website/docs/developer-guide/context-compression-and-caching.md @@ -91,6 +91,7 @@ compression: codex_gpt55_autoraise: true # gpt-5.5 on Codex OAuth: raise trigger to 85% (default: true) codex_gpt55_autoraise_notice: true # Show the one-time autoraise notice (default: true) codex_app_server_auto: native # native|hermes|off for Codex app-server thread compaction + in_place: true # Compact on the same session id, no rotation (default: true) # Summarization model/provider configured under auxiliary: auxiliary: @@ -114,6 +115,18 @@ auxiliary: | `codex_gpt55_autoraise` | `true` | bool | Raise the trigger to 85% for gpt-5.5 on the ChatGPT Codex OAuth route (see below). Set `false` to keep the global `threshold` | | `codex_gpt55_autoraise_notice` | `true` | bool | Show the one-time Codex gpt-5.5 autoraise notice. Set `false` to keep the 85% autoraise but suppress the banner | | `codex_app_server_auto` | `native` | `native`, `hermes`, `off` | Thread-compaction mode for Codex app-server sessions (see below) | +| `in_place` | `true` | bool | Compact on the same session id instead of rotating to a new one (see below) | + +### In-place compaction (single stable session id) + +With `compression.in_place: true` (the default), a compaction **rewrites the live message list on the same session id**: the system prompt is rebuilt, the summarized middle is swapped in, and the pre-compaction turns are soft-archived under the same id (`active=0, compacted=1` in the session store) — still searchable via `session_search` and recoverable, never deleted. There is no `parent_session_id` chain and no `name #N` renumbering; one conversation keeps one durable id for its whole life. This eliminated the session-rotation bug cluster (lost `/goal` state, orphaned sessions, search gaps across boundaries). + +Consumers observe the mode rather than diffing session ids: + +- The `session:compress` event carries `in_place: true/false` and `old_session_id` (empty string in in-place mode, since there is no old id). +- The gateway re-baselines transcript handling from the agent's rotation-independent `_last_compaction_in_place` flag, not from an id-change diff. + +Set `in_place: false` to restore the legacy rotating path, where each compaction commits a new session id linked to the previous one via `parent_session_id`. ### Per-model threshold overrides diff --git a/website/docs/developer-guide/cron-internals.md b/website/docs/developer-guide/cron-internals.md index 36b2c6f7aed..13a342324cc 100644 --- a/website/docs/developer-guide/cron-internals.md +++ b/website/docs/developer-guide/cron-internals.md @@ -114,7 +114,7 @@ The active provider is chosen by the `cron.provider` config key: historical in-process loop calling `scheduler.tick()` every 60 seconds. This is byte-identical to the pre-provider behavior. - **a named provider** (e.g. `chronos`, a managed-cron provider for - scale-to-zero deployments) → discovered from `plugins/cron//` or + scale-to-zero deployments) → discovered from `plugins/cron_providers//` or `$HERMES_HOME/plugins//`. If a named provider is missing, fails to load, or reports `is_available() == diff --git a/website/docs/developer-guide/gateway-internals.md b/website/docs/developer-guide/gateway-internals.md index a097c7adfe4..b4eebf279c3 100644 --- a/website/docs/developer-guide/gateway-internals.md +++ b/website/docs/developer-guide/gateway-internals.md @@ -180,8 +180,8 @@ Experimental connector-backed platforms use the generic relay adapter in `gatewa Adapters implement a common interface: - `connect()` / `disconnect()` — lifecycle management -- `send_message()` — outbound message delivery -- `on_message()` — inbound message normalization → `MessageEvent` +- `send()` — outbound message delivery +- inbound events are normalized into a `MessageEvent` and forwarded via `handle_message()` ### Token Locks diff --git a/website/docs/developer-guide/image-gen-provider-plugin.md b/website/docs/developer-guide/image-gen-provider-plugin.md index 66179cfa5c0..44b50902951 100644 --- a/website/docs/developer-guide/image-gen-provider-plugin.md +++ b/website/docs/developer-guide/image-gen-provider-plugin.md @@ -6,7 +6,7 @@ description: "How to build an image-generation backend plugin for Hermes Agent" # Building an Image Generation Provider Plugin -Image-gen provider plugins register a backend that services every `image_generate` tool call — DALL·E, gpt-image, Grok, Flux, Imagen, Stable Diffusion, fal, Replicate, a local ComfyUI rig, anything. Built-in providers (OpenAI, OpenAI-Codex, xAI) all ship as plugins. You can add a new one, or override a bundled one, by dropping a directory into `plugins/image_gen//`. +Image-gen provider plugins register a backend that services every `image_generate` tool call — DALL·E, gpt-image, Grok, Flux, Imagen, Stable Diffusion, fal, Replicate, a local ComfyUI rig, anything. Built-in providers (OpenAI, OpenAI-Codex, xAI, FAL, Krea, DeepInfra, OpenRouter) all ship as plugins. You can add a new one, or override a bundled one, by dropping a directory into `plugins/image_gen//`. :::tip Image-gen is one of several **backend plugins** Hermes supports. The others (with more specialized ABCs) are [Memory Provider Plugins](/developer-guide/memory-provider-plugin), [Context Engine Plugins](/developer-guide/context-engine-plugin), and [Model Provider Plugins](/developer-guide/model-provider-plugin). General tool/hook/CLI plugins live in [Build a Hermes Plugin](/developer-guide/plugins). diff --git a/website/docs/developer-guide/memory-provider-plugin.md b/website/docs/developer-guide/memory-provider-plugin.md index c490fb2153f..f6a35dc060d 100644 --- a/website/docs/developer-guide/memory-provider-plugin.md +++ b/website/docs/developer-guide/memory-provider-plugin.md @@ -76,8 +76,8 @@ class MyMemoryProvider(MemoryProvider): |--------|-----------|----------| | `system_prompt_block()` | System prompt assembly | Static provider info | | `prefetch(query, *, session_id="")` | Before each API call | Return recalled context | -| `queue_prefetch(query)` | After each turn | Pre-warm for next turn | -| `sync_turn(user, assistant, *, session_id="")` | After each completed turn | Persist conversation | +| `queue_prefetch(query, *, session_id="")` | After each turn | Pre-warm for next turn | +| `sync_turn(user, assistant, *, session_id="", messages=None)` | After each completed turn | Persist conversation | | `on_session_end(messages)` | Conversation ends | Final extraction/flush | | `on_pre_compress(messages)` | Before context compression | Save insights before discard | | `on_memory_write(action, target, content)` | Built-in memory writes | Mirror to your backend | diff --git a/website/docs/developer-guide/model-provider-plugin.md b/website/docs/developer-guide/model-provider-plugin.md index 6c20a66be42..dd1be9f291b 100644 --- a/website/docs/developer-guide/model-provider-plugin.md +++ b/website/docs/developer-guide/model-provider-plugin.md @@ -136,11 +136,11 @@ class AcmeProfile(ProviderProfile): reasoning dict). Default: ({}, {}).""" return {}, {} - def fetch_models(self, *, api_key=None, timeout=8.0) -> list[str] | None: + def fetch_models(self, *, api_key=None, base_url=None, timeout=8.0) -> list[str] | None: """Live catalog fetch. Default hits {models_url or base_url}/models with Bearer auth. Override for: custom auth (Anthropic), no REST endpoint (Bedrock → None), or public/unauthenticated catalogs (OpenRouter).""" - return super().fetch_models(api_key=api_key, timeout=timeout) + return super().fetch_models(api_key=api_key, base_url=base_url, timeout=timeout) ``` ## Hook reference examples diff --git a/website/docs/developer-guide/plugin-llm-access.md b/website/docs/developer-guide/plugin-llm-access.md index b4e81547630..0d7959f519d 100644 --- a/website/docs/developer-guide/plugin-llm-access.md +++ b/website/docs/developer-guide/plugin-llm-access.md @@ -300,7 +300,8 @@ class PluginLlmCompleteResult: audit: Dict[str, Any] # plugin_id, purpose, profile @dataclass -class PluginLlmStructuredResult(PluginLlmCompleteResult): +class PluginLlmStructuredResult: + # same fields as PluginLlmCompleteResult, plus: parsed: Optional[Any] # JSON object when content_type == "json" content_type: str # "json" or "text" # audit also carries schema_name when supplied diff --git a/website/docs/developer-guide/plugins/index.md b/website/docs/developer-guide/plugins/index.md index 97bdf85690f..d1db3a9a267 100644 --- a/website/docs/developer-guide/plugins/index.md +++ b/website/docs/developer-guide/plugins/index.md @@ -577,7 +577,11 @@ def register(ctx): Without `override=True`, the registry rejects any registration that would shadow an existing tool from a different toolset — this prevents -accidental overwrites. The override is logged at INFO level so it's +accidental overwrites. Overriding a **built-in** tool additionally +requires the operator to opt in via +`plugins.entries..allow_tool_override: true` in `config.yaml`; +without that gate, `register_tool(override=True)` raises +`PluginToolOverrideError`. The override is logged so it's auditable in `~/.hermes/logs/agent.log`. Plugins load after built-in tools, so the registration order is correct: your handler replaces the built-in one. @@ -599,7 +603,7 @@ Each hook is documented in full on the **[Event Hooks reference](/user-guide/fea | Hook | Fires when | Callback signature | Returns | |------|-----------|-------------------|---------| -| [`pre_tool_call`](/user-guide/features/hooks#pre_tool_call) | Before any tool executes | `tool_name: str, args: dict, task_id: str` | ignored | +| [`pre_tool_call`](/user-guide/features/hooks#pre_tool_call) | Before any tool executes | `tool_name: str, args: dict, task_id: str` | optional directive: `{"action": "block", "message": ...}` vetoes the call; `{"action": "approve", "message": ...}` escalates to the human-approval gate | | [`post_tool_call`](/user-guide/features/hooks#post_tool_call) | After any tool returns | `tool_name: str, args: dict, result: str, task_id: str, duration_ms: int` | ignored | | [`pre_llm_call`](/user-guide/features/hooks#pre_llm_call) | Once per turn, before the tool-calling loop | `session_id: str, user_message: str, conversation_history: list, is_first_turn: bool, model: str, platform: str` | [context injection](#pre_llm_call-context-injection) | | [`post_llm_call`](/user-guide/features/hooks#post_llm_call) | Once per turn, after the tool-calling loop (successful turns only) | `session_id: str, user_message: str, assistant_response: str, conversation_history: list, model: str, platform: str` | ignored | @@ -611,7 +615,7 @@ Each hook is documented in full on the **[Event Hooks reference](/user-guide/fea | `kanban_task_completed` | A kanban task completes (worker process) | `task_id, board, assignee, run_id, profile_name, summary: str \| None` | ignored | | `kanban_task_blocked` | A kanban task is blocked (worker process) | `task_id, board, assignee, run_id, profile_name, reason: str \| None` | ignored | -Most hooks are fire-and-forget observers — their return values are ignored. The exception is `pre_llm_call`, which can inject context into the conversation. +Most hooks are fire-and-forget observers — their return values are ignored. The exceptions are `pre_llm_call`, which can inject context into the conversation, and `pre_tool_call`, which can return a block/approve directive. All callbacks should accept `**kwargs` for forward compatibility. If a hook callback crashes, it's logged and skipped. Other hooks and the agent continue normally. @@ -1063,7 +1067,8 @@ class MyContextEngine(ContextEngine): def update_from_response(self, usage) -> None: ... def should_compress(self, prompt_tokens: int = None) -> bool: ... - def compress(self, messages, current_tokens=None, focus_topic=None) -> list: ... + def compress(self, messages, current_tokens=None, focus_topic=None, + force=False, memory_context="") -> list: ... def register(ctx): ctx.register_context_engine(MyContextEngine()) diff --git a/website/docs/developer-guide/programmatic-integration.md b/website/docs/developer-guide/programmatic-integration.md index 0feeb3b508e..cc4f8a98bfb 100644 --- a/website/docs/developer-guide/programmatic-integration.md +++ b/website/docs/developer-guide/programmatic-integration.md @@ -28,7 +28,8 @@ Full lifecycle, event bridge, and approval flow: [ACP Internals](./acp-internals ```bash hermes acp # serve ACP on stdio -hermes acp --bootstrap # print install snippet for an ACP-capable IDE +hermes acp --check # verify ACP dependencies and adapter imports +hermes acp --setup # interactive provider/model setup for ACP terminal auth ``` --- diff --git a/website/docs/developer-guide/prompt-assembly.md b/website/docs/developer-guide/prompt-assembly.md index aabd0856242..e7d0497db36 100644 --- a/website/docs/developer-guide/prompt-assembly.md +++ b/website/docs/developer-guide/prompt-assembly.md @@ -165,7 +165,7 @@ def load_soul_md() -> Optional[str]: return None content = soul_path.read_text(encoding="utf-8").strip() content = _scan_context_content(content, "SOUL.md") # Security scan - content = _truncate_content(content, "SOUL.md") # Cap defaults to 20k chars, configurable + content = _truncate_content(content, "SOUL.md") # Cap scales with model context window (20k floor); config override wins return content ``` @@ -232,7 +232,7 @@ def build_context_files_prompt(cwd=None, skip_soul=False): All context files are: - **Security scanned** — checked for prompt injection patterns (invisible unicode, "ignore previous instructions", credential exfiltration attempts) -- **Truncated** — capped at `context_file_max_chars` characters (default 20,000) using 70/20 head/tail ratio with a truncation marker +- **Truncated** — capped at `context_file_max_chars` characters using a 70/20 head/tail split with a truncation marker. The cap scales with the model's context window (20,000-char floor, 500K ceiling); an explicit `context_file_max_chars` in `config.yaml` always wins. - **YAML frontmatter stripped** — `.hermes.md` frontmatter is removed (reserved for future config overrides) ## API-call-time-only layers diff --git a/website/docs/developer-guide/provider-runtime.md b/website/docs/developer-guide/provider-runtime.md index 49f6ac2f565..85925b342ad 100644 --- a/website/docs/developer-guide/provider-runtime.md +++ b/website/docs/developer-guide/provider-runtime.md @@ -16,7 +16,7 @@ Hermes has a shared provider runtime resolver used across: Primary implementation: -- `hermes_cli/runtime_provider.py` — credential resolution, `_resolve_custom_runtime()` +- `hermes_cli/runtime_provider.py` — credential resolution, custom-endpoint runtime resolution - `hermes_cli/auth.py` — provider registry, `resolve_provider()` - `hermes_cli/model_switch.py` — shared `/model` switch pipeline (CLI + gateway) - `agent/auxiliary_client.py` — auxiliary model routing @@ -180,7 +180,7 @@ Hermes supports a configured fallback provider chain — a list of `(provider, m - Resets retry count to 0 and continues the loop 4. **Config flow**: - - CLI: `cli.py` reads `CLI_CONFIG["fallback_model"]` → passes to `AIAgent(fallback_model=...)` + - CLI: reads the fallback chain via `hermes_cli/fallback_config.get_fallback_chain()` → passes to `AIAgent(fallback_model=...)` - Gateway: `gateway/run.py._load_fallback_model()` reads `config.yaml` → passes to `AIAgent` - Validation: both `provider` and `model` keys must be non-empty, or fallback is disabled diff --git a/website/docs/developer-guide/secret-source-plugin.md b/website/docs/developer-guide/secret-source-plugin.md index c3e29f3a28e..5df9f803a69 100644 --- a/website/docs/developer-guide/secret-source-plugin.md +++ b/website/docs/developer-guide/secret-source-plugin.md @@ -6,7 +6,7 @@ description: "How to build a secret-manager backend plugin for Hermes Agent" # Building a Secret Source Plugin -Secret sources resolve provider credentials from an external secret manager (a vault, a password manager, an OS keystore, a custom script) into environment variables at process startup — after `~/.hermes/.env` loads, before Hermes reads credentials. Bitwarden and 1Password ship in-tree; **every other backend is a plugin**. This guide covers building one. +Secret sources resolve provider credentials from an external secret manager (a vault, a password manager, an OS keystore, a custom script) into environment variables at process startup — after `~/.hermes/.env` loads, before Hermes reads credentials. Bitwarden, 1Password, and a generic command-helper source ship in-tree; **every other backend is a plugin**. This guide covers building one. :::tip The bundled set is deliberately closed, same policy as [memory providers](/developer-guide/memory-provider-plugin): PRs adding new vault backends under `agent/secret_sources/` are closed with a pointer to this guide. Publish your backend as a standalone plugin repo and share it in the Nous Research Discord (`#plugins-skills-and-skins`). diff --git a/website/docs/developer-guide/session-storage.md b/website/docs/developer-guide/session-storage.md index 3a5e67ee263..bb1066ad708 100644 --- a/website/docs/developer-guide/session-storage.md +++ b/website/docs/developer-guide/session-storage.md @@ -13,9 +13,14 @@ Source file: `hermes_state.py` ~/.hermes/state.db (SQLite, WAL mode) ├── sessions — Session metadata, token counts, billing ├── messages — Full message history per session +├── session_model_usage — Per-model/per-task usage attribution rows ├── messages_fts — FTS5 virtual table (content + tool_name + tool_calls) ├── messages_fts_trigram — FTS5 virtual table with trigram tokenizer (CJK / substring search) +├── messages_fts_cjk — FTS5 virtual table with cjk_unicode61 tokenizer ├── state_meta — Key/value metadata table +├── gateway_routing — Gateway routing metadata +├── compression_locks — Cross-process compression locking +├── async_delegations — Async delegation bookkeeping └── schema_version — Single-row table tracking migration state ``` @@ -31,6 +36,13 @@ Key design decisions: ### Sessions Table +Abridged — see `SCHEMA_SQL` in `hermes_state.py` for the full current column list +(which also includes gateway routing metadata such as `session_key`, `chat_id`, +`chat_type`, `thread_id`, `display_name`, `origin_json`, `expiry_finalized`, +workspace fields `cwd` / `git_branch` / `git_repo_root`, handoff and +compression-failure fields, `profile_name`, `rewind_count`, `archived`, and +`pinned`): + ```sql CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, @@ -60,6 +72,7 @@ CREATE TABLE IF NOT EXISTS sessions ( pricing_version TEXT, title TEXT, api_call_count INTEGER DEFAULT 0, + -- ... additional gateway/workspace/handoff/compression columns ... FOREIGN KEY (parent_session_id) REFERENCES sessions(id) ); @@ -72,6 +85,10 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_sessions_title_unique ### Messages Table +Abridged — the full schema also includes `effect_disposition`, +`platform_message_id`, `observed`, `active`, `compacted`, `api_content`, +`display_kind`, and `display_metadata`: + ```sql CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -89,6 +106,7 @@ CREATE TABLE IF NOT EXISTS messages ( reasoning_details TEXT, codex_reasoning_items TEXT, codex_message_items TEXT + -- ... additional display/compaction columns ... ); CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, timestamp); @@ -98,6 +116,7 @@ Notes: - `tool_calls` is stored as a JSON string (serialized list of tool call objects) - `reasoning_details`, `codex_reasoning_items`, and `codex_message_items` are stored as JSON strings - `reasoning` stores the raw reasoning text for providers that expose it +- `api_content` is a byte-fidelity sidecar: the exact content string sent to the API for this message when it differs from `content` (ephemeral memory/plugin injections, persist overrides). It preserves the wire bytes for prompt-cache-stable replay — stored as sent, except lone surrogates, which sqlite3 cannot bind and which the conversation loop scrubs from every outgoing payload anyway. `NULL` means `content` was sent verbatim. - Timestamps are Unix epoch floats (`time.time()`) ### FTS5 Full-Text Search @@ -105,35 +124,23 @@ Notes: ```sql CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( content, - content=messages, - content_rowid=id + tool_name, + tool_calls, + content='messages', + content_rowid='id' ); ``` The FTS5 table is kept in sync via three triggers that fire on INSERT, UPDATE, -and DELETE of the `messages` table: - -```sql -CREATE TRIGGER IF NOT EXISTS messages_fts_insert AFTER INSERT ON messages BEGIN - INSERT INTO messages_fts(rowid, content) VALUES (new.id, new.content); -END; - -CREATE TRIGGER IF NOT EXISTS messages_fts_delete AFTER DELETE ON messages BEGIN - INSERT INTO messages_fts(messages_fts, rowid, content) - VALUES('delete', old.id, old.content); -END; - -CREATE TRIGGER IF NOT EXISTS messages_fts_update AFTER UPDATE ON messages BEGIN - INSERT INTO messages_fts(messages_fts, rowid, content) - VALUES('delete', old.id, old.content); - INSERT INTO messages_fts(rowid, content) VALUES (new.id, new.content); -END; -``` +and DELETE of the `messages` table. The current triggers are gated on the +`fts_rebuild_high_water` / `fts_rebuild_progress` markers in `state_meta` (so a +background FTS rebuild can proceed without double-indexing) and cover all three +indexed columns — see `SCHEMA_SQL` in `hermes_state.py` for the exact SQL. ## Schema Version and Migrations -Current schema version: **21** +Current schema version: **23** The `schema_version` table stores a single integer. Simple column additions are handled declaratively by `_reconcile_columns()` (which diffs live columns against `SCHEMA_SQL` and ADDs any missing ones). The version-gated chain is reserved for data migrations and index/FTS changes that can't be expressed declaratively: @@ -153,6 +160,8 @@ The `schema_version` table stores a single integer. Simple column additions are | 16 | Tag delegate subagent rows in `model_config` (`$._delegate_from`) so session pickers stay clean after parent deletes orphan them | | 18 | Gateway metadata consolidation — backfill `display_name` / `origin_json` / `expiry_finalized` from `sessions.json` | | 20 | Per-model usage attribution — seed `session_model_usage` rows from historical per-session aggregate totals | +| 22 | Task-dimension usage attribution — rebuild `session_model_usage` so the `task` column participates in the PRIMARY KEY | +| 23 | FTS storage redesign — external-content FTS tables replacing the v11 inline-mode copies (opt-in transition for existing DBs) | Versions not listed above were declarative column additions handled by `_reconcile_columns()` (version bump only, no data migration). diff --git a/website/docs/developer-guide/tools-runtime.md b/website/docs/developer-guide/tools-runtime.md index 851ad6bc96d..2612ab2db1c 100644 --- a/website/docs/developer-guide/tools-runtime.md +++ b/website/docs/developer-guide/tools-runtime.md @@ -40,7 +40,7 @@ registry.register( ) ``` -Each call creates a `ToolEntry` stored in the singleton `ToolRegistry._tools` dict keyed by tool name. If a name collision occurs across toolsets, a warning is logged and the later registration wins. +Each call creates a `ToolEntry` stored in the singleton `ToolRegistry._tools` dict keyed by tool name. A registration that would shadow an existing tool from a **different** toolset is rejected (with an error log) unless the caller passes `override=True`; plugin overrides of built-in tools additionally require the operator opt-in `plugins.entries..allow_tool_override: true` in `config.yaml`. ### Discovery: `discover_builtin_tools()` diff --git a/website/docs/developer-guide/trajectory-format.md b/website/docs/developer-guide/trajectory-format.md index c2383835709..fbc46632305 100644 --- a/website/docs/developer-guide/trajectory-format.md +++ b/website/docs/developer-guide/trajectory-format.md @@ -215,17 +215,16 @@ preventing Arrow schema mismatch errors during dataset loading. ## Controlling Trajectory Saving -In the CLI, trajectory saving is controlled by: +Trajectory saving is a `run_agent.py` / library-level switch — the `hermes` CLI +does not expose a config key or flag for it: -```yaml -# config.yaml -agent: - save_trajectories: true # default: false +```bash +python run_agent.py --save_trajectories --query='your question here' ``` -Or via the `--save-trajectories` flag. When the agent initializes with -`save_trajectories=True`, the `_save_trajectory()` method is called at the end -of each conversation turn. +Or programmatically: `AIAgent(..., save_trajectories=True)` / +`initialize_agent(..., save_trajectories=True)`. When enabled, the +`_save_trajectory()` method is called at the end of each conversation turn. The batch runner always saves trajectories (that's its primary purpose). diff --git a/website/docs/developer-guide/video-gen-provider-plugin.md b/website/docs/developer-guide/video-gen-provider-plugin.md index f5049398d46..4301b6bd261 100644 --- a/website/docs/developer-guide/video-gen-provider-plugin.md +++ b/website/docs/developer-guide/video-gen-provider-plugin.md @@ -6,7 +6,7 @@ description: "How to build a video-generation backend plugin for Hermes Agent" # Building a Video Generation Provider Plugin -Video-gen provider plugins register a backend that services every `video_generate` tool call. Built-in providers (xAI, FAL) ship as plugins. Add a new one, or override a bundled one, by dropping a directory into `plugins/video_gen//`. +Video-gen provider plugins register a backend that services every `video_generate` tool call. Built-in providers (xAI, FAL, DeepInfra) ship as plugins. Add a new one, or override a bundled one, by dropping a directory into `plugins/video_gen//`. :::tip Video-gen mirrors [Image Generation Provider Plugins](/developer-guide/image-gen-provider-plugin) almost line-for-line — if you've built an image-gen backend, you already know the shape. The main differences: a `capabilities()` method advertising modalities/aspect-ratios/durations, and a routing convention (pass `image_url` to use image-to-video, omit it to use text-to-video — the provider picks the right endpoint internally). diff --git a/website/docs/getting-started/updating.md b/website/docs/getting-started/updating.md index 996dba189f3..0b5813f72a0 100644 --- a/website/docs/getting-started/updating.md +++ b/website/docs/getting-started/updating.md @@ -26,7 +26,7 @@ When you run `hermes update`, the following steps occur: 1. **Pre-update snapshot** — a lightweight state snapshot is saved by default (covers pairing data, cron jobs, `config.yaml`, `.env`, `auth.json`, and other state files that get modified at runtime; individual files over 1 GiB are skipped so a large sessions DB never slows the update down). Controlled by `updates.pre_update_backup` (`quick` by default, `full` for a zip of all of `HERMES_HOME`, `off` to disable). Recoverable via the snapshot restore flow described under [Snapshots and rollback](../user-guide/checkpoints-and-rollback.md). 2. **Git pull** — pulls the latest code from the `main` branch and updates submodules -3. **Post-pull syntax validation + auto-rollback** — after the pull, Hermes compiles the eight critical files every `hermes` invocation imports at startup. If any fails to parse (e.g. an orphan merge-conflict marker, an accidentally truncated file), Hermes runs `git reset --hard ` to roll the install back so your shell stays bootable. Re-run `hermes update` once the upstream fix lands. +3. **Post-pull syntax validation + auto-rollback** — after the pull, Hermes compiles the nine critical files every `hermes` invocation imports at startup. If any fails to parse (e.g. an orphan merge-conflict marker, an accidentally truncated file), Hermes runs `git reset --hard ` to roll the install back so your shell stays bootable. Re-run `hermes update` once the upstream fix lands. 4. **Dependency install** — runs `uv pip install -e ".[all]"` to pick up new or changed dependencies 5. **Config migration** — detects new config options added since your version and prompts you to set them 6. **Gateway auto-restart** — running gateways are refreshed after the update completes so the new code takes effect immediately. Service-managed gateways (systemd on Linux, launchd on macOS) are restarted through the service manager. Manual gateways are relaunched automatically when Hermes can map the running PID back to a profile. diff --git a/website/docs/guides/automation-blueprints.md b/website/docs/guides/automation-blueprints.md index 981f2aaa823..7bf861e8e22 100644 --- a/website/docs/guides/automation-blueprints.md +++ b/website/docs/guides/automation-blueprints.md @@ -76,7 +76,7 @@ Review for: - Missing tests for new behavior Post a concise review. If the PR is a trivial docs/typo change, say so briefly." \ - --skill github-code-review \ + --skills github-code-review \ --deliver github_comment ``` @@ -432,7 +432,7 @@ If action is 'closed' and pull_request.merged is true: 5. Reference the original PR in the new PR description If action is not 'closed' or not merged, respond with [SILENT]." \ - --skill github-pr-workflow \ + --skills github-pr-workflow \ --deliver log ``` diff --git a/website/docs/guides/aws-bedrock.md b/website/docs/guides/aws-bedrock.md index 4c8389ae5e2..ec7b1224fce 100644 --- a/website/docs/guides/aws-bedrock.md +++ b/website/docs/guides/aws-bedrock.md @@ -88,6 +88,14 @@ bedrock: refresh_interval: 3600 # Cache for 1 hour ``` +### Prompt caching (cachePoint) + +Hermes automatically applies prompt caching on the Bedrock **Converse API** path by inserting `cachePoint` markers after the system prompt, tool definitions, and the latest message. Because sending a `cachePoint` block to a model that doesn't support it raises a `ValidationException`, markers are only added for models on a known-good allowlist (Anthropic Claude and Amazon Nova model IDs); unknown models default to no cache markers. Claude models normally use the AnthropicBedrock SDK path, which has its own prompt caching — the Converse `cachePoint` path covers Nova and the bearer-token Claude fallback. No configuration needed; cache reads/writes show up in usage accounting. + +### Context-window probing + +For models whose context window isn't in Hermes' static table, Hermes can probe the real limit by sending oversized requests at fixed tiers (~1.3M and ~2.2M tokens) and parsing the `maximum` reported in Bedrock's length-validation error. Probed values feed the same metadata cache as the static table; stale cached entries that under-report a model's window (e.g. entries seeded before a model's 1M window went GA) are dropped automatically in favor of the larger known value. + ## Available Models Bedrock models use **inference profile IDs** for on-demand invocation. The `hermes model` picker shows these automatically, with recommended models at the top: diff --git a/website/docs/guides/minimax-oauth.md b/website/docs/guides/minimax-oauth.md index b7161aae9d6..0c0770252a7 100644 --- a/website/docs/guides/minimax-oauth.md +++ b/website/docs/guides/minimax-oauth.md @@ -170,7 +170,7 @@ hermes --provider minimax-oauth Both models support up to 200,000 tokens of context. -`MiniMax-M2.7-highspeed` is also used automatically as the auxiliary model for vision and delegation tasks when `minimax-oauth` is the primary provider. +`MiniMax-M2.7` is also used automatically as the auxiliary model for vision and delegation tasks when `minimax-oauth` is the primary provider. ## Troubleshooting diff --git a/website/docs/guides/oauth-over-ssh.md b/website/docs/guides/oauth-over-ssh.md index c904524f528..c58aeb401d4 100644 --- a/website/docs/guides/oauth-over-ssh.md +++ b/website/docs/guides/oauth-over-ssh.md @@ -21,7 +21,7 @@ The fix is a one-line SSH local-forward. For MCP servers on an interactive termi ssh -N -L 43827:127.0.0.1:43827 user@remote-host # In your existing SSH session on the remote machine: -hermes auth add spotify --no-browser +hermes auth spotify --no-browser # → Hermes prints an authorize URL. Open it in a browser on your laptop. # → Your browser redirects to 127.0.0.1:43827/callback, the tunnel forwards # the request to the remote listener, login completes. @@ -92,7 +92,7 @@ ssh -N -L 43827:127.0.0.1:43827 user@remote-host ```bash ssh user@remote-host -hermes auth add spotify --no-browser +hermes auth spotify --no-browser ``` Hermes detects the SSH session, skips the browser auto-open, and prints an authorize URL plus a `Waiting for callback on http://127.0.0.1:/callback` line. diff --git a/website/docs/guides/run-hermes-with-nous-portal.md b/website/docs/guides/run-hermes-with-nous-portal.md index 1f64f03d6a1..e4928ef6df3 100644 --- a/website/docs/guides/run-hermes-with-nous-portal.md +++ b/website/docs/guides/run-hermes-with-nous-portal.md @@ -151,7 +151,7 @@ You'll see per-tool routing — `via Nous Portal` for the ones routed through th Because the Tool Gateway includes OpenAI TTS, [voice mode](/user-guide/features/voice-mode) works without a separate OpenAI key: ```bash -hermes setup voice +hermes setup tts # → pick "Nous Subscription" for TTS # → pick a speech-to-text backend (local faster-whisper is free, no setup) ``` @@ -163,7 +163,7 @@ Then in any messaging-platform session (Telegram, Discord, Signal, etc.), send a The Portal subscription works for [cron jobs](/user-guide/features/cron) and [batch processing](/user-guide/features/batch-processing) the same way it works for interactive chat — the OAuth refresh token is reused automatically. No additional setup; just schedule cron jobs and they'll bill against your subscription. ```bash -hermes cron create "every day at 9am" \ +hermes cron create "0 9 * * *" \ "Search the web for top AI news and summarize the 5 most important stories" \ --name "Daily AI news" ``` diff --git a/website/docs/guides/use-voice-mode-with-hermes.md b/website/docs/guides/use-voice-mode-with-hermes.md index 7007e20efc5..b13baca4af1 100644 --- a/website/docs/guides/use-voice-mode-with-hermes.md +++ b/website/docs/guides/use-voice-mode-with-hermes.md @@ -444,7 +444,7 @@ By default, the bot needs an `@mention` in Discord server text channels unless c If you want the shortest path to success: 1. get text Hermes working -2. run `hermes setup voice` to enable voice support +2. run `hermes setup tts` to enable voice support 3. use CLI voice mode with local STT + Edge TTS 4. then enable `/voice on` in Telegram or Discord 5. only after that, try Discord VC mode diff --git a/website/docs/integrations/index.md b/website/docs/integrations/index.md index 597571eec7e..6fde318ff98 100644 --- a/website/docs/integrations/index.md +++ b/website/docs/integrations/index.md @@ -72,7 +72,7 @@ Text-to-speech and speech-to-text across all messaging platforms: | **xAI TTS** | Good | Paid | `XAI_API_KEY` | | **NeuTTS** | Good | Free | None needed | -Speech-to-text supports six providers: local faster-whisper (free, runs on-device), a local command wrapper, Groq, OpenAI Whisper API, Mistral, and xAI. Voice message transcription works across Telegram, Discord, WhatsApp, and other messaging platforms. See [Voice & TTS](/user-guide/features/tts) and [Voice Mode](/user-guide/features/voice-mode) for details. +Speech-to-text supports eight providers: local faster-whisper (free, runs on-device), a local command wrapper, Groq, OpenAI Whisper API, Mistral, xAI, ElevenLabs Scribe, and DeepInfra. Voice message transcription works across Telegram, Discord, WhatsApp, and other messaging platforms. See [Voice & TTS](/user-guide/features/tts) and [Voice Mode](/user-guide/features/voice-mode) for details. ## IDE & Editor Integration diff --git a/website/docs/integrations/nous-portal.md b/website/docs/integrations/nous-portal.md index fcf20f8574f..61098b3ef4a 100644 --- a/website/docs/integrations/nous-portal.md +++ b/website/docs/integrations/nous-portal.md @@ -217,16 +217,19 @@ The Tool Gateway settings live under their respective tool sections: ```yaml web: - backend: nous # web search/extract routes through Tool Gateway + backend: firecrawl + use_gateway: true # web search/extract routes through Tool Gateway image_gen: - provider: nous + use_gateway: true tts: - provider: nous + provider: openai + use_gateway: true browser: - backend: nous + cloud_provider: browser-use + use_gateway: true ``` The OAuth refresh token is stored separately at `~/.hermes/auth.json` (not in `config.yaml` — credentials and configuration are kept separate by design). diff --git a/website/docs/reference/cli-commands.md b/website/docs/reference/cli-commands.md index 9084e81fae1..8c1ceb11227 100644 --- a/website/docs/reference/cli-commands.md +++ b/website/docs/reference/cli-commands.md @@ -62,6 +62,7 @@ hermes [global-options] [subcommand/options] | `hermes hooks` | Inspect, approve, or remove shell-script hooks declared in `config.yaml`. | | `hermes doctor` | Diagnose config and dependency issues. | | `hermes security audit` | On-demand supply-chain audit (OSV.dev) for the venv, plugin requirements, and pinned MCP servers. | +| `hermes approvals` | Approval-prompt tools — mine approval history into allowlist proposals. | | `hermes dump` | Copy-pasteable setup summary for support/debugging. | | `hermes prompt-size` | Show a byte breakdown of the system prompt + tool schemas (skills index, memory, profile). Runs offline. | | `hermes debug` | Debug tools — upload logs and system info for support. | @@ -70,23 +71,27 @@ hermes [global-options] [subcommand/options] | `hermes import` | Restore a Hermes backup from a zip file. | | `hermes logs` | View, tail, and filter agent/gateway/error log files. | | `hermes config` | Show, edit, migrate, and query configuration files. | +| `hermes skin` | List, switch, and tweak display skins. | +| `hermes console` | Open the safe Hermes command console. | | `hermes pairing` | Approve or revoke messaging pairing codes. | | `hermes skills` | Browse, install, publish, audit, and configure skills. | | `hermes bundles` | Group several skills under a single `/` slash command. See [Skill Bundles](../user-guide/features/skills.md#skill-bundles). | | `hermes curator` | Background skill maintenance — status, run, pause, pin. See [Curator](../user-guide/features/curator.md). | +| `hermes journey` (aliases `learning`, `memory-graph`) | Timeline of learned skills + memories over time. | | `hermes memory` | Configure external memory provider. Plugin-specific subcommands (e.g. `hermes honcho`) register automatically when their provider is active. | | `hermes acp` | Run Hermes as an ACP server for editor integration. | | `hermes mcp` | Manage MCP server configurations and run Hermes as an MCP server. | | `hermes plugins` | Manage Hermes Agent plugins (install, enable, disable, remove). | | `hermes portal` | Nous Portal status, subscription link, and Tool Gateway routing. See [Tool Gateway](../user-guide/features/tool-gateway.md). | | `hermes tools` | Configure enabled tools per platform. | -| `hermes computer-use` | Install or check the cua-driver backend (macOS Computer Use). | +| `hermes computer-use` | Install or check the Computer Use (cua-driver) backend (macOS/Windows/Linux). | | `hermes pets` | Browse, install, and select [petdex](../user-guide/features/pets.md) animated pets shown across the CLI, TUI, and desktop app. Subcommands: `list`, `install`, `select`, `show`, `off`, `scale`, `remove`, `doctor`. | | `hermes sessions` | Browse, export, prune, rename, and delete sessions. | | `hermes insights` | Show token/cost/activity analytics. | | `hermes claw` | OpenClaw migration helpers. | | `hermes import-agent` | Import a Claude Code (`~/.claude`) or Codex CLI (`~/.codex`) setup. | | `hermes dashboard` | Launch the web dashboard for managing config, API keys, and sessions. | +| `hermes serve` | Start the Hermes backend server (headless; powers the desktop app and remote backends). | | `hermes desktop` (alias `gui`) | Build and launch the native Electron desktop app. | | `hermes profile` | Manage profiles — multiple isolated Hermes instances. | | `hermes completion` | Print shell completion scripts (bash/zsh/fish). | @@ -154,6 +159,7 @@ Per-run overrides (no mutation to `~/.hermes/config.yaml`): |---|---|---| | `-m` / `--model ` | `HERMES_INFERENCE_MODEL` | Override the model for this run | | `--provider ` | _(none)_ | Override the provider for this run | +| `--usage-file ` | _(none)_ | Write a JSON usage report after the run (see below) | ```bash hermes -z "…" --provider openrouter --model openai/gpt-5.5 @@ -163,6 +169,15 @@ HERMES_INFERENCE_MODEL=anthropic/claude-sonnet-4.6 hermes -z "…" Same agent, same tools, same skills — just strips every interactive / cosmetic layer. If you need tool output in the transcript too, use `hermes chat -q` instead; `-z` is explicitly for "I only want the final answer". +#### `--usage-file` — JSON usage report for pipelines + +`hermes -z "…" --usage-file /path/report.json` writes a machine-readable usage report after the run: `estimated_cost_usd`, `input_tokens` / `output_tokens` / `cache_read_tokens` / `cache_write_tokens` / `reasoning_tokens` / `total_tokens`, `api_calls`, `model`, `provider`, `session_id`, `service_tier`, and `completed` / `failed` flags. The report is written **even when the run fails**, so batch pipelines can always account for spend. It has no effect outside `-z`/`--oneshot`, and a broken usage write never masks the run's own outcome. + +```bash +hermes -z "summarize this repo" --usage-file /tmp/usage.json +jq .estimated_cost_usd /tmp/usage.json +``` + ## `hermes model` Interactive provider + model selector. **This is the command for adding new providers, setting up API keys, and running OAuth flows.** Run it from your terminal — not from inside an active Hermes chat session. @@ -233,7 +248,7 @@ Subcommands: | `uninstall` | Remove the installed service. | | `setup` | Interactive messaging-platform setup. | | `migrate-legacy` | Remove legacy `hermes.service` units left over from pre-rename installs. Profile units (`hermes-gateway-.service`) and unrelated services are never touched. Flags: `--dry-run`, `-y`/`--yes`. | -| `enroll` | Experimental: enroll this gateway with a relay connector and save relay credentials for connector-backed platforms. | +| `enroll` | Experimental: enroll this gateway with a relay connector and save relay credentials for connector-backed platforms. See [Hermes Relay](/user-guide/messaging/relay). | Options: @@ -1084,7 +1099,9 @@ Subcommands: |------------|-------------| | `show` | Show current config values. | | `edit` | Open `config.yaml` in your editor. | +| `get [--json]` | Print a single config value by dotted key (e.g. `hermes config get model.default`). `--json` emits machine-readable output. | | `set ` | Set a config value. | +| `unset ` | Remove a config key, reverting it to the built-in default. | | `path` | Print the config file path. | | `env-path` | Print the `.env` file path. | | `check` | Check for missing or stale config. | @@ -1667,7 +1684,7 @@ Additional behavior: | `hermes version` | Print version information. | | `hermes update` | Pull latest changes and reinstall dependencies. | -| `hermes uninstall [--full] [--gui] [--yes]` | Remove Hermes, optionally deleting all config/data. `--gui` removes only the desktop Chat GUI, leaving the agent intact; `--full` also deletes config/data; `--yes` skips prompts. | +| `hermes uninstall [--full] [--gui] [--dry-run] [--yes]` | Remove Hermes, optionally deleting all config/data. `--gui` removes only the desktop Chat GUI, leaving the agent intact; `--full` also deletes config/data; `--dry-run` prints what would be removed without changing anything; `--yes` skips prompts. | ## See also diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index 99f87241512..c0e76ea1e64 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -70,6 +70,7 @@ Hermes reads environment variables from the process environment and, for user-ma | `GOOGLE_API_KEY` | Google AI Studio API key ([aistudio.google.com/app/apikey](https://aistudio.google.com/app/apikey)) | | `GEMINI_API_KEY` | Alias for `GOOGLE_API_KEY` | | `GEMINI_BASE_URL` | Override Google AI Studio base URL | +| `VERTEX_CREDENTIALS_PATH` | Path to a Google Cloud service account JSON for Vertex AI (Gemini). Vertex uses OAuth2, not a static API key. Falls back to `GOOGLE_APPLICATION_CREDENTIALS`, then to ADC (`gcloud auth application-default login`). Set project/region under `vertex:` in `config.yaml` | | `ANTHROPIC_API_KEY` | Anthropic Console API key ([console.anthropic.com](https://console.anthropic.com/)) | | `ANTHROPIC_BASE_URL` | Override the Anthropic API base URL | | `ANTHROPIC_TOKEN` | Manual or legacy Anthropic OAuth/setup-token override | @@ -79,6 +80,8 @@ Hermes reads environment variables from the process environment and, for user-ma | `ALIBABA_CODING_PLAN_BASE_URL` | Override the Qwen Coding Plan base URL | | `DEEPSEEK_API_KEY` | DeepSeek API key for direct DeepSeek access ([platform.deepseek.com](https://platform.deepseek.com/api_keys)) | | `DEEPSEEK_BASE_URL` | Custom DeepSeek API base URL | +| `DEEPINFRA_API_KEY` | DeepInfra API key ([deepinfra.com](https://deepinfra.com/dash/api_keys)) | +| `DEEPINFRA_BASE_URL` | DeepInfra base URL override | | `NOVITA_API_KEY` | NovitaAI API key — AI-native cloud for Model API, Agent Sandbox, and GPU Cloud ([novita.ai/settings/key-management](https://novita.ai/settings/key-management)) | | `NOVITA_BASE_URL` | Override NovitaAI base URL (default: `https://api.novita.ai/openai/v1`) | | `NVIDIA_API_KEY` | NVIDIA NIM API key — Nemotron and open models ([build.nvidia.com](https://build.nvidia.com)) | @@ -144,6 +147,7 @@ For native Anthropic auth, Hermes prefers Claude Code's own credential files whe | `FIRECRAWL_BROWSER_TTL` | Firecrawl browser session TTL in seconds (default: 300) | | `BROWSER_CDP_URL` | Chrome DevTools Protocol URL for local browser (set via `/browser connect`, e.g. `ws://localhost:9222`) | | `CAMOFOX_URL` | Camofox local anti-detection browser URL (default: `http://localhost:9377`) | +| `CAMOFOX_API_KEY` | Optional bearer token sent as Authorization header to a remote/authenticated Camofox server | | `CAMOFOX_USER_ID` | Optional externally managed Camofox user ID for shared visible sessions | | `CAMOFOX_SESSION_KEY` | Optional Camofox session key used when creating tabs for `CAMOFOX_USER_ID` | | `CAMOFOX_ADOPT_EXISTING_TAB` | Set to `true` to reuse an existing Camofox tab before creating a new one | @@ -162,7 +166,15 @@ For native Anthropic auth, Hermes prefers Claude Code's own credential files whe | `GITHUB_TOKEN` | GitHub token for Skills Hub (higher API rate limits, skill publish) | | `HONCHO_API_KEY` | Cross-session user modeling ([honcho.dev](https://honcho.dev/)) | | `HONCHO_BASE_URL` | Base URL for self-hosted Honcho instances (default: Honcho cloud). No API key required for local instances | +| `HINDSIGHT_API_KEY` | Hindsight API key for graph-aware persistent memory ([hindsight.vectorize.io](https://hindsight.vectorize.io)) | +| `HINDSIGHT_API_URL` | Base URL for the Hindsight API (default: `https://api.hindsight.vectorize.io`) | | `HINDSIGHT_TIMEOUT` | Timeout in seconds for Hindsight memory-provider API calls (default: `60`). Bump this if your Hindsight instance is slow to respond during `/sync` or `on_session_switch` and you're seeing timeouts in `errors.log`. | +| `MEM0_API_KEY` | Mem0 Platform API key for semantic persistent memory ([app.mem0.ai](https://app.mem0.ai)) | +| `RETAINDB_API_KEY` | RetainDB API key for persistent memory ([retaindb.com](https://retaindb.com)) | +| `RETAINDB_BASE_URL` | Base URL for self-hosted RetainDB instances (default: `https://api.retaindb.com`) | +| `OPENVIKING_API_KEY` | OpenViking API key (leave blank for local dev mode) | +| `OPENVIKING_ENDPOINT` | OpenViking server URL (default: `http://127.0.0.1:1933`) | +| `BRV_API_KEY` | ByteRover API key (optional, for cloud sync — local-first by default) ([app.byterover.dev](https://app.byterover.dev)) | | `SUPERMEMORY_API_KEY` | Semantic long-term memory with profile recall and session ingest ([supermemory.ai](https://supermemory.ai)) | | `DAYTONA_API_KEY` | Daytona cloud sandboxes ([daytona.io](https://daytona.io/)) | @@ -318,6 +330,7 @@ These are set automatically by the Docker terminal backend when `proxy.enabled: | `SLACK_ALLOWED_USERS` | Comma-separated Slack user IDs | | `SLACK_ALLOW_ALL_USERS` | Allow any Slack user to trigger the bot (dev only). | | `SLACK_ALLOW_BOTS` | Accept messages from other Slack bots: `none` (default), `mentions`, or `all`. The bot always ignores its own messages. | +| `SLACK_THREAD_REQUIRE_MENTION` | Require an explicit @mention for Slack thread replies while preserving top-level free-response channels | | `SLACK_HOME_CHANNEL` | Default Slack channel for cron delivery | | `SLACK_HOME_CHANNEL_NAME` | Display name for the Slack home channel | | `GOOGLE_CHAT_PROJECT_ID` | GCP project hosting the Pub/Sub topic (falls back to `GOOGLE_CLOUD_PROJECT`) | @@ -331,6 +344,9 @@ These are set automatically by the Docker terminal backend when `proxy.enabled: | `GOOGLE_CHAT_MAX_BYTES` | Pub/Sub FlowControl max in-flight bytes (default: `16777216`, 16 MiB) | | `GOOGLE_CHAT_BOOTSTRAP_SPACES` | Comma-separated extra space IDs to probe at startup when resolving the bot's own `users/{id}` | | `GOOGLE_CHAT_DEBUG_RAW` | Set to any value to log redacted Pub/Sub envelopes at DEBUG level (debugging only) | +| `GOOGLE_CHAT_HTTP_EVENTS_URL` | Authenticated HTTP endpoint for Chat message events (alternative to Pub/Sub) | +| `GOOGLE_CHAT_HTTP_EVENTS_AUDIENCE` | Expected audience for Google-signed HTTP event bearer tokens (defaults to `GOOGLE_CHAT_HTTP_EVENTS_URL`) | +| `GOOGLE_CHAT_HTTP_EVENTS_SERVICE_ACCOUNT_EMAIL` | Expected Google service account email for HTTP event bearer tokens | | `WHATSAPP_ENABLED` | Enable the WhatsApp bridge (`true`/`false`) | | `WHATSAPP_MODE` | `bot` (separate number) or `self-chat` (message yourself) | | `WHATSAPP_ALLOWED_USERS` | Comma-separated phone numbers (with country code, no `+`), or `*` to allow all senders | @@ -474,8 +490,6 @@ These are set automatically by the Docker terminal backend when `proxy.enabled: | `MATRIX_IGNORE_USER_PATTERNS` | Comma-separated regular expressions for Matrix bridge/appservice ghost user IDs to ignore | | `MATRIX_PROCESS_NOTICES` | Process inbound Matrix `m.notice` events (default: `false`) | | `MATRIX_SESSION_SCOPE` | Matrix session scope for project rooms: `auto`, `room`, or `thread` (default: `auto`) | -| `MATRIX_TOOLS_ALLOW_CROSS_ROOM` | Allow Matrix tools to target explicit rooms other than the current room (default: `false`) | -| `MATRIX_TOOLS_ALLOW_CROSS_ROOM_DESTRUCTIVE` | Allow cross-room Matrix redaction/invite-like tools; requires `MATRIX_TOOLS_ALLOW_CROSS_ROOM=true` (default: `false`) | | `MATRIX_TOOLS_ALLOW_REDACTION` | Allow Matrix message redaction tool execution (default: `false`) | | `MATRIX_TOOLS_ALLOW_INVITES` | Allow Matrix invite tool execution (default: `false`) | | `MATRIX_TOOLS_ALLOW_ROOM_CREATE` | Allow Matrix room creation tool execution (default: `false`) | @@ -664,6 +678,22 @@ Connect Hermes to [Photon](https://photon.codes/) / Spectrum (iMessage and other | `PHOTON_DASHBOARD_HOST` | Photon Dashboard API host (default `https://app.photon.codes`). | | `PHOTON_SPECTRUM_HOST` | Photon Spectrum API host (default `https://spectrum.photon.codes`). | +### Buzz (Nostr communities) + +| Variable | Description | +|----------|-------------| +| `BUZZ_RELAY_URL` | Base URL of the Buzz community relay (e.g. `https://mycommunity.communities.buzz.xyz`) | +| `BUZZ_PRIVATE_KEY` | Nostr private key for the agent's Buzz identity (nsec or hex) — the only Buzz secret | +| `BUZZ_CREDENTIALS_FILE` | JSON credentials file holding the nsec (fallback when `BUZZ_PRIVATE_KEY` is unset) | +| `BUZZ_CHANNELS` | Comma-separated channel UUIDs to watch (default: all joined channels) | +| `BUZZ_HOME_CHANNEL` | Channel UUID for cron / notification delivery (defaults to the first watched channel) | +| `BUZZ_ALLOWED_USERS` | Comma-separated npubs or hex pubkeys allowed to talk to the agent | +| `BUZZ_ALLOW_ALL_USERS` | Allow any community member to talk to the agent (`true`/`false`) | +| `BUZZ_TRANSPORT` | Inbound transport: `auto` (WebSocket w/ poll fallback, default), `websocket`, or `poll` | +| `BUZZ_POLL_INTERVAL` | Seconds between inbound poll sweeps (default: `4`) | +| `BUZZ_AUTH_TAG` | Optional NIP-OA owner-attestation auth tag JSON for NIP-42 WebSocket auth | +| `BUZZ_CLI_PATH` | Path to the buzz CLI binary (default: `buzz` on PATH, then `~/bin/buzz`) | + ### Microsoft Teams (adapter) The Microsoft Teams platform adapter (Bot Framework / Azure AD), distinct from the [Microsoft Graph (Teams Meetings)](#microsoft-graph-teams-meetings) integration above. See [the Teams messaging guide](/user-guide/messaging/teams). @@ -673,6 +703,7 @@ The Microsoft Teams platform adapter (Bot Framework / Azure AD), distinct from t | `TEAMS_CLIENT_ID` | Azure AD application (Bot Framework) client ID. | | `TEAMS_CLIENT_SECRET` | Azure AD application client secret. | | `TEAMS_TENANT_ID` | Azure AD tenant ID hosting the bot application. | +| `TEAMS_HOST` | Webhook bind host (default: unset → dual-stack, all interfaces IPv4+IPv6). | | `TEAMS_PORT` | Webhook listen port (Bot Framework default: `3978`). | | `TEAMS_ALLOWED_USERS` | Comma-separated Teams user IDs / UPNs allowed to talk to the bot. | | `TEAMS_ALLOW_ALL_USERS` | Allow any Teams user to trigger the bot (dev only). | @@ -781,7 +812,6 @@ Advanced per-platform knobs for throttling the outbound message batcher. Most us | `HERMES_DUMP_REQUESTS` | Dump API request payloads to log files (`true`/`false`) | | `HERMES_DUMP_REQUEST_STDOUT` | Dump API request payloads to stdout instead of log files. | | `HERMES_OAUTH_TRACE` | Set to `1` to log OAuth token exchange and refresh attempts. Includes redacted timing info. | -| `HERMES_OAUTH_FILE` | Override the path used for OAuth credential storage (default: `~/.hermes/auth.json`). | | `HERMES_AGENT_HELP_GUIDANCE` | Append additional guidance text to the system prompt for custom deployments. | | `HERMES_AGENT_LOGO` | Override the ASCII banner logo at CLI startup. | | `DELEGATION_MAX_CONCURRENT_CHILDREN` | Max parallel subagents per `delegate_task` batch (default: `3`, floor of 1, no ceiling). Also configurable via `delegation.max_concurrent_children` in `config.yaml` — the config value takes priority. | diff --git a/website/docs/reference/mcp-config-reference.md b/website/docs/reference/mcp-config-reference.md index 2809f709f2e..36b5458ac0d 100644 --- a/website/docs/reference/mcp-config-reference.md +++ b/website/docs/reference/mcp-config-reference.md @@ -58,16 +58,36 @@ mcp_servers: | `connect_timeout` | number | both | Initial connection timeout in seconds (default: `60`) | | `supports_parallel_tool_calls` | bool | both | Allow tools from this server to run concurrently | | `skip_preflight` | bool | HTTP | Bypass the fail-fast content-type probe for valid Streamable HTTP endpoints whose HEAD/GET answers a non-MCP content type (default: `false`) | +| `transport` | string | HTTP | Set to `sse` to use the SSE transport instead of Streamable HTTP | +| `keepalive_interval` | number | both | Liveness ping cadence in seconds (default: `180`, floored at 5s). Set below the server's session TTL for servers that GC idle sessions quickly | +| `idle_timeout_seconds` | number | stdio | Optional stdio server recycle after idle time (`0` disables). May also live under a `lifecycle:` mapping | +| `max_lifetime_seconds` | number | stdio | Optional stdio server recycle after age (`0` disables). May also live under a `lifecycle:` mapping | | `tools` | mapping | both | Filtering and utility-tool policy | | `auth` | string | HTTP | Authentication method. Set to `oauth` to enable OAuth 2.1 with PKCE | | `sampling` | mapping | both | Server-initiated LLM request policy (see MCP guide) | +| `elicitation` | mapping | both | Server-initiated user-input requests. `enabled` (default `true`) and `timeout` in seconds (default `300`). Form-mode requests route through the approval surface; URL-mode is declined (see MCP guide) | + +## Environment variable references + +String values anywhere in a server entry (`env`, `headers`, `args`, `url`, …) may reference environment variables with `${VAR}` or the Cursor-style SecretRef form `${env:VAR}` — both resolve to the same variable, so MCP snippets copied from Cursor / Claude configs work unchanged: + +```yaml +mcp_servers: + github: + command: "npx" + args: ["-y", "@modelcontextprotocol/server-github"] + env: + GITHUB_PERSONAL_ACCESS_TOKEN: "${env:GITHUB_TOKEN}" # same as "${GITHUB_TOKEN}" +``` + +Values resolve from the active profile's secret scope (falling back to the process environment), so put the secret in `~/.hermes/.env`. An unset variable keeps its literal placeholder. ## `tools` policy keys | Key | Type | Meaning | |---|---|---| -| `include` | string or list | Whitelist server-native MCP tools | -| `exclude` | string or list | Blacklist server-native MCP tools | +| `include` | string or list | Whitelist server-native MCP tools. Entries may be exact names or fnmatch-style globs (`*_radar_*`, `get_zones_*`) | +| `exclude` | string or list | Blacklist server-native MCP tools. Same exact-name / glob semantics as `include` | | `resources` | bool-like | Enable/disable `list_resources` + `read_resource` | | `prompts` | bool-like | Enable/disable `list_prompts` + `get_prompt` | @@ -247,28 +267,30 @@ After changing MCP config, reload servers with: Server-native MCP tools become: ```text -mcp__ +mcp____ ``` Examples: -- `mcp_github_create_issue` -- `mcp_filesystem_read_file` -- `mcp_my_api_query_data` +- `mcp__github__create_issue` +- `mcp__filesystem__read_file` +- `mcp__my_api__query_data` Utility tools follow the same prefixing pattern: -- `mcp__list_resources` -- `mcp__read_resource` -- `mcp__list_prompts` -- `mcp__get_prompt` +- `mcp____list_resources` +- `mcp____read_resource` +- `mcp____list_prompts` +- `mcp____get_prompt` + +The double-underscore delimiter (`mcp__…__…`) matches the convention used by Claude Code, Codex, and OpenCode, and disambiguates the server/tool boundary even when either component contains underscores. ### Name sanitization -Hyphens (`-`) and dots (`.`) in both server names and tool names are replaced with underscores before registration. This ensures tool names are valid identifiers for LLM function-calling APIs. +Any character that is not a letter, digit, or underscore (hyphens, dots, spaces, etc.) in both server names and tool names is replaced with an underscore before registration. This ensures tool names are valid identifiers for LLM function-calling APIs. For example, a server named `my-api` exposing a tool called `list-items.v2` becomes: ```text -mcp_my_api_list_items_v2 +mcp__my_api__list_items_v2 ``` Keep this in mind when writing `include` / `exclude` filters — use the **original** MCP tool name (with hyphens/dots), not the sanitized version. diff --git a/website/docs/reference/profile-commands.md b/website/docs/reference/profile-commands.md index 678e518060f..24a8f6791b5 100644 --- a/website/docs/reference/profile-commands.md +++ b/website/docs/reference/profile-commands.md @@ -163,7 +163,7 @@ hermes profile delete mybot --yes ``` :::warning -This permanently deletes the profile's entire directory including all config, memories, sessions, and skills. Cannot delete the currently active profile. +This permanently deletes the profile's entire directory including all config, memories, sessions, and skills. The `default` profile (`~/.hermes`) cannot be deleted — use `hermes uninstall` to remove everything. ::: ## `hermes profile show` diff --git a/website/docs/reference/slash-commands.md b/website/docs/reference/slash-commands.md index a8c1db8d4fc..0a4c9905aaa 100644 --- a/website/docs/reference/slash-commands.md +++ b/website/docs/reference/slash-commands.md @@ -64,6 +64,7 @@ Type `/` in the CLI to open the autocomplete menu. Built-in commands are case-in | `/background ` (alias: `/bg`, `/btw`) | Run a prompt in a separate background session. The agent processes your prompt independently — your current session stays free for other work. Results appear as a panel when the task finishes. See [CLI Background Sessions](/user-guide/cli#background-sessions). | | `/branch [name]` (alias: `/fork`) | Branch the current session (explore a different path) | | `/handoff ` | **CLI only.** Hand the current session off to a messaging platform (Telegram, Discord, Slack, WhatsApp, Signal, Matrix). The gateway picks it up immediately, creates a fresh thread on platforms that support threads (Telegram topics, Discord text-channel threads, Slack message-anchored threads), re-binds the destination to your CLI session_id so the full role-aware transcript replays, and forges a synthetic user turn so the agent confirms it's working in the new place. Your CLI exits cleanly on success with a `/resume` hint; resume locally any time with `/resume `. Refused mid-turn. Requires the gateway to be running and a home channel configured for the target platform (`/sethome` from the destination chat). See [Cross-Platform Handoff](/user-guide/sessions#cross-platform-handoff). | +| `/journey [list\|delete <id>\|edit <id>]` (aliases: `/learning`, `/memory-graph`) | Open the learning journey timeline of learned skills + memories. Works in the classic CLI, as a TUI overlay, and in the desktop app (Star Map panel). Not available on messaging platforms. See [Learning Journey](/user-guide/features/memory#learning-journey-journey). | ### Configuration @@ -82,10 +83,12 @@ Type `/` in the CLI to open the autocomplete menu. Built-in commands are case-in | `/battery [on\|off\|status]` | Toggle a color-coded battery read-out as the first status-bar element (off by default; no-op without a battery). | | `/voice [on\|off\|tts\|status]` | Toggle CLI voice mode and spoken playback. Recording uses `voice.record_key` (default: `Ctrl+B`). | | `/yolo` | Toggle YOLO mode — skip all dangerous command approval prompts. | +| `/approvals [manual\|smart\|off]` | Show or set the persistent dangerous-command approval mode. | | `/footer [on\|off\|status]` | Toggle the gateway runtime-metadata footer on final replies (shows model, context %, and cwd). | | `/busy [queue\|steer\|interrupt\|status]` | CLI-only: control what pressing Enter does while Hermes is working — queue the new message, steer mid-turn, or interrupt immediately. | | `/indicator [kaomoji\|emoji\|unicode\|ascii]` | CLI-only: pick the TUI busy-indicator style. | | `/timestamps [on\|off\|status]` | CLI-only: toggle `[HH:MM]` timestamps on messages and in `/history`. | +| `/wake [on\|off\|status]` | CLI-only: toggle the "Hey Hermes" wake word listener. | ### Tools & Skills @@ -117,15 +120,17 @@ Type `/` in the CLI to open the autocomplete menu. Built-in commands are case-in |---------|-------------| | `/help` | Show this help message | | `/version` | Show Hermes Agent version, build, and environment info. | +| `/whoami` | Show your slash command access level (admin / user). | | `/usage` | Show token usage, cost breakdown, session duration, and — when available from the active provider — an **Account limits** section with remaining quota / credits / plan usage pulled live from the provider's API. | -| `/credits` | Show your Nous credit balance and a top-up handoff link. | -| `/billing` | CLI Remote Spending flow for Nous — view balance, buy credits, and manage auto-reload / monthly limits. | +| `/topup` | Show your Nous balance and manage billing on the portal (replaces the old `/credits` and `/billing` commands). | +| `/subscription` (alias: `/upgrade`) | **CLI only.** View your Nous plan and change it in the browser. | | `/insights` | Show usage insights and analytics (last 30 days) | | `/platforms` (alias: `/gateway`) | Show gateway/messaging platform status (CLI-only summary view). | | `/paste` | Attach a clipboard image | | `/copy [number]` | Copy the last assistant response to clipboard (or the Nth-from-last with a number). CLI-only. | | `/image <path>` | Attach a local image file for your next prompt. | | `/debug` | Upload debug report (system info + logs) and get shareable links. Also available in messaging. | +| `/update` | Update Hermes Agent to the latest version. | | `/profile` | Show active profile name and home directory | ### Exit @@ -228,8 +233,10 @@ The messaging gateway supports the following built-in commands inside Telegram, | `/topic [off\|help\|session-id]` | **Telegram DM only.** Manage user-managed multi-session topic mode. `/topic` enables it or shows status; `/topic off` disables it and clears bindings; `/topic help` shows usage; `/topic <session-id>` inside a topic restores a previous session. See [Multi-session DM mode](/user-guide/messaging/telegram#multi-session-dm-mode-topic). | | `/title [name]` | Set or show the session title. | | `/resume [name]` | Resume a previously named session. | +| `/sessions [all] [search <query>]` | List previous sessions for this chat. `/sessions search <query>` filters by title/id match (most recently active first); `/sessions all` lists across origins (admin only). | | `/usage` | Show token usage, estimated cost breakdown (input/output), context window state, session duration, and — when available from the active provider — an **Account limits** section with remaining quota / credits pulled live from the provider's API. | -| `/credits` | Show your Nous credit balance and a top-up link that opens the portal billing page in a browser. | +| `/topup` | Show your Nous balance and manage billing on the portal. | +| `/whoami` | Show your slash command access level (admin / user). | | `/insights [days]` | Show usage analytics. | | `/reasoning [level\|show\|hide]` | Change reasoning effort or toggle reasoning display. | | `/voice [on\|off\|tts\|join\|channel\|leave\|status]` | Control spoken replies in chat. `join`/`channel`/`leave` manage Discord voice-channel mode. | @@ -260,13 +267,12 @@ The messaging gateway supports the following built-in commands inside Telegram, ## Notes -- `/skin`, `/snapshot`, `/reload`, `/tools`, `/toolsets`, `/browser`, `/config`, `/cron`, `/platforms`, `/paste`, `/image`, `/statusbar`, `/battery`, `/focus`, `/plugins`, `/busy`, `/indicator`, `/redraw`, `/clear`, `/history`, `/save`, `/copy`, `/handoff`, `/billing`, and `/quit` are **CLI-only** commands. +- `/skin`, `/snapshot`, `/reload`, `/tools`, `/toolsets`, `/browser`, `/config`, `/cron`, `/platforms`, `/paste`, `/image`, `/statusbar`, `/battery`, `/focus`, `/plugins`, `/busy`, `/indicator`, `/wake`, `/journey`, `/redraw`, `/clear`, `/history`, `/save`, `/copy`, `/handoff`, `/subscription`, and `/quit` are **CLI-only** commands. - `/skills` is **CLI-only for search/browse/install**; its write-approval review subcommands (`pending`, `approve`, `reject`, `diff`, `approval`) also work on messaging platforms when `skills.write_approval` is on. `/memory` works on **both** surfaces. - `/verbose` is **CLI-only by default**, but can be enabled for messaging platforms by setting `display.tool_progress_command: true` in `config.yaml`. When enabled, it cycles the `display.tool_progress` mode and saves to config. - `/focus` and `/verbose` share one suppression path (`display.tool_progress`), so they can never contradict each other: `/focus on` pins tool progress to `off` and stashes your mode under `display.focus_saved_tool_progress`; `/focus off` restores it; cycling `/verbose` while focus is on takes the mode back and clears the focus badge. Focus view is display-only — it never changes conversation history, the system prompt, or anything sent to the model, so it has zero prompt-cache impact. -- `/sethome`, `/update`, `/restart`, `/approve`, `/deny`, `/topic`, `/platform`, and `/commands` are **messaging-only** commands. -- `/status`, `/egress`, `/version`, `/background`, `/queue`, `/steer`, `/voice`, `/reload-mcp`, `/reload-skills`, `/rollback`, `/debug`, `/fast`, `/footer`, `/curator`, `/kanban`, `/credits`, `/suggestions`, `/blueprint`, `/learn`, `/init`, `/sessions`, and `/yolo` work in **both** the CLI and the messaging gateway. -- `/status`, `/egress`, `/version`, `/background`, `/queue`, `/steer`, `/voice`, `/reload-mcp`, `/reload-skills`, `/rollback`, `/diff`, `/debug`, `/fast`, `/footer`, `/curator`, `/kanban`, `/credits`, `/suggestions`, `/blueprint`, `/learn`, `/sessions`, and `/yolo` work in **both** the CLI and the messaging gateway. +- `/sethome`, `/restart`, `/approve`, `/deny`, `/topic`, `/platform`, and `/commands` are **messaging-only** commands. +- `/status`, `/egress`, `/version`, `/whoami`, `/background`, `/queue`, `/steer`, `/voice`, `/reload-mcp`, `/reload-skills`, `/rollback`, `/diff`, `/debug`, `/fast`, `/approvals`, `/footer`, `/curator`, `/kanban`, `/topup`, `/suggestions`, `/blueprint`, `/learn`, `/init`, `/sessions`, and `/yolo` work in **both** the CLI and the messaging gateway. - `/voice join`, `/voice channel`, and `/voice leave` are only meaningful on Discord. - In the TUI, `/sessions` shows live sessions in the current TUI process. Use `/resume [name]` or `hermes --tui --resume <id-or-title>` for saved or closed transcripts. diff --git a/website/docs/reference/tools-reference.md b/website/docs/reference/tools-reference.md index 54dc040de54..7ce5907e520 100644 --- a/website/docs/reference/tools-reference.md +++ b/website/docs/reference/tools-reference.md @@ -8,10 +8,10 @@ description: "Authoritative reference for Hermes built-in tools, grouped by tool This page documents Hermes' built-in tools, grouped by toolset. Availability varies by platform, credentials, and enabled toolsets. -**Quick counts (current registry):** ~73 tools — 10 browser tools (core) + 2 CDP-gated browser tools, 4 file tools, 4 Home Assistant tools, 3 terminal tools (`terminal`, `process`, `read_terminal`), 2 web tools, 5 Feishu tools, 7 Spotify tools (registered by the bundled `spotify` plugin), 5 Yuanbao tools, 9 kanban tools (registered when the kanban dispatcher spawns the agent), 3 project tools (desktop/GUI sessions), 2 Discord tools, and a handful of standalone tools (`memory`, `clarify`, `delegate_task`, `execute_code`, `cronjob`, `session_search`, `skill_view`/`skill_manage`/`skills_list`, `text_to_speech`, `image_generate`, `video_generate`, `vision_analyze`, `video_analyze`, `todo`, `computer_use`). +**Quick counts (current registry):** ~81 tools — 10 browser tools (core) + 2 CDP-gated browser tools, 4 file tools, 4 Home Assistant tools, 6 terminal tools (`terminal`, `process`, plus desktop-GUI-gated `read_terminal`, `close_terminal`, `open_preview`, `focus_pane`), 2 web tools, 5 Feishu tools, 7 Spotify tools (registered by the bundled `spotify` plugin), 5 Yuanbao tools, 12 kanban tools (registered when the kanban dispatcher spawns the agent), 3 project tools (desktop/GUI sessions), 2 Discord tools, 3 video tools (`video_generate`, `xai_video_edit`, `xai_video_extend`), and a handful of standalone tools (`memory`, `clarify`, `delegate_task`, `execute_code`, `cronjob`, `session_search`, `skill_view`/`skill_manage`/`skills_list`, `text_to_speech`, `image_generate`, `vision_analyze`, `video_analyze`, `todo`, `computer_use`, `x_search`). :::tip MCP Tools -In addition to built-in tools, Hermes can load tools dynamically from MCP servers. MCP tools appear with the prefix `mcp_<server>_` (e.g., `mcp_github_create_issue` for the `github` MCP server). See [MCP Integration](/user-guide/features/mcp) for configuration. +In addition to built-in tools, Hermes can load tools dynamically from MCP servers. MCP tools appear with the prefix `mcp__<server>__` (e.g., `mcp__github__create_issue` for the `github` MCP server). See [MCP Integration](/user-guide/features/mcp) for configuration. ::: ## `browser` toolset @@ -27,7 +27,7 @@ In addition to built-in tools, Hermes can load tools dynamically from MCP server | `browser_scroll` | Scroll the page in a direction. Use this to reveal more content that may be below or above the current viewport. Requires browser_navigate to be called first. | — | | `browser_snapshot` | Get a text-based snapshot of the current page's accessibility tree. Returns interactive elements with ref IDs (like @e1, @e2) for browser_click and browser_type. full=false (default): compact view with interactive elements. full=true: comp… | — | | `browser_type` | Type text into an input field identified by its ref ID. Clears the field first, then types the new text. Requires browser_navigate and browser_snapshot to be called first. | — | -| `browser_vision` | Take a screenshot of the current page and analyze it with vision AI. Use this when you need to visually understand what's on the page - especially useful for CAPTCHAs, visual verification challenges, complex layouts, or when the text snaps… | — | +| `browser_vision` | Take a screenshot of the current page so you can inspect it visually. Use this when you need to understand what the page looks like — especially for CAPTCHAs, visual verification challenges, complex layouts, or cases where the text snapshot misses important visual information. On native-vision models the screenshot is attached directly; otherwise falls back to an auxiliary vision mo… | — | ## `browser` toolset (CDP-gated tools) @@ -42,7 +42,7 @@ These two tools live in the `browser` toolset but only register when a Chrome De | Tool | Description | Requires environment | |------|-------------|----------------------| -| `clarify` | Ask the user a question when you need clarification, feedback, or a decision before proceeding. Supports two modes: 1. **Multiple choice** — provide up to 4 choices. The user picks one or types their own answer via a 5th 'Other' option. 2.… | — | +| `clarify` | Ask the user a question when you need clarification, feedback, or a decision before proceeding. Supports three modes: 1. **Single-select multiple choice** — up to 4 choices; the user picks one or types their own answer via a 5th 'Other' option. 2. **Multi-select multiple choice** — `multi_select=true` renders checkboxes and returns a list of selected choices. 3. **Open-ended** — no choices; the user types a free-form response. On the classic CLI multi-select uses Space-to-toggle checkboxes; on messaging platforms without native checkbox UIs the user replies with comma/space-separated numbers (e.g. "1, 3") or the option text. | — | ## `code_execution` toolset @@ -86,9 +86,9 @@ Scoped to the Feishu document-comment handler. Drives comment read/write operati | Tool | Description | Requires environment | |------|-------------|----------------------| | `patch` | Targeted find-and-replace edits in files. Use this instead of sed/awk in terminal. Uses fuzzy matching (9 strategies) so minor whitespace/indentation differences won't break it. Returns a unified diff. Auto-runs syntax checks after editing… | — | -| `read_file` | Read a text file with line numbers and pagination. Use this instead of cat/head/tail in terminal. Output format: 'LINE_NUM\|CONTENT'. Suggests similar filenames if not found. Use offset and limit for large files. NOTE: Cannot read images o… | — | +| `read_file` | Read a text file with line numbers and pagination. Use this instead of cat/head/tail in terminal. Output format: 'LINE_NUM\|CONTENT'. Suggests similar filenames if not found. Use offset and limit for large files. Reads exceeding ~100K characters are truncated on a line boundary and return a next_offset. Jupyter notebooks (.ipynb), Word documents (.docx), and Excel workbooks (.xlsx) a… | — | | `search_files` | Search file contents or find files by name. Use this instead of grep/rg/find/ls in terminal. Ripgrep-backed, faster than shell equivalents. Content search (target='content'): Regex search inside files. Output modes: full matches with line… | — | -| `write_file` | Write content to a file, completely replacing existing content. Use this instead of echo/cat heredoc in terminal. Creates parent directories automatically. OVERWRITES the entire file — use 'patch' for targeted edits. | — | +| `write_file` | Write content to a file, completely replacing existing content. Use this instead of echo/cat heredoc in terminal. Creates parent directories automatically. OVERWRITES the entire file — use 'patch' for targeted edits. Auto-runs syntax checks on .py/.json/.yaml/.toml and other linted languages; only NEW errors introduced by the write are surfaced. | — | ## `homeassistant` toolset @@ -131,6 +131,9 @@ Registered when the agent is either (a) spawned by the kanban dispatcher (`HERME | `kanban_create` | Fan out child tasks from the current task. Used by orchestrators and follow-up-spawning workers. | `HERMES_KANBAN_TASK` or `kanban` toolset | | `kanban_link` | Link tasks with a parent → child dependency edge. | `HERMES_KANBAN_TASK` or `kanban` toolset | | `kanban_unblock` | Move a blocked task to `ready` when all parents are done, or `todo` while any parent remains open. Orchestrator-only; hidden from dispatcher-spawned task workers. | profile with `kanban` toolset | +| `kanban_attach` | Attach a file to a task by passing its bytes inline (base64). Stored as a real attachment under the task's attachments dir, capped at 25 MB. | `HERMES_KANBAN_TASK` or `kanban` toolset | +| `kanban_attach_url` | Attach a file to a task by URL — Hermes downloads it server-side and stores it as a real attachment (capped at 25 MB). Only http/https URLs. | `HERMES_KANBAN_TASK` or `kanban` toolset | +| `kanban_attachments` | List the files attached to a task: id, filename, content_type, size, uploader, and the absolute on-disk path. | `HERMES_KANBAN_TASK` or `kanban` toolset | ## `project` toolset @@ -152,7 +155,7 @@ Tools for driving desktop [Projects](../user-guide/cli.md) — named, multi-fold | Tool | Description | Requires environment | |------|-------------|----------------------| -| `session_search` | Search past sessions stored in the local session DB, or scroll inside one. FTS5-backed retrieval; returns actual messages from the DB (no LLM calls). Three shapes: discovery (pass `query`), scroll (pass `session_id` + `around_message_id`), browse (no args). | — | +| `session_search` | Search past sessions stored in the local session DB, or scroll inside one. FTS5-backed retrieval; returns actual messages from the DB (no LLM calls). Four shapes: discovery (pass `query`), scroll (pass `session_id` + `around_message_id`), read (pass `session_id` only), browse (no args). | — | ## `skills` toolset @@ -169,6 +172,9 @@ Tools for driving desktop [Projects](../user-guide/cli.md) — named, multi-fold | `process` | Manage background processes started with terminal(background=true). Actions: 'list' (show all), 'poll' (check status + new output), 'log' (full output with pagination), 'wait' (block until done or timeout), 'kill' (terminate), 'write' (sen… | — | | `terminal` | Execute shell commands on a Linux environment. Filesystem persists between calls. Set `background=true` for long-running servers. Set `notify_on_complete=true` (with `background=true`) to get an automatic notification when the process finishes — no polling needed. Do NOT use cat/head/tail — use read_file. Do NOT use grep/rg/find — use search_files. | — | | `read_terminal` | Read what's currently shown in the in-app terminal pane of the Hermes desktop GUI (the embedded shell beside this chat). Desktop-app only. | — | +| `close_terminal` | Close the read-only terminal tab for a background process in the Hermes desktop GUI. Does NOT kill the process — only drops the tab/view; use process(action='kill') to stop it. Desktop-app only. | — | +| `open_preview` | Open a web URL, localhost dev-server URL, or file path in the preview pane beside the chat in the Hermes desktop app. Desktop-app only. | — | +| `focus_pane` | Reveal and focus a pane in the Hermes desktop app (chat, files, terminal, review, sessions). Desktop-app only. | — | ## `todo` toolset @@ -204,13 +210,15 @@ The single `video_generate` tool covers both modalities — pass `image_url` to | Tool | Description | Requires environment | |------|-------------|----------------------| | `video_generate` | Generate a video from a text prompt (text-to-video) or animate a still image (image-to-video) using the user's configured video generation backend. Pass `image_url` to animate that image; omit it to generate from text alone. The backend auto-routes to the right endpoint. Returns either an HTTP URL or an absolute file path in the `video` field. | Active `video_gen` plugin + its credential (e.g. `XAI_API_KEY`, `FAL_KEY`) | +| `xai_video_edit` | Edit an existing video with xAI Imagine. Provider-specific (separate from `video_generate`). `video_url` must be the public HTTPS MP4 URL from a prior Imagine result. | xAI Imagine credentials (SuperGrok OAuth or `XAI_API_KEY`) | +| `xai_video_extend` | Extend an existing video with xAI Imagine. Provider-specific (separate from `video_generate`). `video_url` must be the public HTTPS MP4 URL from a prior Imagine result. | xAI Imagine credentials (SuperGrok OAuth or `XAI_API_KEY`) | ## `web` toolset | Tool | Description | Requires environment | |------|-------------|----------------------| | `web_search` | Search the web for information. Returns up to 5 results by default with titles, URLs, and descriptions. Accepts an optional `limit` (1-100, default 5). The query is passed through to the configured backend, so operators such as `site:domain`, `filetype:pdf`, `intitle:word`, `-term`, and `"exact phrase"` may work when the backend supports them. | EXA_API_KEY or PARALLEL_API_KEY or FIRECRAWL_API_KEY or TAVILY_API_KEY | -| `web_extract` | Extract content from web page URLs. Returns page content in markdown format. Also works with PDF URLs — pass the PDF link directly and it converts to markdown text. Pages under 5000 chars return full markdown; larger pages are LLM-summarized. | EXA_API_KEY or PARALLEL_API_KEY or FIRECRAWL_API_KEY or TAVILY_API_KEY | +| `web_extract` | Extract content from web page URLs. Returns clean page content in markdown/text (no LLM summarization — fast). Also works with PDF URLs (arxiv papers, documents) — pass the PDF link directly. Pages within the char budget (default 15000) return whole; larger pages return a head+tail window with a footer pointing at the full text saved on disk. Max 5 URLs per call. | EXA_API_KEY or PARALLEL_API_KEY or FIRECRAWL_API_KEY or TAVILY_API_KEY | ## `x_search` toolset diff --git a/website/docs/reference/toolsets-reference.md b/website/docs/reference/toolsets-reference.md index 1f8092e7b43..05b24828633 100644 --- a/website/docs/reference/toolsets-reference.md +++ b/website/docs/reference/toolsets-reference.md @@ -68,8 +68,8 @@ Or in-session: | `computer_use` | `computer_use` | Background desktop control via cua-driver — does not steal cursor/focus. Works with any tool-capable model. macOS, Windows, and Linux; requires `cua-driver` on `$PATH`. | | `context_engine` | (varies) | Runtime tools exposed by the active context-engine plugin (empty until a plugin populates it). | | `image_gen` | `image_generate` | Text-to-image generation via FAL.ai (with opt-in OpenAI / xAI backends). | -| `video_gen` | `video_generate` | Text-to-video and image-to-video via plugin-registered backends (xAI Grok-Imagine, FAL.ai Veo 3.1 / Pixverse v6 / Kling O3). Pass `image_url` to animate an image; omit it for text-to-video. | -| `kanban` | `kanban_block`, `kanban_comment`, `kanban_complete`, `kanban_create`, `kanban_heartbeat`, `kanban_link`, `kanban_list`, `kanban_show`, `kanban_unblock` | Multi-agent coordination tools. Registered for dispatcher-spawned task workers (`HERMES_KANBAN_TASK`) and for profiles that explicitly list the `kanban` toolset by name (the `all`/`*` wildcard does **not** enable it). Workers mark tasks done, block, heartbeat, comment, and create/link follow-up tasks; orchestrator profiles additionally get board-routing tools like list/unblock. `delegate_task` children are not Kanban run owners: their schema strips/disables this toolset and runtime guards reject direct board mutations, even if parent `HERMES_KANBAN_*` env vars are present. | +| `video_gen` | `video_generate`, `xai_video_edit`, `xai_video_extend` | Text-to-video and image-to-video via plugin-registered backends (xAI Grok-Imagine, FAL.ai Veo 3.1 / Pixverse v6 / Kling O3). Pass `image_url` to animate an image; omit it for text-to-video. `xai_video_edit` / `xai_video_extend` are provider-specific edit/extend tools, gated on xAI Imagine credentials. | +| `kanban` | `kanban_attach`, `kanban_attach_url`, `kanban_attachments`, `kanban_block`, `kanban_comment`, `kanban_complete`, `kanban_create`, `kanban_heartbeat`, `kanban_link`, `kanban_list`, `kanban_show`, `kanban_unblock` | Multi-agent coordination tools. Registered for dispatcher-spawned task workers (`HERMES_KANBAN_TASK`) and for profiles that explicitly list the `kanban` toolset by name (the `all`/`*` wildcard does **not** enable it). Workers mark tasks done, block, heartbeat, comment, and create/link follow-up tasks; orchestrator profiles additionally get board-routing tools like list/unblock. `delegate_task` children are not Kanban run owners: their schema strips/disables this toolset and runtime guards reject direct board mutations, even if parent `HERMES_KANBAN_*` env vars are present. | | `memory` | `memory` | Persistent cross-session memory management. | | `project` | `project_create`, `project_list`, `project_switch` | Create and switch desktop [Projects](../user-guide/cli.md) (named, multi-folder workspaces). GUI / desktop sessions only. | | `safe` | `image_generate`, `vision_analyze`, `web_extract`, `web_search` (via `includes`) | Read-only research + media generation. No file writes, no terminal, no code execution. | @@ -77,7 +77,7 @@ Or in-session: | `session_search` | `session_search` | Search past conversation sessions. | | `skills` | `skill_manage`, `skill_view`, `skills_list` | Skill CRUD and browsing. | | `spotify` | `spotify_albums`, `spotify_devices`, `spotify_library`, `spotify_playback`, `spotify_playlists`, `spotify_queue`, `spotify_search` | Native Spotify control (playback, queue, search, playlists, albums, library). Registered by the bundled `spotify` plugin. | -| `terminal` | `process`, `terminal` | Shell command execution and background process management. | +| `terminal` | `close_terminal`, `focus_pane`, `open_preview`, `process`, `read_terminal`, `terminal` | Shell command execution and background process management. `read_terminal`, `close_terminal`, `open_preview`, and `focus_pane` drive the desktop GUI's embedded panes and are check_fn-gated — they only register in desktop-app sessions. | | `todo` | `todo` | Task list management within a session. | | `tts` | `text_to_speech` | Text-to-speech audio generation. | | `vision` | `vision_analyze` | Image analysis via vision-capable models. | @@ -92,9 +92,9 @@ Platform toolsets define the complete tool configuration for a deployment target | Toolset | Differences from `hermes-cli` | |---------|-------------------------------| -| `hermes-cli` | Full toolset — the default for interactive CLI sessions. Includes file, terminal, web, browser, memory, skills, vision, image_gen, todo, tts, delegation, code_execution, cronjob, session_search, and clarify, plus the `safe` (read-only) bundle. | -| `hermes-acp` | Drops `clarify`, `cronjob`, `image_generate`, `text_to_speech`, and all four Home Assistant tools. Focused on coding tasks in IDE context. | -| `hermes-api-server` | Drops `clarify` and `text_to_speech`. Keeps everything else — suitable for programmatic access where user interaction isn't possible. | +| `hermes-cli` | Full toolset — the default for interactive CLI sessions. Includes file, terminal (plus the desktop-GUI pane tools `read_terminal`, `close_terminal`, `open_preview`, `focus_pane`), web, browser, memory, skills, vision, image_gen, todo, tts, delegation, code_execution, cronjob, session_search, clarify, computer_use, Home Assistant, and the kanban tools (all check_fn-gated at runtime). | +| `hermes-acp` | Drops `clarify`, `cronjob`, `image_generate`, `text_to_speech`, `computer_use`, all four Home Assistant tools, the kanban tools, and the desktop-GUI pane tools. Focused on coding tasks in IDE context. | +| `hermes-api-server` | Drops `clarify`, `text_to_speech`, `computer_use`, the kanban tools, and the desktop-GUI pane tools. Keeps everything else — suitable for programmatic access where user interaction isn't possible. | | `hermes-cron` | Same as `hermes-cli`. | | `hermes-telegram` | Same as `hermes-cli`. | | `hermes-discord` | Adds `discord` and `discord_admin` on top of `hermes-cli`. | @@ -114,7 +114,7 @@ Platform toolsets define the complete tool configuration for a deployment target | `hermes-weixin` | Same as `hermes-cli`. | | `hermes-yuanbao` | Adds the five `yb_*` tools (DM/group/sticker) on top of `hermes-cli`. | | `hermes-homeassistant` | Same as `hermes-cli` (the Home Assistant tools are already present by default and activate when `HASS_TOKEN` is set). | -| `hermes-webhook` | Same as `hermes-cli`. | +| `hermes-webhook` | Restricted safe subset — only `web_search`, `web_extract`, `vision_analyze`, and `clarify`. Webhook-triggered runs get no terminal, file, or browser access. | | `hermes-gateway` | Internal gateway orchestrator toolset — union of every `hermes-<platform>` toolset; used when the gateway needs to accept any message source. | ## Dynamic Toolsets diff --git a/website/docs/user-guide/configuration.md b/website/docs/user-guide/configuration.md index 09558a4e9c9..1f2e37db306 100644 --- a/website/docs/user-guide/configuration.md +++ b/website/docs/user-guide/configuration.md @@ -83,7 +83,9 @@ delegation: api_key: ${DELEGATION_KEY} ``` -Multiple references in a single value work: `url: "${HOST}:${PORT}"`. If a referenced variable is not set, the placeholder is kept verbatim (`${UNDEFINED_VAR}` stays as-is). Only the `${VAR}` syntax is supported — bare `$VAR` is not expanded. +Multiple references in a single value work: `url: "${HOST}:${PORT}"`. If a referenced variable is not set, the placeholder is kept verbatim (`${UNDEFINED_VAR}` stays as-is) and a warning is logged. Bare `$VAR` is not expanded. + +Cursor-style SecretRef syntax is also accepted: `${env:VAR_NAME}` resolves exactly like `${VAR_NAME}` (the `env:` prefix is stripped), so MCP or provider snippets copied from Cursor / Claude configs work unchanged in both `config.yaml` and the `mcp_servers` block. Other SecretRef sources (`${file:...}`, `${vault:...}`, `${bitwarden:...}`) are **not** resolved inline — external secret backends inject their values into the environment at startup via the `secrets:` block, so reference them as `${env:NAME}` instead; unknown prefixes warn once and stay verbatim. For AI provider setup (OpenRouter, Anthropic, Copilot, custom endpoints, self-hosted LLMs, fallback models, etc.), see [AI Providers](/integrations/providers). @@ -410,22 +412,13 @@ If terminal commands fail immediately or the terminal tool is reported as disabl When in doubt, set `terminal.backend` back to `local` and verify that commands run there first. -### Remote-to-Host File Sync on Teardown +### Remote-to-Host State Sync on Teardown -For the **SSH**, **Modal**, and **Daytona** backends (anywhere the agent's working tree lives on a different machine than the host running Hermes), Hermes tracks files the agent touched inside the remote sandbox and, on session teardown / sandbox cleanup, **syncs the modified files back to the host** under `~/.hermes/cache/remote-syncs/<session-id>/`. +For the **SSH**, **Modal**, and **Daytona** backends, Hermes pushes your `~/.hermes/` state (credential files, skills, cache) into the remote sandbox during the session, and on teardown **syncs changed state files back** to their original host locations. Files that differ from what was originally pushed (compared by content hash) are applied back in place; new remote files under a synced directory (e.g. a skill the agent created remotely) are mapped back to the corresponding host path. Upload-only credential files are never overwritten on the host. -- Triggers on: session close, `/new`, `/reset`, gateway message timeout, `delegate_task` subagent completion when the child used a remote backend. -- Covers the whole tree the agent modified, not just files it explicitly opened. Additions, edits, and deletions are all captured. -- The remote sandbox may have been torn down by the time you go looking; the local `~/.hermes/cache/remote-syncs/…` copy is the authoritative record of what the agent changed. -- Large binary outputs (model checkpoints, raw datasets) are capped by size — the sync skips files over `file_sync_max_mb` (default `100`). Bump that if you expect bigger artifacts to come back. - -```yaml -terminal: - file_sync_max_mb: 100 # default — sync files up to 100 MB each - file_sync_enabled: true # default — set false to skip the sync entirely -``` - -This is how you recover results from ephemeral cloud sandboxes that get destroyed after the session ends, without having to tell the agent to explicitly `scp` or `modal volume put` every artifact. +- The sync-back retries up to 3 times with backoff and refuses to extract remote archives larger than 2 GiB. +- Docker and Singularity use bind mounts (live host filesystem view) and don't need this. +- This covers Hermes state (`~/.hermes/`), **not** arbitrary working-tree files inside the sandbox — have the agent copy important artifacts out explicitly (e.g. `scp`, `modal volume put`) before the sandbox is destroyed. ### Docker Volume Mounts @@ -627,10 +620,10 @@ With `memory.write_approval: true`, memory writes need your approval before they Controls how much content Hermes loads from each automatic context file before applying head/tail truncation. This applies to files injected into the system prompt such as `SOUL.md`, `.hermes.md`, `AGENTS.md`, `CLAUDE.md`, and `.cursorrules`. It does **not** affect the `read_file` tool. ```yaml -context_file_max_chars: 20000 # default +context_file_max_chars: null # default — dynamic cap scaled to the model's context window (floor 20K, ceiling 500K chars) ``` -Raise it when you intentionally keep larger identity or project-context files and run models with enough context window to carry them: +Set a positive integer to pin a fixed cap instead of the dynamic behavior: ```yaml context_file_max_chars: 25000 @@ -749,6 +742,7 @@ compression: target_ratio: 0.20 # Fraction of threshold to preserve as recent tail protect_last_n: 20 # Min recent messages to keep uncompressed protect_first_n: 3 # Non-system head messages pinned across compactions (0 = pin nothing) + in_place: true # Compact on the same session id (no rotation) — see below idle_compact_after_seconds: 0 # Opt-in idle compaction (0 = disabled) — see below hygiene_hard_message_limit: 5000 # Gateway safety valve — see below hygiene_timeout_seconds: 30 # Max seconds of NO summary-model output before hygiene compression is cut off @@ -782,6 +776,8 @@ Older configs with `compression.summary_model`, `compression.summary_provider`, `protect_first_n` controls how many **non-system** head messages are pinned across every compaction. Default `3` — the opening user/assistant exchange survives every summarizer pass so the original goal stays visible. On long-running rolling-compaction sessions where the opening turn is no longer relevant, set `protect_first_n: 0` to pin nothing but the system prompt + summary + tail. The system prompt itself is always preserved regardless of this setting. +`in_place` (default `true`) controls what happens to the session identity when compaction fires. When `true`, compaction rewrites the message list and rebuilds the system prompt **without rotating the session id** — the conversation keeps one durable id for its whole life (no `parent_session_id` chain, no `name #2` / `#3` renumbering in session lists). Compaction is non-destructive: the live context is compacted, but the pre-compaction turns are soft-archived under the same id (marked inactive/compacted) — still searchable via `session_search` and recoverable, not deleted. Hooks see the mode via the `in_place` field on the `session:compress` event. Set `in_place: false` to restore the legacy behavior where each compaction rotates to a new session id linked to the old one. + `threshold_tokens` sets an optional **absolute token cap** for the compression trigger. When set, compression fires at the lower of the ratio-based `threshold` and this absolute count — so compression never fires later than the user's preferred token number regardless of which model is active. This solves the problem where switching between models with different context windows (e.g. 1M → 400K) shifts the absolute trigger point. The cap is clamped to the model's context length, so setting it higher than the model supports is safe — the ratio-based threshold is used instead. Default `null` (disabled — ratio-based threshold only). The cap survives model switches and fallback activations. `idle_compact_after_seconds` is an **opt-in, time-based** trigger that complements the size-based `threshold`. Default `0` (disabled). When set above 0, a session that resumes after at least that many seconds of inactivity compacts its accumulated history up front, before the first reply — so a long-lived thread (e.g. a Telegram conversation you come back to hours later) doesn't re-read its full stale context on every subsequent turn. It never fires when the context is already at or below the post-compression target (`threshold × target_ratio`), and it honors the same failure-cooldown, anti-thrash, and per-session lock guards as every automatic compaction. Example: `idle_compact_after_seconds: 1800` compacts after 30 minutes idle. @@ -864,10 +860,26 @@ agent: api_max_retries: 3 # Retries per provider before fallback engages (default: 3) ``` -When the iteration budget is fully exhausted, the CLI shows a notification to the user: `⚠ Iteration budget reached (90/90) — response may be incomplete`. +When the iteration budget is fully exhausted, the CLI shows a notification to the user: `⚠ Iteration budget reached (500/500) — response may be incomplete`. `agent.api_max_retries` controls how many times Hermes retries a provider API call on transient errors (rate limits, connection drops, 5xx) **before** fallback-provider switching engages. The default is `3` — four attempts total. If you have [fallback providers](/user-guide/features/fallback-providers) configured and want to fail over faster, drop this to `0` so the first transient error on your primary immediately hands off to the fallback instead of churning retries against the flaky endpoint. +## Verify-on-Stop (coding verification) + +When enabled, Hermes refuses to accept a final answer on a turn where the agent edited code in a workspace but produced no fresh verification evidence (a passing test run, build, lint, etc.) — it injects a synthetic follow-up asking the agent to verify or explain why it can't. Doc/markdown/skill-only edits never trigger it, and the loop is bounded so it can never trap the agent. + +```yaml +agent: + verify_on_stop: false # true | false | "auto" (surface-aware: on for CLI/TUI/desktop, off for messaging) + verify_guidance: true # Append creative-UI / clean-diff guidance to the missing-evidence nudge + max_verify_nudges: 3 # Cap on consecutive continue nudges per turn (built-in + pre_verify hooks) + coding_instructions: "" # Standing project-wide coding rules appended to the coding brief +``` + +`verify_on_stop` accepts `true` (on everywhere), `false` (off), or `"auto"` (on for interactive coding surfaces — CLI, TUI, desktop — and programmatic callers; off for messaging surfaces like Telegram/Discord where the verification narrative reads as chat noise). The config migration turns it **off** on existing installs, so treat off as the effective default and opt in explicitly. The `HERMES_VERIFY_ON_STOP` env var overrides the config value when set. + +For a user/plugin policy gate at the same point — keep the agent going with your own checks — see the [`pre_verify` hook](/user-guide/features/hooks#pre_verify). + ## Standing Goals (`/goal`) When a standing goal is active, Hermes judges whether each assistant response satisfies it. If not, it feeds a continuation prompt back into the same session and keeps working until the goal is done, the turn budget is exhausted, or the user pauses/clears it. The turn budget is the real backstop — judge failures fail **open** (continue) so a flaky judge never wedges progress. @@ -886,13 +898,13 @@ Hermes has separate timeout layers for streaming, plus a stale detector for non- | Timeout | Default | Local providers | Config / env | |---------|---------|----------------|--------------| | Socket read timeout | 120s | Auto-raised to 1800s | `HERMES_STREAM_READ_TIMEOUT` | -| Stale stream detection | 180s | Auto-disabled | `HERMES_STREAM_STALE_TIMEOUT` | -| Stale non-stream detection | 300s | Auto-disabled when left implicit | `providers.<id>.stale_timeout_seconds` or `HERMES_API_CALL_STALE_TIMEOUT` | +| Stale stream detection | 180s | Raised to a 900s ceiling (`agent.local_stream_stale_timeout`) | `HERMES_STREAM_STALE_TIMEOUT` | +| Stale non-stream detection | 90s | Auto-disabled when left implicit | `providers.<id>.stale_timeout_seconds` or `HERMES_API_CALL_STALE_TIMEOUT` | | API call (non-streaming) | 1800s | Unchanged | `providers.<id>.request_timeout_seconds` / `timeout_seconds` or `HERMES_API_TIMEOUT` | The **socket read timeout** controls how long httpx waits for the next chunk of data from the provider. Local LLMs can take minutes for prefill on large contexts before producing the first token, so Hermes raises this to 30 minutes when it detects a local endpoint. If you explicitly set `HERMES_STREAM_READ_TIMEOUT`, that value is always used regardless of endpoint detection. -The **stale stream detection** kills connections that receive SSE keep-alive pings but no actual content. This is disabled entirely for local providers since they don't send keep-alive pings during prefill. +The **stale stream detection** kills connections that receive SSE keep-alive pings but no actual content. For local providers (which don't send keep-alive pings during prefill) the default is raised to a finite 900-second ceiling instead of the 180s base — configurable via `agent.local_stream_stale_timeout` or the `HERMES_LOCAL_STREAM_STALE_TIMEOUT` env var. The **stale non-stream detection** kills non-streaming calls that produce no response for too long. By default Hermes disables this on local endpoints to avoid false positives during long prefills. If you explicitly set `providers.<id>.stale_timeout_seconds`, `providers.<id>.models.<model>.stale_timeout_seconds`, or `HERMES_API_CALL_STALE_TIMEOUT`, that explicit value is honored even on local endpoints. @@ -1411,7 +1423,7 @@ agent: | Value | Behavior | |-------|----------| -| `"auto"` (default) | Enabled for models matching: `gpt`, `codex`, `gemini`, `gemma`, `grok`. Disabled for all others (Claude, DeepSeek, Qwen, etc.). | +| `"auto"` (default) | Enabled for models matching: `gpt`, `codex`, `gemini`, `gemma`, `grok`, `glm`, `qwen`, `deepseek`. Disabled for all others (e.g. Claude). | | `true` | Always enabled, regardless of model. Useful if you notice your current model describing actions instead of performing them. | | `false` | Always disabled, regardless of model. | | `["gpt", "codex", "qwen", "llama"]` | Enabled only when the model name contains one of the listed substrings (case-insensitive). | @@ -1422,7 +1434,7 @@ When enabled, three layers of guidance may be added to the system prompt: 1. **General tool-use enforcement** (all matched models) — instructs the model to make tool calls immediately instead of describing intentions, keep working until the task is complete, and never end a turn with a promise of future action. -2. **OpenAI execution discipline** (GPT and Codex models only) — additional guidance addressing GPT-specific failure modes: abandoning work on partial results, skipping prerequisite lookups, hallucinating instead of using tools, and declaring "done" without verification. +2. **OpenAI execution discipline** (GPT, Codex, and Grok models) — additional guidance addressing GPT-specific failure modes: abandoning work on partial results, skipping prerequisite lookups, hallucinating instead of using tools, and declaring "done" without verification. 3. **Google operational guidance** (Gemini and Gemma models only) — conciseness, absolute paths, parallel tool calls, and verify-before-edit patterns. @@ -1474,7 +1486,7 @@ This mirrors Claude Code's per-session WebSearch and subagent caps (v2.1.212), w ```yaml tts: - provider: "edge" # "edge" | "elevenlabs" | "openai" | "minimax" | "mistral" | "gemini" | "xai" | "neutts" + provider: "edge" # "edge" | "elevenlabs" | "openai" | "minimax" | "mistral" | "gemini" | "xai" | "neutts" | "kittentts" | "piper" | "deepinfra" speed: 1.0 # Global speed multiplier (fallback for all providers) edge: voice: "en-US-AriaNeural" # 322 voices, 74 languages @@ -1527,14 +1539,15 @@ display: interim_assistant_messages: true # Gateway: send natural mid-turn assistant updates as separate messages show_commentary: true # Codex models: deliver commentary-channel progress narration as visible mid-turn updates skin: default # Built-in or custom CLI skin (see user-guide/features/skins) - personality: "kawaii" # Legacy cosmetic field still surfaced in some summaries + personality: "" # Legacy cosmetic field still surfaced in some summaries compact: false # Compact output mode (less whitespace) resume_display: full # full (show previous messages on resume) | minimal (one-liner only) bell_on_complete: false # Play terminal bell when agent finishes (great for long tasks) - show_reasoning: false # Show model reasoning/thinking above each response (toggle with /reasoning show|hide) + show_reasoning: true # Show model reasoning/thinking above each response (default: true; toggle with /reasoning show|hide) streaming: false # Stream tokens to terminal as they arrive (real-time output) show_cost: false # Show estimated $ cost in the CLI status bar - timestamps: false # When true, prefixes user and assistant labels with [HH:MM] timestamps in the CLI / TUI transcript + timestamps: false # When true, prefixes user and assistant labels with timestamps in the CLI / TUI transcript + timestamp_format: "%H:%M" # strftime format for those timestamps (e.g. "%b-%d %H:%M" for month-day) tool_preview_length: 0 # Max chars for tool call previews (0 = no limit, show full paths/commands) turn_summary: true # CLI only: print a one-line post-turn accounting footer after each interactive turn spinner_token_flow: true # CLI only: append live cumulative turn tokens to the spinner timer @@ -1712,7 +1725,7 @@ Hashes are deterministic — the same user always maps to the same hash, so the stt: enabled: true # Auto-transcribe inbound voice messages (default: true) echo_transcripts: true # Post raw transcripts back to the chat as 🎙️ "..." (default: true) - provider: "local" # "local" | "groq" | "openai" | "mistral" + provider: "local" # "local" | "groq" | "openai" | "mistral" | "xai" | "elevenlabs" | "deepinfra" | ... language: "en" # GLOBAL language hint for every provider (per-provider language wins); set "" for auto-detect local: model: "base" # tiny, base, small, medium, large-v3 @@ -1784,10 +1797,10 @@ When enabled, responses appear token-by-token inside a streaming box. Tool calls ```yaml streaming: - enabled: true # Enable progressive message editing - transport: edit # "edit" (progressive message editing) or "off" - edit_interval: 0.3 # Seconds between message edits - buffer_threshold: 40 # Characters before forcing an edit flush + enabled: true # Enable progressive message editing (default: false) + transport: auto # "auto" (default) | "edit" (progressive message editing) | "off" + edit_interval: 0.8 # Seconds between message edits (default: 0.8) + buffer_threshold: 24 # Characters before forcing an edit flush (default: 24) cursor: " ▉" # Cursor shown during streaming fresh_final_after_seconds: 0 # Opt in to fresh final (Telegram) when preview is this old ``` @@ -2158,11 +2171,11 @@ The delegation provider uses the same credential resolution as CLI/gateway start ## Clarify -Configure the clarification prompt behavior: +Configure how long the gateway waits for a response to a clarifying question. The canonical key is `agent.clarify_timeout` (default `3600` seconds); a legacy top-level `clarify.timeout` is still honored if explicitly set: ```yaml -clarify: - timeout: 120 # Seconds to wait for user clarification response +agent: + clarify_timeout: 3600 # Seconds to wait for user clarification response (0 or less = unlimited) ``` ## Context Files (SOUL.md, AGENTS.md) diff --git a/website/docs/user-guide/configuring-models.md b/website/docs/user-guide/configuring-models.md index e220b534983..ebc433c9b9f 100644 --- a/website/docs/user-guide/configuring-models.md +++ b/website/docs/user-guide/configuring-models.md @@ -153,6 +153,38 @@ auxiliary: When `fallback_chain` is absent, `auto` uses the top-level `fallback_providers` chain before the built-in auxiliary discovery chain. +## Per-provider request options + +Provider entries (`providers.<name>` or items in the `custom_providers` list) accept two knobs that shape how Hermes talks to the endpoint: + +**`extra_headers`** — a mapping of extra HTTP headers attached to every LLM request routed to that provider's base URL. They are applied last, after URL/profile defaults and user header overrides, so they survive credential swaps and client rebuilds. Useful for Cloudflare Access service tokens, proxy auth, or custom bearer schemes: + +```yaml +custom_providers: + - name: my-gateway + base_url: https://llm.internal.example.com/v1 + api_key: sk-... + extra_headers: + CF-Access-Client-Id: "xxxx.access" + CF-Access-Client-Secret: "yyyy" +``` + +Header values routinely carry credentials — Hermes never logs them. `extra_headers` applies to OpenAI-compatible routes; the `anthropic_messages` and `bedrock_converse` API modes do not use it. + +**`discover_models`** — set to `false` (default `true`) to skip querying the endpoint's `/models` listing and use only the `models` you configured on the entry. Handy for gateways whose model listing is slow, unreliable, or noisy: + +```yaml +custom_providers: + - name: my-gateway + base_url: https://llm.internal.example.com/v1 + discover_models: false + models: + - my-finetune-v2 + - my-finetune-v1 +``` + +With discovery off, the model picker (`hermes model`, `/model`) shows the configured list instead of a live probe. + ## When does it take effect? - **CLI** (`hermes chat`): next `hermes chat` invocation. diff --git a/website/docs/user-guide/features/api-server.md b/website/docs/user-guide/features/api-server.md index cfb87fcfd94..cca3d11b5cf 100644 --- a/website/docs/user-guide/features/api-server.md +++ b/website/docs/user-guide/features/api-server.md @@ -568,10 +568,15 @@ gateway: key: your-secret-key cors_origins: http://localhost:3000 model_name: my-hermes + max_concurrent_runs: 10 # concurrent-run cap; 0 disables the limit ``` `port`, `key`, `host`, `cors_origins`, and `model_name` are automatically bridged into the platform's `extra` settings, so they behave exactly like their `API_SERVER_*` environment-variable counterparts. Environment variables take precedence over `config.yaml` values. The block is also accepted under `gateway.platforms.api_server:` or a top-level `platforms.api_server:` section. +### Concurrent-run cap + +The API server limits how many agent runs may execute at once across the OpenAI-compatible and Runs endpoints. The cap is read from `gateway.api_server.max_concurrent_runs` (default **10**; `0` disables the limit, negative values clamp to 0). When the cap is reached, new run-starting requests are rejected with **HTTP 429** `Too many concurrent runs (max N)` — clients should back off and retry. + ## Security Headers All responses include security headers: diff --git a/website/docs/user-guide/features/built-in-plugins.md b/website/docs/user-guide/features/built-in-plugins.md index 722105fcf67..9ffbf639f70 100644 --- a/website/docs/user-guide/features/built-in-plugins.md +++ b/website/docs/user-guide/features/built-in-plugins.md @@ -211,16 +211,17 @@ Lets the agent **join, transcribe, and participate in Google Meet calls** — ta - A headless virtual participant that joins a Meet URL using browser automation - Live transcription of the meeting audio via the configured STT provider -- A `meet_summarize` / `meet_speak` / `meet_followup` toolset the agent invokes to act on what it heard -- Post-meeting artifacts (transcript, speaker-attributed notes, action items) saved under `~/.hermes/cache/google_meet/<meeting_id>/` +- A `meet_join` / `meet_status` / `meet_transcript` / `meet_leave` / `meet_say` toolset the agent invokes to join calls, poll the live transcript, and act on what it heard +- Post-meeting artifacts (transcript, status) saved under `~/.hermes/workspace/meetings/<meeting_id>/` **Setup:** ```bash hermes plugins enable google_meet -# Prompts you to sign in via the plugin's OAuth flow on first use — -# needs a Google account with Meet access. Host approval may be required -# if the meeting enforces "only invited participants can join". +hermes meet setup # preflight: playwright, chromium, auth file +hermes meet auth # opens a browser to sign into Google and saves session state — + # needs a Google account with Meet access. Host approval may be + # required if the meeting enforces "only invited participants can join". ``` Usage from chat: @@ -231,7 +232,7 @@ The agent kicks off the meeting join, streams the transcription back into its co **When to use it:** recurring standups where you want a bot to transcribe + summarize for async attendees; deposition-style interviews where you want structured notes; any case where you'd otherwise need Fireflies / Otter / Grain. When you'd rather not have an AI listening in — don't enable it. -**Disabling:** `hermes plugins disable google_meet`. Any cached transcripts and recordings stay in `~/.hermes/cache/google_meet/` until you remove them. +**Disabling:** `hermes plugins disable google_meet`. Any saved transcripts stay in `~/.hermes/workspace/meetings/` until you remove them. ### hermes-achievements diff --git a/website/docs/user-guide/features/code-execution.md b/website/docs/user-guide/features/code-execution.md index f3beaa473f5..1585f415a24 100644 --- a/website/docs/user-guide/features/code-execution.md +++ b/website/docs/user-guide/features/code-execution.md @@ -293,4 +293,4 @@ Hermes always writes the script and the auto-generated `hermes_tools.py` RPC stu ## Platform Support -Code execution requires Unix domain sockets and is available on **Linux and macOS only**. It is automatically disabled on Windows — the agent falls back to regular sequential tool calls. +Code execution is available on **Linux, macOS, and Windows**. On Linux and macOS the RPC channel uses a Unix domain socket; on Windows, where `AF_UNIX` is unreliable, Hermes automatically falls back to a loopback TCP socket for the sandbox RPC transport. Remote terminal backends (Docker/SSH/Modal/etc.) use a file-based RPC transport instead and additionally require Python 3 inside the backend. diff --git a/website/docs/user-guide/features/context-files.md b/website/docs/user-guide/features/context-files.md index 195201439f2..c3e3f61edfb 100644 --- a/website/docs/user-guide/features/context-files.md +++ b/website/docs/user-guide/features/context-files.md @@ -109,7 +109,7 @@ Context files are loaded by `build_context_files_prompt()` in `agent/prompt_buil 1. **Scan working directory** — checks for `.hermes.md` → `AGENTS.md` → `CLAUDE.md` → `.cursorrules` (first match wins) 2. **Content is read** — each file is read as UTF-8 text 3. **Security scan** — content is checked for prompt injection patterns -4. **Truncation** — files exceeding `context_file_max_chars` characters (default 20,000) are head/tail truncated (70% head, 20% tail, with a marker in the middle) +4. **Truncation** — files exceeding the character cap are head/tail truncated (70% head, 20% tail, with a marker in the middle). The cap is an explicit `context_file_max_chars` from config.yaml when set; otherwise it scales dynamically with the model's context window (floor 20,000 chars, ceiling 500,000) 5. **Assembly** — all sections are combined under a `# Project Context` header 6. **Injection** — the assembled content is added to the system prompt @@ -171,7 +171,7 @@ This scanner protects against common injection patterns, but it's not a substitu | Limit | Value | |-------|-------| -| Max chars per file | `context_file_max_chars` (default 20,000, ~7,000 tokens) | +| Max chars per file | `context_file_max_chars` when set; otherwise dynamic (scales with model context window, floor 20,000, ceiling 500,000) | | Head truncation ratio | 70% | | Tail truncation ratio | 20% | | Truncation marker | 10% (shows char counts and suggests using file tools) | diff --git a/website/docs/user-guide/features/credential-pools.md b/website/docs/user-guide/features/credential-pools.md index 5d8503b3a79..c4fd897544c 100644 --- a/website/docs/user-guide/features/credential-pools.md +++ b/website/docs/user-guide/features/credential-pools.md @@ -33,7 +33,7 @@ Your request → Second 429 → rotate to next pool key → All keys exhausted → fallback_model (different provider) → 402 billing error? - → Immediately rotate to next pool key (24h cooldown) + → Immediately rotate to next pool key (1h cooldown) → 401 auth expired? → Try refreshing the token (OAuth) → Refresh failed → rotate to next pool key @@ -141,10 +141,12 @@ The pool handles different errors differently: | Error | Behavior | Cooldown | |-------|----------|----------| | **429 Rate Limit** | Retry same key once (transient). Second consecutive 429 rotates to next key | 1 hour | -| **402 Billing/Quota** | Immediately rotate to next key | 24 hours | -| **401 Auth Expired** | Try refreshing the OAuth token first. Rotate only if refresh fails | — | +| **402 Billing/Quota** | Immediately rotate to next key | 1 hour | +| **401 Auth Expired** | Try refreshing the OAuth token first. Rotate only if refresh fails | 5 minutes | | **All keys exhausted** | Fall through to `fallback_model` if configured | — | +Provider-supplied `reset_at` timestamps override these default cooldowns. + The `has_retried_429` flag resets on every successful API call, so a single transient 429 doesn't trigger rotation. ## Custom Endpoint Pools diff --git a/website/docs/user-guide/features/curator.md b/website/docs/user-guide/features/curator.md index 2497aca61b3..1d5b1933f28 100644 --- a/website/docs/user-guide/features/curator.md +++ b/website/docs/user-guide/features/curator.md @@ -32,7 +32,7 @@ If you want to see what the curator *would* do before it runs for real, run `her A run has two phases: 1. **Automatic transitions** (deterministic, no LLM). Skills unused for `stale_after_days` (30) become `stale`; skills unused for `archive_after_days` (90) are moved to `~/.hermes/skills/.archive/`. This is the always-on pruning behavior — it runs whenever the curator is enabled, with no aux-model cost. -2. **LLM consolidation** (single aux-model pass, `max_iterations=8`) — **OFF by default**. When `curator.consolidate: true`, the forked agent surveys the agent-created skills, can read any of them with `skill_view`, and decides per-skill whether to keep, patch (via `skill_manage`), consolidate overlapping ones into class-level umbrellas, or archive via the terminal tool. Consolidation treats a skill as a full package: if a skill has `references/`, `templates/`, `scripts/`, `assets/`, or relative links to those paths, the curator must either keep it standalone, re-home the needed support files and rewrite paths, or archive the entire package unchanged — not flatten only `SKILL.md` into another skill's `references/` file. +2. **LLM consolidation** (single aux-model pass with a high iteration ceiling — a full curation sweep typically takes 50–100 API calls) — **OFF by default**. When `curator.consolidate: true`, the forked agent surveys the agent-created skills, can read any of them with `skill_view`, and decides per-skill whether to keep, patch (via `skill_manage`), consolidate overlapping ones into class-level umbrellas, or archive via the terminal tool. Consolidation treats a skill as a full package: if a skill has `references/`, `templates/`, `scripts/`, `assets/`, or relative links to those paths, the curator must either keep it standalone, re-home the needed support files and rewrite paths, or archive the entire package unchanged — not flatten only `SKILL.md` into another skill's `references/` file. :::info Consolidation is opt-in By default the curator only **prunes** — the deterministic inactivity pass marks skills stale and archives long-unused ones. The opinionated LLM **consolidation** pass (umbrella-building, merging overlapping skills) is off by default because it costs aux-model tokens on every run and makes broad structural changes to your library. Turn it on with `curator.consolidate: true`, or run it once on demand with `hermes curator run --consolidate`. diff --git a/website/docs/user-guide/features/delegation.md b/website/docs/user-guide/features/delegation.md index d40a0dfc107..14fbad48058 100644 --- a/website/docs/user-guide/features/delegation.md +++ b/website/docs/user-guide/features/delegation.md @@ -158,10 +158,13 @@ If omitted, subagents use the same model as the parent. `delegate_task` does not accept a model-facing `toolsets` parameter. Each subagent inherits the parent's enabled toolsets so the model cannot grant a child capabilities that the parent does not have. Configure the parent's tools before starting the conversation if delegated work needs additional capabilities. Certain tools are blocked for subagents even when the parent has them: -- `delegation` — blocked for leaf subagents (the default). Retained for `role="orchestrator"` children, bounded by `max_spawn_depth` — see [Depth Limit and Nested Orchestration](#depth-limit-and-nested-orchestration) below. +- `delegate_task` — blocked for leaf subagents (the default). Retained for `role="orchestrator"` children, bounded by `max_spawn_depth` — see [Depth Limit and Nested Orchestration](#depth-limit-and-nested-orchestration) below. - `clarify` — subagents cannot interact with the user - `memory` — no writes to shared persistent memory -- `code_execution` — children should reason step-by-step +- `send_message` — no cross-platform side effects +- `cronjob` — no scheduling more work in the parent's name + +Both roles retain `execute_code` (programmatic tool calling) so children can batch mechanical work. ## Max Iterations diff --git a/website/docs/user-guide/features/deliverable-mode.md b/website/docs/user-guide/features/deliverable-mode.md index 52e1736f77c..d01847ebb05 100644 --- a/website/docs/user-guide/features/deliverable-mode.md +++ b/website/docs/user-guide/features/deliverable-mode.md @@ -41,12 +41,13 @@ Three pieces fit together: | Category | Extensions | Delivery | |---|---|---| | Images | `.png .jpg .jpeg .gif .webp .bmp .tiff .svg` | Inline embed | -| Video | `.mp4 .mov .avi .mkv .webm` | Inline embed (where supported) | -| Audio | `.mp3 .wav .ogg .m4a .flac` | Voice / audio attachment | -| Documents | `.pdf .docx .doc .odt .rtf .txt .md` | File upload | -| Data | `.xlsx .xls .csv .tsv .json .xml .yaml .yml` | File upload | -| Presentations | `.pptx .ppt .odp` | File upload | -| Archives | `.zip .tar .gz .tgz .bz2 .7z` | File upload | +| Video | `.mp4 .mov .avi .mkv .webm .3gp` | Inline embed (where supported) | +| Audio | `.mp3 .m2a .wav .ogg .opus .m4a .flac` | Voice / audio attachment | +| Documents | `.pdf .docx .doc .odt .rtf .txt .md .epub` | File upload | +| Data | `.xlsx .xls .ods .csv .tsv .json .xml .yaml .yml` | File upload | +| Geospatial | `.kmz .kml .geojson .gpx` | File upload | +| Presentations | `.pptx .ppt .odp .key` | File upload | +| Archives | `.zip .tar .gz .tgz .bz2 .xz .7z .rar .apk .ipa` | File upload | | Web | `.html .htm` | File upload | `.py`, `.log`, and other source-file extensions are intentionally excluded so diff --git a/website/docs/user-guide/features/extending-the-dashboard.md b/website/docs/user-guide/features/extending-the-dashboard.md index ad7f4273727..50f4958b12b 100644 --- a/website/docs/user-guide/features/extending-the-dashboard.md +++ b/website/docs/user-guide/features/extending-the-dashboard.md @@ -747,7 +747,7 @@ Routes are mounted under `/api/plugins/<name>/`, so the above becomes: - `GET /api/plugins/my-plugin/data` - `POST /api/plugins/my-plugin/action` -Plugin API routes bypass session-token authentication since the dashboard server binds to localhost by default. **Don't expose the dashboard on a public interface with `--host 0.0.0.0` if you run untrusted plugins** — their routes become reachable too. +Plugin API routes sit behind the dashboard's normal auth gate — unauthenticated requests get a `401` before the plugin route runs, and requests to a disabled plugin's routes are rejected at request time. Still, **don't expose the dashboard on a public interface with `--host 0.0.0.0` if you run untrusted plugins** — an authenticated session can reach their routes too. #### Accessing Hermes internals diff --git a/website/docs/user-guide/features/fallback-providers.md b/website/docs/user-guide/features/fallback-providers.md index 5355d7a4281..2c0248b06fb 100644 --- a/website/docs/user-guide/features/fallback-providers.md +++ b/website/docs/user-guide/features/fallback-providers.md @@ -428,5 +428,5 @@ See [Scheduled Tasks (Cron)](/user-guide/features/cron) for full configuration d | Approval classification | Layered (see above) | `auxiliary.approval` | | Title generation | Layered (see above) | `auxiliary.title_generation` | | Triage specifier | Layered (see above) | `auxiliary.triage_specifier` | -| Delegation | Provider override only (no automatic fallback) | `delegation.provider` / `delegation.model` | -| Cron jobs | Per-job provider override only (no automatic fallback) | Per-job `provider` / `model` | +| Delegation | Inherits the parent's `fallback_providers` chain; optional provider/model override | `delegation.provider` / `delegation.model` | +| Cron jobs | Inherit the configured `fallback_providers` chain; optional per-job provider override | Per-job `provider` / `model` | diff --git a/website/docs/user-guide/features/goals.md b/website/docs/user-guide/features/goals.md index 50b0a17e876..2bf3fd939ca 100644 --- a/website/docs/user-guide/features/goals.md +++ b/website/docs/user-guide/features/goals.md @@ -137,7 +137,7 @@ After every turn, Hermes calls an auxiliary model with: - The standing goal text - The agent's most recent final response (last ~4 KB of text) -- A system prompt telling the judge to reply with strict JSON: `{"done": <bool>, "reason": "<one-sentence rationale>"}` +- A system prompt telling the judge to reply with strict one-line JSON: `{"verdict": "done" | "continue" | "wait", "reason": "<one-sentence rationale>"}` (wait verdicts add `wait_on_session` / `wait_on_pid` / `wait_for_seconds`; the legacy `{"done": <bool>, "reason": "..."}` shape is still accepted) The judge is deliberately conservative: it marks a goal `done` only when the response **explicitly** confirms the goal is complete, when the final deliverable is clearly produced, or when the goal is unachievable/blocked (treated as DONE with a block reason so we don't burn budget on impossible tasks). diff --git a/website/docs/user-guide/features/hooks.md b/website/docs/user-guide/features/hooks.md index 52417bf5741..25be5f9b480 100644 --- a/website/docs/user-guide/features/hooks.md +++ b/website/docs/user-guide/features/hooks.md @@ -78,9 +78,10 @@ async def handle(event_type: str, context: dict): | `session:start` | New messaging session created | `platform`, `user_id`, `session_id`, `session_key` | | `session:end` | Session ended (before reset) | `platform`, `user_id`, `session_key` | | `session:reset` | User ran `/new` or `/reset` | `platform`, `user_id`, `session_key` | -| `agent:start` | Agent begins processing a message | `platform`, `user_id`, `session_id`, `message` | +| `session:compress` | Context compression completed for a session | `platform`, `session_id`, `old_session_id` (empty when compacted in place), `in_place` (bool — `true` = transcript compacted on the same id, `false` = rotated from `old_session_id`), `compression_count` | +| `agent:start` | Agent begins processing a message | `platform`, `user_id`, `chat_id`, `thread_id` (forum-topic / thread root id; empty when not in a thread), `chat_type` (`"dm"` \| `"group"` \| `"forum"`; empty if unknown), `session_id`, `message` (truncated to 500 chars) | | `agent:step` | Each iteration of the tool-calling loop | `platform`, `user_id`, `session_id`, `iteration`, `tool_names` | -| `agent:end` | Agent finishes processing | `platform`, `user_id`, `session_id`, `message`, `response` | +| `agent:end` | Agent finishes processing | same keys as `agent:start`, plus `response` (truncated to 500 chars) | | `reaction:added` | An emoji reaction was added to a message the bot can see (Slack adapter currently). Requires the `reactions:read` scope + the `reaction_added` bot event subscription; the bot must be a member of the channel. | `platform`, `reaction`, `user_id`, `item_user_id`, `item_type`, `channel_id`, `message_ts`, `team_id`, `event_ts`, `raw_event` | | `reaction:removed` | An emoji reaction was removed from a message the bot can see. Requires the `reaction_removed` bot event subscription. | same shape as `reaction:added` | | `command:*` | Any slash command executed | `platform`, `user_id`, `command`, `args` | @@ -89,6 +90,10 @@ async def handle(event_type: str, context: dict): Handlers registered for `command:*` fire for any `command:` event (`command:model`, `command:reset`, etc.). Monitor all slash commands with a single subscription. +:::tip Threaded replies +A handler posting a follow-up message into the same Telegram forum topic should include `message_thread_id=int(thread_id)` when `chat_type == "forum"` and `thread_id` is non-empty. +::: + ### Examples #### Telegram Alert on Long Tasks @@ -367,6 +372,10 @@ def register(ctx): ctx.register_hook("post_llm_call", my_sync_callback) ctx.register_hook("on_session_start", my_init_callback) ctx.register_hook("on_session_end", my_cleanup_callback) + # Kanban board lifecycle (fire after the board DB change commits): + ctx.register_hook("kanban_task_claimed", my_claim_callback) # dispatcher process + ctx.register_hook("kanban_task_completed", my_done_callback) # worker process + ctx.register_hook("kanban_task_blocked", my_blocked_callback) # worker process ``` **General rules for all hooks:** @@ -1286,7 +1295,7 @@ The hook is guarded on a non-empty, non-interrupted response — it will not fir ## Shell Hooks -Declare shell-script hooks in your `cli-config.yaml` and Hermes will run them as subprocesses whenever the corresponding plugin-hook event fires — in both CLI and gateway sessions. No Python plugin authoring required. +Declare shell-script hooks in your `~/.hermes/config.yaml` and Hermes will run them as subprocesses whenever the corresponding plugin-hook event fires — in both CLI and gateway sessions. No Python plugin authoring required. Use shell hooks when you want a drop-in, single-file script (Bash, Python, anything with a shebang) to: @@ -1454,7 +1463,7 @@ Three escape hatches bypass the interactive prompt — any one is sufficient: 1. `--accept-hooks` flag on the CLI (e.g. `hermes --accept-hooks chat`) 2. `HERMES_ACCEPT_HOOKS=1` environment variable -3. `hooks_auto_accept: true` in `cli-config.yaml` +3. `hooks_auto_accept: true` in `~/.hermes/config.yaml` Non-TTY runs (gateway, cron, CI) need one of these three — otherwise any newly-added hook silently stays un-registered and logs a warning. diff --git a/website/docs/user-guide/features/kanban-tutorial.md b/website/docs/user-guide/features/kanban-tutorial.md index 7224ce5cbb1..8280631cade 100644 --- a/website/docs/user-guide/features/kanban-tutorial.md +++ b/website/docs/user-guide/features/kanban-tutorial.md @@ -10,7 +10,7 @@ hermes dashboard # opens http://127.0.0.1:9119 in your browser # click Kanban in the left nav ``` -The dashboard is the most comfortable place for **you** to watch the system. Agent workers the dispatcher spawns never see the dashboard or the CLI — they drive the board through a dedicated `kanban_*` [toolset](./kanban#how-workers-interact-with-the-board) (`kanban_show`, `kanban_list`, `kanban_complete`, `kanban_block`, `kanban_heartbeat`, `kanban_comment`, `kanban_create`, `kanban_link`, `kanban_unblock`). All three surfaces — dashboard, CLI, worker tools — route through the same per-board SQLite DB (`~/.hermes/kanban.db` for the default board, `~/.hermes/kanban/boards/<slug>/kanban.db` for any board you create later), so each board is consistent no matter which side of the fence a change came from. +The dashboard is the most comfortable place for **you** to watch the system. Agent workers the dispatcher spawns never see the dashboard or the CLI — they drive the board through a dedicated `kanban_*` [toolset](./kanban#how-workers-interact-with-the-board) (`kanban_show`, `kanban_list`, `kanban_complete`, `kanban_block`, `kanban_heartbeat`, `kanban_comment`, `kanban_attach`, `kanban_attach_url`, `kanban_attachments`, `kanban_create`, `kanban_link`, `kanban_unblock`). All three surfaces — dashboard, CLI, worker tools — route through the same per-board SQLite DB (`~/.hermes/kanban.db` for the default board, `~/.hermes/kanban/boards/<slug>/kanban.db` for any board you create later), so each board is consistent no matter which side of the fence a change came from. This tutorial uses the `default` board throughout. If you want multiple isolated queues (one per project / repo / domain), see [Boards (multi-project)](./kanban#boards-multi-project) in the overview — the same CLI / dashboard / worker flows apply per board, and workers physically cannot see tasks on other boards. diff --git a/website/docs/user-guide/features/kanban.md b/website/docs/user-guide/features/kanban.md index 2b68409249c..a2ecb1bb5b7 100644 --- a/website/docs/user-guide/features/kanban.md +++ b/website/docs/user-guide/features/kanban.md @@ -14,7 +14,7 @@ Hermes Kanban is a durable task board, shared across all your Hermes profiles, t The board has two front doors, both backed by the same `~/.hermes/kanban.db`: -- **Agents drive the board through a dedicated `kanban_*` toolset** — `kanban_show`, `kanban_list`, `kanban_complete`, `kanban_block`, `kanban_heartbeat`, `kanban_comment`, `kanban_create`, `kanban_link`, `kanban_unblock`. The dispatcher spawns each worker with these tools already in its schema; orchestrator profiles can also enable the `kanban` toolset explicitly. The model reads and routes tasks by calling tools directly, *not* by shelling out to `hermes kanban`. See [How workers interact with the board](#how-workers-interact-with-the-board) below. +- **Agents drive the board through a dedicated `kanban_*` toolset** — `kanban_show`, `kanban_list`, `kanban_complete`, `kanban_block`, `kanban_heartbeat`, `kanban_comment`, `kanban_attach`, `kanban_attach_url`, `kanban_attachments`, `kanban_create`, `kanban_link`, `kanban_unblock`. The dispatcher spawns each worker with these tools already in its schema; orchestrator profiles can also enable the `kanban` toolset explicitly. The model reads and routes tasks by calling tools directly, *not* by shelling out to `hermes kanban`. See [How workers interact with the board](#how-workers-interact-with-the-board) below. - **You (and scripts, and cron) drive the board through `hermes kanban …`** on the CLI, `/kanban …` as a slash command, or the dashboard. These are for humans and automation — the places without a tool-calling model behind them. Both surfaces route through the same `kanban_db` layer, so reads see a consistent view and writes can't drift. The rest of this page shows CLI examples because they're easy to copy-paste, but every CLI verb has a tool-call equivalent the model uses. @@ -296,6 +296,9 @@ parent, missing input, unmet capability) before unblocking, or raise | `kanban_block` | Stop work and route by why: `kind=dependency` (waits in `todo`, auto-resumes), `needs_input`/`capability`/`transient` (surface to a human). Repeated same-kind re-blocks auto-escalate to `triage`. | `reason` | | `kanban_heartbeat` | Signal liveness during long operations. Pure side-effect. | — | | `kanban_comment` | Append a durable note to the task thread. | `task_id`, `body` | +| `kanban_attach` | Attach a file to a task by passing its bytes inline (base64); stored under the task's attachments dir (25 MB cap). | file bytes + name | +| `kanban_attach_url` | Attach a file to a task by URL. | `url` | +| `kanban_attachments` | List a task's attachments. | — | | `kanban_create` | (Orchestrators) fan out into child tasks with an `assignee`, optional `parents`, `skills`, etc. | `title`, `assignee` | | `kanban_link` | (Orchestrators) add a `parent_id → child_id` dependency edge after the fact. | `parent_id`, `child_id` | | `kanban_unblock` | (Orchestrators) move a blocked task to `ready` when all parents are done, or `todo` while any parent remains open. | `task_id` | @@ -454,6 +457,33 @@ hermes kanban create "audit auth flow" \ The dispatcher emits one `--skills <name>` flag per skill listed, so the worker spawns with all of them loaded on top of the auto-injected kanban guidance. The skill names must match skills that are actually installed on the assignee's profile (run `hermes skills list` to see what's available); there's no runtime install. +### Per-task model override + +Pin a task's worker to a specific model (and optionally provider), independent of the assignee profile's default: + +```bash +# At creation +hermes kanban create "hard refactor" --assignee coder \ + --model claude-opus-4.6 --provider anthropic + +# Or later — takes effect on the next dispatch +hermes kanban set-model t_abcd claude-opus-4.6 --provider anthropic +hermes kanban set-model t_abcd none # clear the override +``` + +The dispatcher spawns the worker with the pinned model (`--provider <name>` is passed when set; `--provider` requires a model). The dashboard's per-task model dropdown drives the same `model_override` field. With no override, the worker uses its profile's configured model. + +### Lifecycle plugin hooks + +Board transitions fire [plugin hooks](/user-guide/features/hooks#plugin-hooks): `kanban_task_claimed`, `kanban_task_completed`, and `kanban_task_blocked`, each carrying `task_id` and `profile_name`. Hooks fire **after** the board DB change commits, so callbacks always see durable state. Note the process split: `kanban_task_claimed` fires in the **dispatcher** process, while `kanban_task_completed`/`kanban_task_blocked` fire in the **worker** process — register the hook in the dispatcher profile to observe every transition centrally. + +```python +def register(ctx): + def on_blocked(task_id=None, profile_name=None, **kw): + ctx.dispatch_tool("terminal", {"command": f"notify-send 'kanban blocked: {task_id}'"}) + ctx.register_hook("kanban_task_blocked", on_blocked) +``` + ### Goal-mode cards (`--goal`) By default each worker gets **one shot** at its card — do the work, call `kanban_complete`/`kanban_block`, exit. Pass `--goal` (CLI) or `goal_mode=True` (the `kanban_create` tool / dashboard) to instead run that worker in a **goal loop**, the same Ralph-style engine behind the `/goal` slash command: after every turn an auxiliary judge checks the worker's output against the card's title + body (treated as the acceptance criteria), and if the work isn't done — and the turn budget remains — the worker keeps going **in the same session** until the judge agrees, the worker terminates the task itself, or the budget runs out (which **blocks** the card for human review rather than exiting silently). diff --git a/website/docs/user-guide/features/mcp.md b/website/docs/user-guide/features/mcp.md index aa494b697f9..12de41ef987 100644 --- a/website/docs/user-guide/features/mcp.md +++ b/website/docs/user-guide/features/mcp.md @@ -742,6 +742,23 @@ mcp_servers: enabled: false ``` +## MCP Elicitation Support + +MCP servers can ask the user for structured input mid-tool-call via the `elicitation/create` protocol (mcp Python SDK ≥ 1.11.0). Hermes routes **form-mode** elicitations through its existing approval surface — an interactive prompt in the CLI/TUI, or approval buttons on gateway platforms like Telegram and Slack — so the request reaches you wherever the session lives. **URL-mode** elicitations (where a server points you at an external URL) are declined as unsupported. + +Elicitation is **enabled by default** per server. Configure it under the `elicitation` key: + +```yaml +mcp_servers: + my_server: + command: "my-mcp-server" + elicitation: + enabled: true # default: true + timeout: 300 # seconds to wait for your answer (default: 300) +``` + +The 5-minute default timeout mirrors the gateway approval default so users on async surfaces have time to respond before the server gives up. Per-server metrics (requests, accepted, declined, errors) are tracked on the handler. + ## 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. diff --git a/website/docs/user-guide/features/memory-providers.md b/website/docs/user-guide/features/memory-providers.md index 5aedf04bc7d..099d73b3ebe 100644 --- a/website/docs/user-guide/features/memory-providers.md +++ b/website/docs/user-guide/features/memory-providers.md @@ -290,7 +290,7 @@ Context database by Volcengine (ByteDance) with filesystem-style knowledge hiera | **Data storage** | Self-hosted (local or cloud) | | **Cost** | Free (open-source, AGPL-3.0) | -**Tools:** `viking_search` (semantic search), `viking_read` (tiered: abstract/overview/full), `viking_browse` (filesystem navigation), `viking_remember` (store facts), `viking_add_resource` (ingest URLs/docs) +**Tools (6):** `viking_search` (semantic search), `viking_read` (tiered: abstract/overview/full), `viking_browse` (filesystem navigation), `viking_remember` (store facts), `viking_forget` (delete a memory file by exact `viking://` URI), `viking_add_resource` (ingest URLs/docs) **Setup:** ```bash @@ -505,7 +505,7 @@ Cloud memory API with hybrid search (Vector + BM25 + Reranking), 7 memory types, | **Data storage** | RetainDB Cloud | | **Cost** | $20/month | -**Tools:** `retaindb_profile` (user profile), `retaindb_search` (semantic search), `retaindb_context` (task-relevant context), `retaindb_remember` (store with type + importance), `retaindb_forget` (delete memories) +**Tools (10):** `retaindb_profile` (user profile), `retaindb_search` (semantic search), `retaindb_context` (task-relevant context), `retaindb_remember` (store with type + importance), `retaindb_forget` (delete memories), plus file tools: `retaindb_upload_file`, `retaindb_list_files`, `retaindb_read_file`, `retaindb_ingest_file`, `retaindb_delete_file` **Setup:** ```bash @@ -659,11 +659,11 @@ hermes memory setup | Provider | Storage | Cost | Tools | Dependencies | Unique Feature | |----------|---------|------|-------|-------------|----------------| | **Honcho** | Cloud | Paid | 5 | `honcho-ai` | Dialectic user modeling + session-scoped context | -| **OpenViking** | Self-hosted | Free | 5 | `openviking` + server | Filesystem hierarchy + tiered loading | +| **OpenViking** | Self-hosted | Free | 6 | `openviking` + server | Filesystem hierarchy + tiered loading | | **Mem0** | Cloud/Self-hosted | Free/Paid | 4 | `mem0ai` | Server-side LLM extraction + self-hosted/OSS modes | | **Hindsight** | Cloud/Local | Free/Paid | 3 | `hindsight-client` | Knowledge graph + reflect synthesis | | **Holographic** | Local | Free | 2 | None | HRR algebra + trust scoring | -| **RetainDB** | Cloud | $20/mo | 5 | `requests` | Delta compression | +| **RetainDB** | Cloud | $20/mo | 10 | `requests` | Delta compression | | **ByteRover** | Local/Cloud | Free/Paid | 3 | `brv` CLI | Pre-compression extraction | | **Supermemory** | Cloud/Self-hosted | Free/Paid | 4 | `supermemory` | Context fencing + session graph ingest + multi-container | | **Memori** | Cloud | Free/Paid | 5 | `hermes-memori` | Tool-aware memory + structured recall | diff --git a/website/docs/user-guide/features/memory.md b/website/docs/user-guide/features/memory.md index 20c37afa12f..3cfc20fe5c7 100644 --- a/website/docs/user-guide/features/memory.md +++ b/website/docs/user-guide/features/memory.md @@ -206,6 +206,24 @@ See [Session Search Tool](/user-guide/sessions#session-search-tool) for the thre **Memory** is for critical facts that should always be in context. **Session search** is for "did we discuss X last week?" queries where the agent needs to recall specifics from past conversations. +## Learning Journey (`/journey`) + +The learning journey is a timeline view of everything Hermes has learned — saved skills and memory entries plotted over time (oldest at top, newest at bottom), with a playable "constellation" scrubber that replays the build-up. The same graph data drives three surfaces: + +- **Classic CLI / standalone** — `hermes journey` (aliases: `hermes learning`, `hermes memory-graph`) renders the timeline in the terminal. Flags: `--play` animates the build-up (`--fps` to tune it), `--width`/`--height` override the render size, `--no-color` disables color, and `--json` dumps the raw graph payload. +- **TUI** — `/journey` (aliases: `/learning`, `/memory-graph`) opens the timeline as an overlay. +- **Desktop app** — `/journey` opens the Star Map / memory-graph panel, an interactive visual of the same nodes. + +Beyond viewing, the journey is also where you **prune and correct** what Hermes has learned: + +| Command | What it does | +|---------|--------------| +| `hermes journey list` | List node ids — skill names and `memory:<source>:<index>` ids for memory chunks. | +| `hermes journey delete <node> [-y]` | Delete a node. Skills are **archived** (restorable), memory chunks are removed. `-y` skips the confirmation. | +| `hermes journey edit <node>` | Open the node's content (a skill's `SKILL.md` or the memory chunk) in `$EDITOR`. | + +The same `list` / `delete <id>` / `edit <id>` subcommands work from the in-chat `/journey` command on the CLI, and the desktop panel offers edit/delete on nodes directly. + ## Configuration ```yaml diff --git a/website/docs/user-guide/features/pets.md b/website/docs/user-guide/features/pets.md index de082f36c8a..f36f90a471c 100644 --- a/website/docs/user-guide/features/pets.md +++ b/website/docs/user-guide/features/pets.md @@ -135,6 +135,31 @@ In the desktop app you can manage the pet two ways: Both adopt/toggle/resize the floating mascot in place — size changes apply instantly; adopting a new pet lights it up within a moment. +### Roaming + +Settings → Appearance has a **Roam** toggle: when enabled, the pet wanders the +window on its own while the agent is idle — walking surfaces, pausing, and +hopping between spots. Roaming only runs while the pet is in-window, active, +and the agent is at rest; any agent-driven state (working, celebrating) +immediately takes over. The toggle is off by default and persists across +restarts. + +### Alt+wheel resizing + +Hold **Alt** and scroll the mouse wheel over the pet to resize it in place — +in the app window and on the popped-out overlay alike. The overlay zooms +toward the cursor position and the resulting scale is persisted, so it +survives restarts and stays in sync with the in-app pet. + +### Vibe reactions + +Say something nice to the agent — "good bot", "thank you", "ily", `<3`, or a +heart emoji — and the pet reacts with floating hearts (desktop) or a heart +flash (CLI/TUI). Detection is a curated, token-free lexicon matched locally on +each user message (no model call); it fires on affection and gratitude aimed at +the agent, not general positive sentiment. All surfaces — CLI pet, TUI, desktop +floating pet, and the pop-out overlay — react off the same signal. + ### Pop-out overlay **Shift-click** the floating pet to pop it out into its own transparent, diff --git a/website/docs/user-guide/features/skills.md b/website/docs/user-guide/features/skills.md index e1d5158bad0..1857648c1f4 100644 --- a/website/docs/user-guide/features/skills.md +++ b/website/docs/user-guide/features/skills.md @@ -812,7 +812,7 @@ hermes skills install my-org/hermes-skills/deploy-runbook #### Non-default paths -If your skills don't live under `skills/` (common when you're adding a `skills/` subtree to an existing project), edit the tap entry in `~/.hermes/.hub/taps.json`: +If your skills don't live under `skills/` (common when you're adding a `skills/` subtree to an existing project), edit the tap entry in `~/.hermes/skills/.hub/taps.json`: ```json { @@ -854,7 +854,7 @@ Inside a running session: /skills tap remove myorg/skills-repo ``` -Taps are stored in `~/.hermes/.hub/taps.json` (created on demand). +Taps are stored in `~/.hermes/skills/.hub/taps.json` (created on demand). ## Bundled skill updates (`hermes skills reset`) diff --git a/website/docs/user-guide/features/skins.md b/website/docs/user-guide/features/skins.md index d83fda7d650..75353479f12 100644 --- a/website/docs/user-guide/features/skins.md +++ b/website/docs/user-guide/features/skins.md @@ -56,7 +56,7 @@ Controls all color values throughout the CLI. Values are hex color strings. | `banner_dim` | Muted text in the banner (separators, secondary labels) | `#B8860B` (dark goldenrod) | | `banner_text` | Body text in the banner (tool names, skill names) | `#FFF8DC` (cornsilk) | | `ui_accent` | General UI accent color (highlights, active elements) | `#FFBF00` | -| `ui_label` | UI labels and tags | `#4dd0e1` (teal) | +| `ui_label` | UI labels and tags | `#DAA520` (goldenrod) | | `ui_ok` | Success indicators (checkmarks, completion) | `#4caf50` (green) | | `ui_error` | Error indicators (failures, blocked) | `#ef5350` (red) | | `ui_warn` | Warning indicators (caution, approval prompts) | `#ffa726` (orange) | @@ -67,7 +67,7 @@ Controls all color values throughout the CLI. Values are hex color strings. | `session_border` | Session ID dim border color | `#8B8682` | | `status_bar_bg` | Background color for the TUI status / usage bar | `#1a1a2e` | | `voice_status_bg` | Background color for the voice-mode status badge | `#1a1a2e` | -| `selection_bg` | Background color for the TUI mouse-selection highlighter. Falls back to `completion_menu_current_bg` when unset. | `#333355` | +| `selection_bg` | Background color for the TUI mouse-selection highlighter. Falls back to `completion_menu_current_bg` when unset. | `#3a3a55` | | `completion_menu_bg` | Background color for the completion menu list | `#1a1a2e` | | `completion_menu_current_bg` | Background color for the active completion row | `#333355` | | `completion_menu_meta_bg` | Background color for the completion meta column | `#1a1a2e` | diff --git a/website/docs/user-guide/features/tools.md b/website/docs/user-guide/features/tools.md index 92a5bc06904..b5cb84a40bc 100644 --- a/website/docs/user-guide/features/tools.md +++ b/website/docs/user-guide/features/tools.md @@ -84,7 +84,7 @@ terminal: docker_image: python:3.11-slim ``` -**One persistent container, shared across the whole process.** Hermes starts a single long-lived container on first use (`docker run -d ... sleep 2h`) and routes every terminal, file, and `execute_code` call through `docker exec` into that same container. Working-directory changes, installed packages, environment tweaks, and files written to `/workspace` all carry over from one tool call to the next, across `/new`, `/reset`, and `delegate_task` subagents, for the lifetime of the Hermes process. The container is stopped and removed on shutdown. +**One persistent container, shared across the whole process.** Hermes starts a single long-lived container on first use (`docker run -d ... sleep infinity`) and routes every terminal, file, and `execute_code` call through `docker exec` into that same container. Working-directory changes, installed packages, environment tweaks, and files written to `/workspace` all carry over from one tool call to the next, across `/new`, `/reset`, and `delegate_task` subagents, for the lifetime of the Hermes process. The container is stopped and removed on shutdown. This means the Docker backend behaves like a persistent sandbox VM, not a fresh container per command. If you `pip install foo` once, it's there for the rest of the session. If you `cd /workspace/project`, subsequent `ls` calls see that directory. See [Configuration → Docker Backend](../configuration.md#docker-backend) for the full lifecycle details and the `container_persistent` flag that controls whether `/workspace` and `/root` survive across Hermes restarts. diff --git a/website/docs/user-guide/features/tts.md b/website/docs/user-guide/features/tts.md index d413f6d3a0c..dca24845959 100644 --- a/website/docs/user-guide/features/tts.md +++ b/website/docs/user-guide/features/tts.md @@ -14,7 +14,7 @@ If you have a paid [Nous Portal](https://portal.nousresearch.com) subscription, ## Text-to-Speech -Convert text to speech with ten providers: +Convert text to speech with eleven providers: | Provider | Quality | Cost | API Key | |----------|---------|------|---------| @@ -25,6 +25,7 @@ Convert text to speech with ten providers: | **Mistral (Voxtral TTS)** | Excellent | Paid | `MISTRAL_API_KEY` | | **Google Gemini TTS** | Excellent | Free tier | `GEMINI_API_KEY` | | **xAI TTS** | Excellent | Paid | `XAI_API_KEY` | +| **DeepInfra TTS** | Good | Paid | `DEEPINFRA_API_KEY` | | **NeuTTS** | Good | Free (local) | None needed | | **KittenTTS** | Good | Free (local) | None needed | | **Piper** | Good | Free (local) | None needed | @@ -43,7 +44,7 @@ Convert text to speech with ten providers: ```yaml # In ~/.hermes/config.yaml tts: - provider: "edge" # "edge" | "elevenlabs" | "openai" | "minimax" | "mistral" | "gemini" | "xai" | "neutts" | "kittentts" | "piper" + provider: "edge" # "edge" | "elevenlabs" | "openai" | "minimax" | "mistral" | "gemini" | "xai" | "deepinfra" | "neutts" | "kittentts" | "piper" speed: 1.0 # Global speed multiplier (provider-specific settings override this) edge: voice: "en-US-AriaNeural" # 322 voices, 74 languages @@ -60,7 +61,7 @@ tts: minimax: region: "global" # "global" or "cn"; see selection rules below model: "speech-02-hd" # speech-02-hd (default), speech-02-turbo - voice_id: "English_Graceful_Lady" # See https://platform.minimax.io/faq/system-voice-id + voice_id: "English_expressive_narrator" # See https://platform.minimax.io/faq/system-voice-id speed: 1 # 0.5 - 2.0 vol: 1 # 0 - 10 pitch: 0 # -12 - 12 @@ -458,7 +459,8 @@ Local transcription works out of the box when `faster-whisper` is installed. If ```yaml # In ~/.hermes/config.yaml stt: - provider: "local" # "local" | "groq" | "openai" | "mistral" | "xai" + provider: "local" # "local" | "groq" | "openai" | "mistral" | "xai" | "elevenlabs" | "deepinfra" + language: "en" # Global language hint applied to every provider unless a per-provider language overrides it; set "" to restore auto-detect local: model: "base" # tiny, base, small, medium, large-v3 language: "" # optional ISO-639-1 hint; blank = use HERMES_LOCAL_STT_LANGUAGE if set, else auto-detect @@ -608,7 +610,7 @@ The shell command runs under the same user as Hermes with full filesystem access ### Python plugin providers (STT) -For STT engines that aren't built-in AND can't be expressed as a shell command (need a Python SDK, OAuth-refreshing auth, streaming chunks, etc.), register a Python plugin via `ctx.register_transcription_provider()`. The plugin **coexists with** the 6 built-in providers (`local`, `local_command`, `groq`, `openai`, `mistral`, `xai`) and the `stt.providers.<name>: type: command` registry — built-ins keep their native implementations and always win on name collision; command providers win over plugins of the same name (config is more local than plugin install). +For STT engines that aren't built-in AND can't be expressed as a shell command (need a Python SDK, OAuth-refreshing auth, streaming chunks, etc.), register a Python plugin via `ctx.register_transcription_provider()`. The plugin **coexists with** the 8 built-in providers (`local`, `local_command`, `groq`, `openai`, `mistral`, `xai`, `elevenlabs`, `deepinfra`) and the `stt.providers.<name>: type: command` registry — built-ins keep their native implementations and always win on name collision; command providers win over plugins of the same name (config is more local than plugin install). #### When to pick which (STT) diff --git a/website/docs/user-guide/features/wake-word.md b/website/docs/user-guide/features/wake-word.md index e8438d765f2..96c49425325 100644 --- a/website/docs/user-guide/features/wake-word.md +++ b/website/docs/user-guide/features/wake-word.md @@ -80,7 +80,7 @@ wake_word: wake_word: enabled: false surface: auto # eligible surface: "auto" | "cli" | "tui" | "gui" - provider: openwakeword # "openwakeword" (free, local) | "porcupine" + provider: openwakeword # "openwakeword" (free, local) | "sherpa" (free, any phrase) | "porcupine" phrase: "hey hermes" # cosmetic label only — detection is keyed by the model/keyword below sensitivity: 0.6 # 0.0-1.0 — higher = stricter (fewer false triggers), consistent across all engines confirmation_frames: 3 # openWakeWord only — consecutive over-threshold frames required to fire diff --git a/website/docs/user-guide/features/web-dashboard.md b/website/docs/user-guide/features/web-dashboard.md index 842c134e7c1..ccaf77c010f 100644 --- a/website/docs/user-guide/features/web-dashboard.md +++ b/website/docs/user-guide/features/web-dashboard.md @@ -27,7 +27,7 @@ This starts a local web server and opens `http://127.0.0.1:9119` in your browser | `--port` | `9119` | Port to run the web server on | | `--host` | `127.0.0.1` | Bind address | | `--no-open` | — | Don't auto-open the browser | -| `--insecure` | off | Allow binding to non-localhost hosts (**DANGEROUS** — exposes API keys on the network; pair with a firewall and strong auth) | +| `--insecure` | off | **Deprecated / no-op.** Formerly bypassed auth on a non-loopback bind; it no longer disables authentication — a public bind always requires an auth provider (password or OAuth) | | `--isolated` | off | When launched from a named profile (`worker dashboard`), run a dedicated per-profile server instead of routing to the machine dashboard | ```bash @@ -300,7 +300,7 @@ Browse, search, and toggle installed skills and toolsets, and install new ones f ### MCP -Manage [MCP](/integrations/mcp) servers without the CLI. The same `mcp_servers` +Manage [MCP](./mcp) servers without the CLI. The same `mcp_servers` block in `config.yaml` that `hermes mcp` reads from. **Your MCP servers:** @@ -383,7 +383,7 @@ Creating a shell hook (note the consent checkbox and the run-arbitrary-commands ![New shell hook modal](/img/dashboard/admin-hook-create.png) :::warning Security -The web dashboard reads and writes your `.env` file, which contains API keys and secrets. It binds to `127.0.0.1` by default — only accessible from your local machine. If you bind to `0.0.0.0`, anyone on your network can view and modify your credentials. The dashboard has no authentication of its own. +The web dashboard reads and writes your `.env` file, which contains API keys and secrets. It binds to `127.0.0.1` by default — only accessible from your local machine, with no login required. Binding to any non-loopback address (including `0.0.0.0`) engages the [auth gate](#authentication-gated-mode): the server refuses to start until an auth provider (username/password or OAuth) is configured. ::: ## `/reload` Slash Command @@ -573,13 +573,10 @@ Operator-owned dashboards bound to loopback are unaffected — no auth, no login | `hermes dashboard` (default — binds to `127.0.0.1`) | OFF | Local development | | `hermes dashboard --host 0.0.0.0` | **ON** | Remote / production — protect with the username/password provider or OAuth | -The gate is on if and only if: +The gate is on if and only if the bind host is not `127.0.0.1`, `::1`, or `localhost`. Binding to `0.0.0.0` (or any RFC1918 / LAN address) engages the gate. The legacy `--insecure` flag **no longer disables it** — it's accepted for backward compatibility but ignored, with a warning. -1. The bind host is not `127.0.0.1`, `::1`, `localhost`, or `0.0.0.0` AND -2. The `--insecure` flag is **not** set. - -:::danger `--insecure` disables auth entirely -`--insecure` skips the gate and serves an unauthenticated dashboard that reads/writes your `.env` (API keys, secrets) and can run agent commands. **Do not use it for a remote connection.** To expose the dashboard to another machine, configure the [username/password provider](#usernamepassword-provider-no-oauth-idp) (or OAuth) and leave `--insecure` off. The flag exists only as a last-resort escape hatch on a fully trusted, firewalled single-host network. +:::danger `--insecure` is a no-op — it does not disable auth +Since the June 2026 hardening, `--insecure` no longer bypasses dashboard authentication: a non-loopback bind always requires an auth provider (the username/password provider or OAuth). If you want an auth-free dashboard, bind to `127.0.0.1` and reach it over an SSH tunnel or Tailscale. ::: ### Fail-closed semantics @@ -633,19 +630,19 @@ Empty environment values are treated as unset, so a provisioned-but-not-populate If neither source provides a client_id, the plugin reports the specific reason and the dashboard's fail-closed bind error tells you exactly what to fix: ``` -Refusing to bind dashboard to 0.0.0.0 — the OAuth auth gate engages on +Refusing to bind dashboard to 0.0.0.0 — the auth gate engages on non-loopback binds, but no auth providers are registered. Bundled providers reported these issues: • nous: HERMES_DASHBOARD_OAUTH_CLIENT_ID is not set (and - dashboard.oauth.client_id in config.yaml is empty). The Nous Portal - provisions this env var (shape 'agent:{instance_id}') when it - deploys a Hermes Agent instance — set it to your provisioned - client id (either as an env var or under dashboard.oauth.client_id - in config.yaml), or pass --insecure to skip the OAuth gate entirely. + dashboard.oauth.client_id in config.yaml is empty). … -Or pass --insecure to skip the auth gate (NOT recommended on untrusted -networks). +Configure an auth provider before exposing the dashboard: + • Password: set dashboard.basic_auth.username + password_hash in config.yaml + • OAuth: run `hermes dashboard register` (Nous Portal) or install a + DashboardAuthProvider plugin. +There is no unauthenticated public-bind option — to keep it local, bind +127.0.0.1 and tunnel in (SSH / Tailscale). ``` #### Worked example: Nous Research @@ -661,7 +658,7 @@ hermes dashboard register # …writes HERMES_DASHBOARD_OAUTH_CLIENT_ID to ~/.hermes/.env ``` -**2. Run the dashboard on a reachable address.** A non-loopback bind without `--insecure` engages the OAuth gate, and the `client_id` just written activates the `nous` provider: +**2. Run the dashboard on a reachable address.** A non-loopback bind engages the OAuth gate, and the `client_id` just written activates the `nous` provider: ```bash hermes dashboard --host 0.0.0.0 --port 9119 --no-open @@ -681,7 +678,7 @@ curl -s http://<host>:9119/api/status | jq '.auth_required, .auth_providers' If you don't want to wire up an OAuth identity provider — a self-hosted "just put a password on my dashboard" deployment — the bundled `plugins/dashboard_auth/basic` plugin registers a `DashboardAuthProvider` named `basic` that authenticates with a **username and password** instead of an OAuth redirect. -It plugs into the same gate as the OAuth provider: the gate engages on a non-loopback bind without `--insecure`, the login page renders a credential form for this provider (instead of a "Log in with X" button), and everything downstream of login — session cookies, transparent refresh, WS tickets, logout, the audit log — is identical to the OAuth path. Sessions are stateless HMAC-signed tokens the provider mints itself, so there's **no database and no external IDP**. Password hashing uses stdlib `scrypt` (no third-party dependency). +It plugs into the same gate as the OAuth provider: the gate engages on a non-loopback bind, the login page renders a credential form for this provider (instead of a "Log in with X" button), and everything downstream of login — session cookies, transparent refresh, WS tickets, logout, the audit log — is identical to the OAuth path. Sessions are stateless HMAC-signed tokens the provider mints itself, so there's **no database and no external IDP**. Password hashing uses stdlib `scrypt` (no third-party dependency). :::warning Use this on trusted networks only — not the public internet The username/password provider is intended for self-hosted / on-prem / homelab dashboards on a **trusted network**, or reachable only over a **VPN**. It protects a single shared credential with no external identity provider, MFA, or per-user accounts behind it, so it is **not suitable for exposing a dashboard directly to the public internet**. For an internet-facing dashboard, use the [Nous Research provider](#default-provider-nous-research) (or your own [self-hosted OIDC](#self-hosted-oidc-provider) / [custom OAuth](#custom-providers) provider) instead. @@ -689,7 +686,7 @@ The username/password provider is intended for self-hosted / on-prem / homelab d #### Configuration -Like the Nous provider, it reads from `config.yaml` (canonical) with environment variables winning when set non-empty. It activates only when `username` plus either `password_hash` (preferred) or `password` are configured — otherwise it's a no-op, so OAuth users and loopback/`--insecure` operators are unaffected. +Like the Nous provider, it reads from `config.yaml` (canonical) with environment variables winning when set non-empty. It activates only when `username` plus either `password_hash` (preferred) or `password` are configured — otherwise it's a no-op, so OAuth users and loopback operators are unaffected. **`config.yaml`:** @@ -740,7 +737,7 @@ EOF chmod 600 ~/.hermes/.env ``` -**2. Run the dashboard on a reachable address.** A non-loopback bind without `--insecure` engages the gate, and the username + hash activate the `basic` provider: +**2. Run the dashboard on a reachable address.** A non-loopback bind engages the gate, and the username + hash activate the `basic` provider: ```bash hermes dashboard --host 0.0.0.0 --port 9119 --no-open @@ -766,7 +763,7 @@ If you run your own identity provider, the bundled `plugins/dashboard_auth/self_ > **Authentik · Keycloak · Zitadel · Authelia · Auth0 · Okta · Google · …** -Like the Nous provider, it auto-loads and only registers itself once it's configured, so it's a no-op for loopback / `--insecure` dashboards. +Like the Nous provider, it auto-loads and only registers itself once it's configured, so it's a no-op for loopback dashboards. #### Configuration @@ -875,7 +872,7 @@ hermes dashboard --host 0.0.0.0 --port 9119 --no-open `HERMES_DASHBOARD_PUBLIC_URL` tells the dashboard its OAuth callback is `http://localhost:9119/auth/callback` — the redirect URI the realm registered -above. Binding to `0.0.0.0` (a non-loopback bind) without `--insecure` is what +above. Binding to `0.0.0.0` (a non-loopback bind) is what engages the OAuth gate. **3. Log in.** Open `http://localhost:9119/`, you'll be bounced to `/login`. Click **Sign in with Self-Hosted OIDC** → authenticate at Keycloak as `testuser` / `testpassword` → land back on the authenticated dashboard. The sidebar shows `Logged in as Test User via self-hosted`, and `GET /api/auth/me` returns the verified session (`provider: self-hosted`, `email: testuser@example.com`). diff --git a/website/docs/user-guide/features/web-search.md b/website/docs/user-guide/features/web-search.md index c02f78dd958..ca7f529bbcf 100644 --- a/website/docs/user-guide/features/web-search.md +++ b/website/docs/user-guide/features/web-search.md @@ -39,39 +39,19 @@ If you have a paid [Nous Portal](https://portal.nousresearch.com) subscription, ## How `web_extract` handles long pages -Backends return raw page markdown, which can be huge (forum threads, docs sites, news articles with embedded comments). To keep your context window usable and your costs down, `web_extract` runs returned content through the **`web_extract` auxiliary model** before handing it to the agent. Behavior is purely size-driven: +Backends return raw page markdown, which can be huge (forum threads, docs sites, news articles with embedded comments). To keep your context window usable, `web_extract` applies a **deterministic character budget** — no LLM summarization is involved: | Page size (characters) | What happens | |------------------------|--------------| -| Under 5 000 | Returned as-is — no LLM call, full markdown reaches the agent | -| 5 000 – 500 000 | Single-pass summary via the `web_extract` auxiliary model, capped at ~5 000 chars of output | -| 500 000 – 2 000 000 | Chunked: split into 100 k-char chunks, summarize each in parallel, then synthesize a final summary (~5 000 chars) | -| Over 2 000 000 | Refused with a hint to use a more focused source URL | +| At or under the budget (default 15 000) | Returned whole — full markdown reaches the agent | +| Over the budget | Head+tail window (~75% head / ~25% tail, cut on markdown line boundaries) plus an explicit `[TRUNCATED]` footer. The full clean text is stored to disk and the footer tells the agent the file path and the exact `read_file` call to page through the omitted middle | +| Over 2 000 000 | Stored text is capped at 2 MB | -The summary keeps quotes, code blocks, and key facts in their original formatting — it's a content compressor, not a paraphraser. If summarization fails or times out, Hermes falls back to the first ~5 000 chars of raw content rather than a useless error. +The per-page budget is configurable via `web.extract_char_limit` in `config.yaml` (default `15000`, clamped to 2 000–500 000), and the agent can raise it per-call with the tool's `char_limit` argument. -### Which model does the summarizing? +### When truncation gets in the way -The `web_extract` auxiliary task. By default (`auxiliary.web_extract.provider: "auto"`), this is your **main chat model** — same provider, same model as `hermes model`. That's fine for most setups, but on expensive reasoning models (Opus, MiniMax M2.7, etc.) every long-page extract adds meaningful cost. - -To route extraction summaries to a cheap, fast model regardless of your main: - -```yaml -# ~/.hermes/config.yaml -auxiliary: - web_extract: - provider: openrouter - model: google/gemini-3-flash-preview - timeout: 360 # seconds; raise if you hit summarization timeouts -``` - -Or pick interactively: `hermes model` → **Configure auxiliary models** → `web_extract`. - -See [Auxiliary Models](/user-guide/configuration#auxiliary-models) for the full reference and per-task override patterns. - -### When summarization gets in the way - -If you specifically need raw, unsummarized page content — for example, you're scraping a structured page where the LLM summary would drop important fields — use `browser_navigate` + `browser_snapshot` instead. The browser tool returns the live accessibility tree without auxiliary-model rewriting (subject to its own 8 000-char snapshot cap on huge pages). +If you specifically need the live DOM rather than extracted markdown — for example, a JS-heavy page where extraction returns little content — use `browser_navigate` + `browser_snapshot` instead. The browser tool returns the live accessibility tree (subject to its own snapshot cap on huge pages). --- @@ -373,11 +353,13 @@ If no backend is explicitly configured, Hermes picks the first available one bas | Credential present | Auto-selected backend | |--------------------|-----------------------| -| `FIRECRAWL_API_KEY` or `FIRECRAWL_API_URL` | firecrawl | -| `PARALLEL_API_KEY` | parallel | | `TAVILY_API_KEY` | tavily | | `EXA_API_KEY` | exa | +| `PARALLEL_API_KEY` | parallel | +| `FIRECRAWL_API_KEY` or `FIRECRAWL_API_URL` (or the Nous Tool Gateway is ready) | firecrawl | | `SEARXNG_URL` | searxng | +| `BRAVE_SEARCH_API_KEY` | brave-free | +| `ddgs` package importable | ddgs | xAI Web Search is **not** in the auto-detection chain — having `XAI_API_KEY` set (or being signed in via xAI Grok OAuth) does not automatically route web traffic through xAI, since those credentials are also used for inference / TTS / image gen and the user may want a different backend for web. Opt in explicitly with `web.backend: "xai"`. @@ -437,13 +419,9 @@ Some public instances disable certain search engines or categories. Try: Switch to a self-hosted instance (see [Option A](#option-a--self-host-with-docker-recommended) above). With Docker, your own instance has no rate limits. -### `web_extract` returns truncated content with a "summarization timed out" note +### `web_extract` returns truncated content with a `[TRUNCATED]` footer -The auxiliary model didn't finish summarizing within the configured timeout. Either: - -- Raise `auxiliary.web_extract.timeout` in `config.yaml` (default 360s on fresh installs, 30s if the key is missing) -- Switch the `web_extract` auxiliary task to a faster model (e.g. `google/gemini-3-flash-preview`) — see [How `web_extract` handles long pages](#how-web_extract-handles-long-pages) -- For pages where summarization is the wrong tool, use `browser_navigate` instead +That's expected for pages over the character budget. The footer names the on-disk file holding the full clean text and the exact `read_file` call to page through the omitted middle. To see more inline, raise `web.extract_char_limit` in `config.yaml` or pass a larger `char_limit` on the call. --- diff --git a/website/docs/user-guide/messaging/discord.md b/website/docs/user-guide/messaging/discord.md index 80bf88b716e..31dd0863af8 100644 --- a/website/docs/user-guide/messaging/discord.md +++ b/website/docs/user-guide/messaging/discord.md @@ -577,6 +577,19 @@ display: tool_progress_command: true ``` +#### `display.reasoning_style` + +**Type:** string — **Default (Discord):** `"subtext"` — **Values:** `code`, `blockquote`, `subtext` + +Controls how the model's reasoning block is rendered when reasoning display is enabled. Discord defaults to `subtext`, which uses Discord's native `-# ` small grey metadata text so reasoning stays visually secondary to the answer. `blockquote` renders it as a `>` quote, and `code` (the default on other platforms) uses a fenced code block. Long reasoning is collapsed to the first 15 lines. + +```yaml +display: + platforms: + discord: + reasoning_style: subtext # code | blockquote | subtext +``` + ## Slash Command Access Control By default, every allowed user can run every slash command. To split your allowlist into **admins** (full slash command access) and **regular users** (only commands you explicitly enable), add `allow_admin_from` and `user_allowed_commands` to the Discord platform's `extra` block: diff --git a/website/docs/user-guide/messaging/google_chat.md b/website/docs/user-guide/messaging/google_chat.md index 70fe328b19e..881fac6acd2 100644 --- a/website/docs/user-guide/messaging/google_chat.md +++ b/website/docs/user-guide/messaging/google_chat.md @@ -228,6 +228,16 @@ Thread support: when a user replies inside a thread, Hermes detects the `thread.name` and posts its reply in the same thread, so each thread gets a separate Hermes session. +### Clarify questions as interactive cards + +When the agent asks a multiple-choice clarify question, the adapter renders it +as a native **Card v2** with one button per choice plus an +**"Other / type answer"** button, instead of a plain numbered text list. +Clicking a button answers the question directly (`CARD_CLICKED` events route +the choice back into the waiting session). If the card fails to send, or the +question has no fixed choices, the adapter falls back to the standard text +clarify. No configuration needed. + --- ## Step 10: Native attachment delivery (optional) diff --git a/website/docs/user-guide/messaging/index.md b/website/docs/user-guide/messaging/index.md index a9cfcc85ba8..b8a24508aea 100644 --- a/website/docs/user-guide/messaging/index.md +++ b/website/docs/user-guide/messaging/index.md @@ -23,7 +23,8 @@ Bots need both a model provider and tool providers (TTS, web). A [Nous Portal](/ | Slack | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Google Chat | — | ✅ | ✅ | ✅ | — | ✅ | — | | WhatsApp | — | ✅ | ✅ | — | — | ✅ | ✅ | -| Signal | — | ✅ | ✅ | — | — | ✅ | ✅ | +| WhatsApp Cloud API | ✅ | ✅ | ✅ | — | — | ✅ | — | +| Signal | — | ✅ | ✅ | — | — | ✅ | — | | SMS | — | — | — | — | — | — | — | | Email | — | ✅ | ✅ | ✅ | — | — | — | | Home Assistant | — | — | — | — | — | — | — | @@ -33,8 +34,9 @@ Bots need both a model provider and tool providers (TTS, web). A [Nous Portal](/ | Feishu/Lark | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | WeCom | ✅ | ✅ | ✅ | — | — | — | — | | WeCom Callback | — | — | — | — | — | — | — | -| Weixin | ✅ | ✅ | ✅ | — | — | ✅ | ✅ | +| Weixin | ✅ | ✅ | ✅ | — | — | ✅ | — | | BlueBubbles | — | ✅ | ✅ | — | ✅ | ✅ | — | +| Photon (iMessage) | ✅ | ✅ | ✅ | — | ✅ | ✅ | — | | QQ | ✅ | ✅ | ✅ | — | — | ✅ | — | | Yuanbao | ✅ | ✅ | ✅ | — | — | ✅ | ✅ | | Microsoft Teams | — | ✅ | — | ✅ | — | ✅ | — | @@ -43,9 +45,14 @@ Bots need both a model provider and tool providers (TTS, web). A [Nous Portal](/ | Raft | — | — | — | — | — | — | — | | IRC | — | — | — | — | — | — | — | | Buzz | — | ✅ | — | ✅ | — | — | — | +| SimpleX | ✅ | ✅ | ✅ | — | — | ✅ | — | **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. +:::note Hermes Relay +[Hermes Relay](/user-guide/messaging/relay) (experimental) is not a chat platform itself — it is a connector system that fronts platforms like Discord, Telegram, Slack, and WhatsApp through an external connector that owns the platform credentials. Capabilities (media, native approval/clarify prompts, reactions, threads, typing, streaming) are negotiated per connector at handshake rather than fixed in the table above. +::: + ## Architecture ```mermaid @@ -197,6 +204,7 @@ platform network disconnect as an event-loop failure. | `/compress` | Manually compress conversation context | | `/title [name]` | Set or show the session title | | `/resume [name]` | Resume a previously named session | +| `/sessions [all] [search <query>]` | List previous sessions; `search <query>` filters by title or id | | `/usage` | Show token usage for this session (`/usage reset [--force]` redeems a banked Codex limit reset) | | `/insights [days]` | Show usage insights and analytics | | `/reasoning [level\|show\|hide]` | Change reasoning effort or toggle reasoning display | @@ -214,6 +222,14 @@ platform network disconnect as an event-loop failure. Sessions persist across messages until they reset. The agent remembers your conversation context. +### Finding Past Sessions (`/sessions`) + +`/sessions` lists your previous sessions for the current chat, and `/sessions <name>` resumes one (shorthand for `/resume`). When the list grows long, `/sessions search <query>` (alias `find`) filters by title or session-id match, ordered by most recently active. Cross-origin listing with `/sessions all` is admin-only — regular users only ever see sessions from their own chat origin. + +### Persistent `/model` Overrides + +A `/model` switch in a gateway chat applies to that session and now **survives gateway restarts**: the model/provider choice is persisted to the session store and rehydrated on first use after a restart (credentials are re-resolved at load time and never written to disk). `/new` (or `/reset`) clears the override, and `/model <name> --global` writes it through to `config.yaml` instead. `/model <name> --once` applies for a single turn only. + ### Delivery Reliability Final agent responses are recorded in a durable **delivery ledger** @@ -275,6 +291,30 @@ Configure per-platform overrides in `~/.hermes/gateway.json`: } ``` +## Per-Channel Model & System Prompt Overrides + +Different channels can run different models and personas from a **single gateway** — e.g. a cheap fast model in `#daily` and a frontier model with a specialist prompt in `#dev`. Configure `channel_overrides` under the platform in `~/.hermes/gateway-config.yaml`: + +```yaml +platforms: + discord: + enabled: true + channel_overrides: + "123456789012345678": # channel/thread id + model: anthropic/claude-sonnet-4.6 + provider: anthropic + system_prompt: "You are the #dev channel code-review specialist." + "987654321098765432": + model: openai/gpt-5-mini +``` + +Details: + +- All three keys are optional — set only `model`, only `system_prompt`, or any combination. Unset fields fall back to the global defaults. +- Lookup order is exact channel/thread id first, then the **parent** channel/forum id — so Discord threads inherit their parent channel's override automatically. +- Resolution priority for the model is: session `/model` override → `channel_overrides` → global config. A user running `/model` in a chat still wins over the channel default. +- The `system_prompt` override replaces the global gateway prompt for that channel (it is ephemeral — injected per turn, not stored in history). + ## Security **By default, the gateway denies all users who are not in an allowlist or paired via DM.** This is the safe default for a bot with terminal access. @@ -379,13 +419,22 @@ The first time you message a busy agent on any platform, Hermes appends a one-li If you find the busy acknowledgment noisy, set `display.busy_ack_enabled: false`. Input handling is unchanged; only the confirmation message is hidden. +## Clarify Questions (Multi-Select) + +When the agent uses the `clarify` tool to ask you a question, the gateway renders the choices as a numbered prompt (or native buttons on platforms that support them). Clarify supports **multi-select** questions too — the agent can let you pick several options at once: + +- **Messaging platforms** — the prompt says "Multiple selections allowed"; reply with the numbers separated by commas or spaces (e.g. `1, 3`), the option text, or your own free-form answer. +- **Classic CLI / TUI** — multi-select renders as checkboxes: **Space** toggles an option, **Enter** submits the selection. + +Single-select prompts behave as before: pick one option by number, button, or text, or type your own answer via the "Other" path. + ## Tool Progress Notifications Control how much tool activity is displayed in `~/.hermes/config.yaml`: ```yaml display: - tool_progress: all # off | new | all | verbose + tool_progress: all # off | new | all | verbose | log tool_progress_command: false # set to true to enable /verbose in messaging # How progress is grouped on platforms that support message editing: # accumulate (default) — edit one bubble in place as tools run @@ -394,6 +443,26 @@ display: tool_progress_grouping: accumulate # accumulate | separate ``` +### `log` mode — audit file instead of chat messages + +Setting `display.tool_progress: log` sends **no** progress bubbles to chat. Instead, each tool call is appended as a line to `~/.hermes/logs/tool_calls.log` — a rotating audit file (5 MB × 3 backups) run through the same secret-redacting formatter as regular logs, so credentials never land on disk. Use it when you want a full tool-call trail without any chat noise. + +### Configurable status phrases + +Long-running gateway status lines ("still working…"-style heartbeats) draw from a phrase catalog. Built-in defaults ship in `gateway/assets/status_phrases.yaml`; you can add your own with profile-portable files under `HERMES_HOME`: + +- `~/.hermes/status_phrases.yaml` or any `*.yaml` in `~/.hermes/status_phrases/` (conventional paths, auto-loaded), or +- point config at a relative path: + +```yaml +display: + status_phrases: + path: status_phrases/whatsapp.yaml # relative to HERMES_HOME + mode: append # append (default) or replace +``` + +Phrase files map a surface (`status`, `generic`) to a list of strings (max 80 phrases per surface, 160 chars each). Absolute paths and `..` escapes are ignored so config stays profile-portable. Only your configured phrase strings are used — raw tool arguments, commands, and reasoning text are never interpolated into a status phrase. + ### Message timestamps in model context Off by default. When enabled, Hermes prepends a human-readable timestamp @@ -721,10 +790,15 @@ Defaults to `false`. Only platforms whose adapter implements `delete_message` ho - [WeCom Callback Setup](wecom-callback.md) - [Weixin Setup (WeChat)](weixin.md) - [BlueBubbles Setup (iMessage)](bluebubbles.md) +- [Photon Setup (iMessage)](photon.md) - [QQBot Setup](qqbot.md) - [Yuanbao Setup](yuanbao.md) - [Microsoft Teams Setup](teams.md) - [Teams Meetings Pipeline](teams-meetings.md) +- [Microsoft Graph Webhook Listener](msgraph-webhook.md) +- [LINE Setup](line.md) +- [ntfy Setup](ntfy.md) +- [SimpleX Chat Setup](simplex.md) - [Open WebUI + API Server](open-webui.md) - [Raft Setup](raft.md) - [IRC Setup](irc.md) diff --git a/website/docs/user-guide/messaging/matrix.md b/website/docs/user-guide/messaging/matrix.md index 8dc73b1643c..c5406c87b05 100644 --- a/website/docs/user-guide/messaging/matrix.md +++ b/website/docs/user-guide/messaging/matrix.md @@ -418,11 +418,7 @@ In Matrix conversations, Hermes exposes Matrix-specific tools to the agent: - `matrix_set_presence` These tools are scoped to Matrix contexts and are not available in non-Matrix toolsets. Admin-style tools are disabled by default: redaction requires `MATRIX_TOOLS_ALLOW_REDACTION=true`, invites require `MATRIX_TOOLS_ALLOW_INVITES=true`, and room creation requires `MATRIX_TOOLS_ALLOW_ROOM_CREATE=true`. Public room creation also requires `MATRIX_ALLOW_PUBLIC_ROOMS=true`. -Matrix tools are limited to the current Matrix room by default. Explicit -cross-room targets require `MATRIX_TOOLS_ALLOW_CROSS_ROOM=true`; redaction and -invite-like cross-room actions additionally require -`MATRIX_TOOLS_ALLOW_CROSS_ROOM_DESTRUCTIVE=true`. If `MATRIX_ALLOWED_ROOMS` is -set, Matrix tools may only target those rooms. +If `MATRIX_ALLOWED_ROOMS` is set, Matrix tools may only target those rooms. Reaction controls use: @@ -447,24 +443,6 @@ Inbound media must use Matrix `mxc://` content URIs. Hermes rejects arbitrary HTTP(S) media URLs in Matrix events to avoid turning a federated room into an unrestricted downloader. -## Synapse Integration Tests - -Hermes includes an opt-in Synapse harness for local validation: - -```bash -docker compose -f tests/e2e/matrix_synapse_gateway/docker-compose.yml up -d -HERMES_MATRIX_SYNAPSE_INTEGRATION=1 \ - scripts/run_tests.sh -m "integration and matrix_synapse" \ - tests/e2e/matrix_synapse_gateway/test_gateway.py -docker compose -f tests/e2e/matrix_synapse_gateway/docker-compose.yml down -v -``` - -The harness creates temporary users through Synapse shared-secret registration -and covers private-room send/receive, named-room invite/join, media -upload/download, bot response delivery, and startup old-event filtering. E2EE -smoke coverage is separately marked with `matrix_e2ee` so it can stay opt-in on -developer machines. - ### Cross-Signing Verification (Recommended) If your Matrix account has cross-signing enabled (the default in Element), set the recovery key so the bot can self-sign its device on startup. Without this, other Matrix clients may refuse to share encryption sessions with the bot after a device key rotation. diff --git a/website/docs/user-guide/messaging/teams-meetings.md b/website/docs/user-guide/messaging/teams-meetings.md index e0e118cc091..aca258d3721 100644 --- a/website/docs/user-guide/messaging/teams-meetings.md +++ b/website/docs/user-guide/messaging/teams-meetings.md @@ -69,12 +69,13 @@ 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 ``` +The bind host is read from the platform's `extra.host` in `config.yaml` (there is no `MSGRAPH_WEBHOOK_HOST` env var — see the [webhook listener reference](msgraph-webhook.md)). + The listener exposes: - `/msgraph/webhook` for Graph notifications - `/health` for a simple health check diff --git a/website/docs/user-guide/messaging/whatsapp.md b/website/docs/user-guide/messaging/whatsapp.md index a80333d3967..17d89e968d4 100644 --- a/website/docs/user-guide/messaging/whatsapp.md +++ b/website/docs/user-guide/messaging/whatsapp.md @@ -212,6 +212,16 @@ 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. +### Native Polls, Clarify-as-Poll, and Locations + +The Baileys-bridge adapter (bot mode) supports several native WhatsApp message types: + +- **Polls** — the agent can send a native WhatsApp poll (question + options) via the bridge's `/send-poll` endpoint. Poll votes flow back into the conversation. +- **Clarify questions as polls** — when the agent asks a multiple-choice clarify question, it's rendered as a native single-select poll; tapping an option answers the question. If the poll fails to send, the adapter falls back to a plain text question. Approval prompts are **never** mapped onto polls — polls are only used for genuine multiple-choice clarifies. +- **Location pins** — the agent can send a native location pin (latitude/longitude, optional name/address) via `/send-location`, and incoming shared locations (including live locations) are delivered to the agent as location messages. + +All of this works out of the box in bot (Baileys) mode; 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`: diff --git a/website/docs/user-guide/messaging/yuanbao.md b/website/docs/user-guide/messaging/yuanbao.md index 768003ae4c1..a7414f8852f 100644 --- a/website/docs/user-guide/messaging/yuanbao.md +++ b/website/docs/user-guide/messaging/yuanbao.md @@ -98,6 +98,7 @@ The adapter will connect to the Yuanbao WebSocket gateway, authenticate using HM - **Automatic reconnection** — handles WebSocket disconnections with exponential backoff - **Group information queries** — retrieve group details and member lists - **Sticker/Emoji support** — send TIMFaceElem stickers and emoji in conversations +- **WeChat forwarded chat-history support** — when a user forwards a WeChat chat-history bundle into Yuanbao, the adapter decodes the forwarded records (sender nicknames, text, and multimedia entries, including nested forwards) and injects them into the conversation so the agent can read the full forwarded thread - **Auto-sethome** — first user to message the bot is automatically set as the home channel owner - **Slow-response notification** — sends a waiting message when the agent takes longer than expected @@ -244,7 +245,7 @@ When you ask the bot to create or export a file, it sends the file directly to y 1. Check gateway logs for error patterns 2. Increase heartbeat timeout in connection settings 3. Ensure stable network connection to Yuanbao API -4. Consider enabling verbose logging: `HERMES_LOG_LEVEL=debug` +4. Consider enabling verbose logging: `hermes gateway run -vv` ## Access Control @@ -302,7 +303,7 @@ These values are currently not configurable via environment variables. They are Enable debug logging to troubleshoot connection issues: ```bash -HERMES_LOG_LEVEL=debug hermes gateway +hermes gateway run -vv ``` ## Integration with Other Features diff --git a/website/docs/user-guide/secrets/index.md b/website/docs/user-guide/secrets/index.md index 427d50c1ea3..29c7df9e94e 100644 --- a/website/docs/user-guide/secrets/index.md +++ b/website/docs/user-guide/secrets/index.md @@ -30,7 +30,7 @@ Every credential injected by a source is labelled with its origin — setup flow ## Profiles and shared vaults -Two orchestrator-level knobs make one shared vault safe across [profiles](../features/profiles): +Two orchestrator-level knobs make one shared vault safe across [profiles](../profiles): - **`secrets.preserve_existing`** — a list of env var names whose existing `.env` / shell value always wins, even against a source with `override_existing: true`. Use it for per-profile platform secrets (e.g. `FEISHU_APP_SECRET`) that intentionally differ across profiles while everything else rotates centrally: diff --git a/website/docs/user-guide/sessions.md b/website/docs/user-guide/sessions.md index e154209f6b8..44de98ec662 100644 --- a/website/docs/user-guide/sessions.md +++ b/website/docs/user-guide/sessions.md @@ -142,6 +142,25 @@ hermes chat --resume 20250305_091523_a1b2c3d4 Session IDs are shown when you exit a CLI session, and can be found with `hermes sessions list`. +### Resume Restores the Working Directory + +Resuming a CLI session also `cd`s back into the session's recorded working directory (its git repo root or project dir), so the conversation picks up in the workspace it belonged to. If you'd rather stay where you are, pass `--no-restore-cwd`: + +```bash +hermes --resume 20250305_091523_a1b2c3 --no-restore-cwd +``` + +A `↪ restored workspace dir: …` line confirms the switch. Restore failures never break the resume itself. + +### Filtering Sessions by Workspace + +`hermes sessions list` accepts `--workspace <needle>` to show only sessions whose workspace key (git repo root, else cwd) matches — by path substring or exact directory basename: + +```bash +hermes sessions list --workspace my-project +hermes sessions list --workspace ~/code/hermes-agent +``` + ### Conversation Recap on Resume When you resume a session, Hermes displays a compact recap of the previous conversation in a styled panel before the input prompt: diff --git a/website/docs/user-guide/windows-native.md b/website/docs/user-guide/windows-native.md index 2055318127d..91ca5104571 100644 --- a/website/docs/user-guide/windows-native.md +++ b/website/docs/user-guide/windows-native.md @@ -9,7 +9,7 @@ sidebar_position: 3 Hermes runs natively on Windows 10 and Windows 11 — no WSL, no Cygwin, no Docker. This page is the deep dive: what works natively, what's WSL-only, what the installer actually does, and the Windows-specific knobs you might need to touch. -If you just want to install, the one-liner on the [landing page](/) or [Installation page](../getting-started/installation#windows-native-powershell) is all you need. Come back here when something surprises you. +If you just want to install, the one-liner on the [landing page](/) or [Installation page](../getting-started/installation#windows-native) is all you need. Come back here when something surprises you. :::tip Want WSL instead? If you prefer a real POSIX environment (for the dashboard's embedded terminal, `fork` semantics, Linux-style file watchers, etc.), see the **[Windows (WSL2) Guide](./windows-wsl-quickstart.md)**. Both coexist cleanly: native data lives under `%LOCALAPPDATA%\hermes`, WSL data lives under `~/.hermes`. diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/use-mcp-with-hermes.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/use-mcp-with-hermes.md index b2b942541d3..ee5ac3f1d79 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/use-mcp-with-hermes.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/guides/use-mcp-with-hermes.md @@ -109,7 +109,7 @@ mcp_servers: 对于敏感系统,这通常是最佳默认设置。 -## WSL2:将 WSL 中的 Hermes 桥接到 Windows Chrome +## WSL2:将 WSL 中的 Hermes 桥接到 Windows Chrome {#wsl2-bridge-hermes-in-wsl-to-windows-chrome} 以下是适用场景的实际配置: diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/integrations/providers.md b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/integrations/providers.md index b6ea6e8dd88..71a1f5c9b69 100644 --- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/integrations/providers.md +++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/integrations/providers.md @@ -808,7 +808,7 @@ Hermes 会自动以 64K 上下文长度加载 LM Studio 模型。 --- -### WSL2 网络(Windows 用户) +### WSL2 网络(Windows 用户) {#wsl2-networking-windows-users} 由于 Hermes Agent 需要 Unix 环境,Windows 用户在 WSL2 内运行它。如果你的模型服务器(Ollama、LM Studio 等)运行在 **Windows 主机**上,需要桥接网络——WSL2 使用具有独立子网的虚拟网络适配器,因此 WSL2 内的 `localhost` 指向 Linux 虚拟机,**而非** Windows 主机。