Merge branch 'main' of github.com:NousResearch/hermes-agent into bb/gui
# Conflicts: # tui_gateway/server.py
This commit is contained in:
@@ -784,6 +784,7 @@ $ hermes model
|
||||
[ ] title_generation currently: openrouter / google/gemini-3-flash-preview
|
||||
[ ] compression currently: auto / main model
|
||||
[ ] approval currently: auto / main model
|
||||
[ ] triage_specifier currently: auto / main model
|
||||
```
|
||||
|
||||
Select a task, pick a provider (OAuth flows open a browser; API-key providers prompt), pick a model. The change persists to `auxiliary.<task>.*` in `config.yaml`. Same machinery as the main-model picker — no extra syntax to learn.
|
||||
@@ -880,6 +881,18 @@ auxiliary:
|
||||
base_url: ""
|
||||
api_key: ""
|
||||
timeout: 30
|
||||
|
||||
# Kanban triage specifier — `hermes kanban specify <id>` (or the
|
||||
# dashboard's ✨ Specify button on Triage-column cards) uses this
|
||||
# slot to expand a one-liner into a concrete spec and promote the
|
||||
# task to `todo`. Cheap fast models work well here; spec expansion
|
||||
# is short and doesn't need reasoning depth.
|
||||
triage_specifier:
|
||||
provider: "auto"
|
||||
model: ""
|
||||
base_url: ""
|
||||
api_key: ""
|
||||
timeout: 120
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
||||
@@ -271,6 +271,10 @@ The entrypoint script (`docker/entrypoint.sh`) bootstraps the data volume on fir
|
||||
- Optionally launches `hermes dashboard` as a background side-process when `HERMES_DASHBOARD=1` (see [Running the dashboard](#running-the-dashboard))
|
||||
- Then runs `hermes` with whatever arguments you pass
|
||||
|
||||
:::warning
|
||||
Do not override the image entrypoint unless you keep `/opt/hermes/docker/entrypoint.sh` in the command chain. The entrypoint drops root privileges to the `hermes` user before gateway state files are created. Starting `hermes gateway run` as root inside the official image is refused by default because it can leave root-owned files in `/opt/data` and break later dashboard or gateway starts. Set `HERMES_ALLOW_ROOT_GATEWAY=1` only when you intentionally accept that risk.
|
||||
:::
|
||||
|
||||
## Upgrading
|
||||
|
||||
Pull the latest image and recreate the container. Your data directory is untouched.
|
||||
|
||||
@@ -84,8 +84,8 @@ Earlier releases used a one-off `curator.auxiliary.{provider,model}` block. That
|
||||
|
||||
```bash
|
||||
hermes curator status # last run, counts, pinned list, LRU top 5
|
||||
hermes curator run # trigger a review now (background by default)
|
||||
hermes curator run --sync # same, but block until the LLM pass finishes
|
||||
hermes curator run # trigger a review now (blocks until the LLM pass finishes)
|
||||
hermes curator run --background # fire-and-forget: start the LLM pass in a background thread
|
||||
hermes curator run --dry-run # preview only — report without any mutations
|
||||
hermes curator backup # take a manual snapshot of ~/.hermes/skills/
|
||||
hermes curator rollback # restore from the newest snapshot
|
||||
|
||||
@@ -192,6 +192,7 @@ Hermes uses separate lightweight models for side tasks. Each task has its own pr
|
||||
| MCP | MCP helper operations | `auxiliary.mcp` |
|
||||
| Approval | Smart command-approval classification | `auxiliary.approval` |
|
||||
| Title Generation | Session title summaries | `auxiliary.title_generation` |
|
||||
| Triage Specifier | `hermes kanban specify` / dashboard ✨ button — fleshes out a one-liner triage task into a real spec | `auxiliary.triage_specifier` |
|
||||
|
||||
### Auto-Detection Chain
|
||||
|
||||
@@ -384,5 +385,6 @@ See [Scheduled Tasks (Cron)](/docs/user-guide/features/cron) for full configurat
|
||||
| MCP helpers | Auto-detection chain | `auxiliary.mcp` |
|
||||
| Approval classification | Auto-detection chain | `auxiliary.approval` |
|
||||
| Title generation | Auto-detection chain | `auxiliary.title_generation` |
|
||||
| Triage specifier | Auto-detection chain | `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` |
|
||||
|
||||
@@ -387,6 +387,7 @@ def register(ctx):
|
||||
| [`post_approval_response`](#post_approval_response) | User responded to an approval prompt (or it timed out) | ignored |
|
||||
| [`transform_tool_result`](#transform_tool_result) | After any tool returns, before the result is handed back to the model | `str` to replace the result, `None` to leave unchanged |
|
||||
| [`transform_terminal_output`](#transform_terminal_output) | Inside the `terminal` tool, before truncation/ANSI-strip/redact | `str` to replace the raw output, `None` to leave unchanged |
|
||||
| [`transform_llm_output`](#transform_llm_output) | After the tool-calling loop completes, before the final response is delivered | `str` to replace the response text, `None`/empty to leave unchanged |
|
||||
|
||||
---
|
||||
|
||||
@@ -1093,6 +1094,49 @@ Pairs well with `transform_tool_result` (which covers every other tool).
|
||||
|
||||
---
|
||||
|
||||
### `transform_llm_output`
|
||||
|
||||
Fires **once per turn** after the tool-calling loop completes and the model has produced a final response, **before** that response is delivered to the user (CLI, gateway, or programmatic caller). Lets a plugin rewrite the assistant's final text using classical-programming methods — no extra inference tokens burned on SOUL flavor text or a skill-driven transform.
|
||||
|
||||
**Callback signature:**
|
||||
|
||||
```python
|
||||
def my_callback(
|
||||
response_text: str,
|
||||
session_id: str,
|
||||
model: str,
|
||||
platform: str,
|
||||
**kwargs,
|
||||
) -> str | None:
|
||||
```
|
||||
|
||||
| Parameter | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `response_text` | `str` | The assistant's final response text for this turn. |
|
||||
| `session_id` | `str` | Session ID for this conversation (may be empty for one-shot runs). |
|
||||
| `model` | `str` | Model name that produced the response (e.g. `anthropic/claude-sonnet-4.6`). |
|
||||
| `platform` | `str` | Delivery platform (`cli`, `telegram`, `discord`, …; empty when unset). |
|
||||
|
||||
**Return value:** Non-empty `str` to replace the response text, `None` or empty string to leave it unchanged. **First non-empty string wins** when multiple plugins register — mirroring `transform_tool_result`.
|
||||
|
||||
**Use cases:** Apply a personality/vocabulary transform (pirate-speak, Spongebob), redact user-specific identifiers from the final text, append a project-specific signature footer, enforce a house style guide without burning tokens on SOUL instructions.
|
||||
|
||||
```python
|
||||
import os, re
|
||||
|
||||
def spongebob(response_text, **kwargs):
|
||||
if os.environ.get("SPONGEBOB_MODE") != "on":
|
||||
return None # pass through unchanged
|
||||
return re.sub(r"!", "!! Tartar sauce!", response_text)
|
||||
|
||||
def register(ctx):
|
||||
ctx.register_hook("transform_llm_output", spongebob)
|
||||
```
|
||||
|
||||
The hook is guarded on a non-empty, non-interrupted response — it will not fire on stop-button interrupts or empty turns. Exceptions are logged as warnings and do not break agent execution.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -22,7 +22,7 @@ Throughout the tutorial, **code blocks labelled `bash` are commands *you* run.**
|
||||
|
||||
Six columns, left to right:
|
||||
|
||||
- **Triage** — raw ideas, a specifier will flesh out the spec before anyone works on them.
|
||||
- **Triage** — raw ideas, a specifier will flesh out the spec before anyone works on them. Click the **✨ Specify** button on any triage card (or run `hermes kanban specify <id>` / `/kanban specify <id>` from a chat) to have the auxiliary LLM turn a one-liner into a full spec (goal, approach, acceptance criteria) and promote it to `todo` in one shot. Configure which model runs it under `auxiliary.triage_specifier` in `config.yaml`.
|
||||
- **Todo** — created but waiting on dependencies, or not yet assigned.
|
||||
- **Ready** — assigned and waiting for the dispatcher to claim.
|
||||
- **In progress** — a worker is actively running the task. With "Lanes by profile" on (the default), this column sub-groups by assignee so you can see at a glance what each worker is doing.
|
||||
|
||||
@@ -335,10 +335,19 @@ Any profile that should be able to work kanban tasks must load the `kanban-worke
|
||||
3. Call `kanban_heartbeat(note="...")` every few minutes during long operations.
|
||||
4. Complete with `kanban_complete(summary="...", metadata={...})`, or `kanban_block(reason="...")` if stuck.
|
||||
|
||||
Load it with (this one is **you**, installing into a profile — not a tool call):
|
||||
`kanban-worker` is a bundled skill, synced into every profile during install and
|
||||
update — there is no separate Skills Hub install step. Verify it is present in
|
||||
whichever profile you use for kanban workers (`researcher`, `writer`, `ops`,
|
||||
etc.):
|
||||
|
||||
```bash
|
||||
hermes skills install devops/kanban-worker
|
||||
hermes -p <your-worker-profile> skills list | grep kanban-worker
|
||||
```
|
||||
|
||||
If the bundled copy is missing, restore it for that profile:
|
||||
|
||||
```bash
|
||||
hermes -p <your-worker-profile> skills reset kanban-worker --restore
|
||||
```
|
||||
|
||||
The dispatcher also auto-passes `--skills kanban-worker` when spawning every worker, so the worker always has the pattern library available even if a profile's default skills config doesn't include it.
|
||||
@@ -403,10 +412,18 @@ kanban_complete(
|
||||
)
|
||||
```
|
||||
|
||||
Load it into your orchestrator profile:
|
||||
`kanban-orchestrator` is a bundled skill. It is synced into each profile during
|
||||
install and update, so there is no separate Skills Hub install step. Verify it is
|
||||
present in your orchestrator profile:
|
||||
|
||||
```bash
|
||||
hermes skills install devops/kanban-orchestrator
|
||||
hermes -p orchestrator skills list | grep kanban-orchestrator
|
||||
```
|
||||
|
||||
If the bundled copy is missing, restore it for that profile:
|
||||
|
||||
```bash
|
||||
hermes -p orchestrator skills reset kanban-orchestrator --restore
|
||||
```
|
||||
|
||||
For best results, pair it with a profile whose toolsets are restricted to board operations (`kanban`, `gateway`, `memory`) so the orchestrator literally cannot execute implementation tasks even if it tries.
|
||||
@@ -425,7 +442,7 @@ hermes dashboard # "Kanban" tab appears in the nav, after "Skills"
|
||||
### What the plugin gives you
|
||||
|
||||
- A **Kanban** tab showing one column per status: `triage`, `todo`, `ready`, `running`, `blocked`, `done` (plus `archived` when the toggle is on).
|
||||
- `triage` is the parking column for rough ideas a specifier is expected to flesh out. Tasks created with `hermes kanban create --triage` (or via the Triage column's inline create) land here and the dispatcher leaves them alone until a human or specifier promotes them to `todo` / `ready`.
|
||||
- `triage` is the parking column for rough ideas a specifier is expected to flesh out. Tasks created with `hermes kanban create --triage` (or via the Triage column's inline create) land here and the dispatcher leaves them alone until a human or specifier promotes them to `todo` / `ready`. Run `hermes kanban specify <id>` to have the auxiliary LLM expand a triage task into a concrete spec (title + body with goal, approach, acceptance criteria) and promote it to `todo` in one shot; `--all` sweeps every triage task at once. Configure which model runs the specifier under `auxiliary.triage_specifier` in `config.yaml`.
|
||||
- Cards show the task id, title, priority badge, tenant tag, assigned profile, comment/link counts, a **progress pill** (`N/M` children done when the task has dependents), and "created N ago". A per-card checkbox enables multi-select.
|
||||
- **Per-profile lanes inside Running** — toolbar checkbox toggles sub-grouping of the Running column by assignee.
|
||||
- **Live updates via WebSocket** — the plugin tails the append-only `task_events` table on a short poll interval; the board reflects changes the instant any profile (CLI, gateway, or another dashboard tab) acts. Reloads are debounced so a burst of events triggers a single refetch.
|
||||
@@ -437,7 +454,7 @@ hermes dashboard # "Kanban" tab appears in the nav, after "Skills"
|
||||
- **Editable assignee / priority** — click the meta row to rewrite.
|
||||
- **Editable description** — markdown-rendered by default (headings, bold, italic, inline code, fenced code, `http(s)` / `mailto:` links, bullet lists), with an "edit" button that swaps in a textarea. Markdown rendering is a tiny, XSS-safe renderer — every substitution runs on HTML-escaped input, only `http(s)` / `mailto:` links pass through, and `target="_blank"` + `rel="noopener noreferrer"` are always set.
|
||||
- **Dependency editor** — chip list of parents and children, each with an `×` to unlink, plus dropdowns over every other task to add a new parent or child. Cycle attempts are rejected server-side with a clear message.
|
||||
- **Status action row** (→ triage / → ready / → running / block / unblock / complete / archive) with confirm prompts for destructive transitions.
|
||||
- **Status action row** (→ triage / → ready / → running / block / unblock / complete / archive) with confirm prompts for destructive transitions. For cards in the **Triage** column the row also exposes a **✨ Specify** button that calls the auxiliary LLM (`auxiliary.triage_specifier` in `config.yaml`) to expand the one-liner into a concrete spec (title + body with goal, approach, acceptance criteria) and promote the task to `todo`. The same behaviour is reachable from the CLI (`hermes kanban specify <id>` / `--all`), from any gateway platform (`/kanban specify <id>`), and programmatically via `POST /api/plugins/kanban/tasks/:id/specify`.
|
||||
- Result section (also markdown-rendered), comment thread with Enter-to-submit, the last 20 events.
|
||||
- **Toolbar filters** — free-text search, tenant dropdown (defaults to `dashboard.kanban.default_tenant` from `config.yaml`), assignee dropdown, "show archived" toggle, "lanes by profile" toggle, and a **Nudge dispatcher** button so you don't have to wait for the next 60 s tick.
|
||||
|
||||
@@ -479,6 +496,7 @@ All routes are mounted under `/api/plugins/kanban/` and protected by the dashboa
|
||||
| `PATCH` | `/tasks/:id` | Status / assignee / priority / title / body / result |
|
||||
| `POST` | `/tasks/bulk` | Apply the same patch (status / archive / assignee / priority) to every id in `ids`. Per-id failures reported without aborting siblings |
|
||||
| `POST` | `/tasks/:id/comments` | Append a comment |
|
||||
| `POST` | `/tasks/:id/specify` | Run the triage specifier — auxiliary LLM fleshes out the task body and promotes it from `triage` to `todo`. Returns `{ok, task_id, reason, new_title}`; `ok=false` with a human-readable reason on "not in triage" / no aux client / LLM error is a 200, not a 4xx |
|
||||
| `POST` | `/links` | Add a dependency (`parent_id` → `child_id`) |
|
||||
| `DELETE` | `/links?parent_id=…&child_id=…` | Remove a dependency |
|
||||
| `POST` | `/dispatch?max=…&dry_run=…` | Nudge the dispatcher — skip the 60 s wait |
|
||||
@@ -571,6 +589,8 @@ hermes kanban notify-list [<id>] [--json]
|
||||
hermes kanban notify-unsubscribe <id>
|
||||
--platform <name> --chat-id <id> [--thread-id <id>]
|
||||
hermes kanban context <id> # what a worker sees
|
||||
hermes kanban specify [<id> | --all] [--tenant T] # flesh out a triage-column idea
|
||||
[--author NAME] [--json] # into a full spec and promote to todo
|
||||
hermes kanban gc [--event-retention-days N] # workspaces + old events + old logs
|
||||
[--log-retention-days N]
|
||||
```
|
||||
@@ -588,6 +608,8 @@ Every `hermes kanban <action>` verb is also reachable as `/kanban <action>` —
|
||||
/kanban comment t_abcd "looks good, ship it"
|
||||
/kanban unblock t_abcd
|
||||
/kanban dispatch --max 3
|
||||
/kanban specify t_abcd # flesh out a triage one-liner into a real spec
|
||||
/kanban specify --all --tenant engineering # sweep every triage task in one tenant
|
||||
```
|
||||
|
||||
Quote multi-word arguments the same way you would on a shell — `run_slash` parses the rest of the line with `shlex.split`, so `"..."` and `'...'` both work.
|
||||
@@ -641,7 +663,7 @@ The board supports these eight patterns without any new primitives:
|
||||
| **P6 `@mention`** | inline routing from prose | `@reviewer look at this` |
|
||||
| **P7 Thread-scoped workspace** | `/kanban here` in a thread | per-project gateway threads |
|
||||
| **P8 Fleet farming** | one profile, N subjects | 50 social accounts |
|
||||
| **P9 Triage specifier** | rough idea → `triage` → specifier expands body → `todo` | "turn this one-liner into a spec' task" |
|
||||
| **P9 Triage specifier** | rough idea → `triage` → `hermes kanban specify` expands body → `todo` | "turn this one-liner into a spec'd task" |
|
||||
|
||||
For worked examples of each, see `docs/hermes-kanban-v1-spec.pdf`.
|
||||
|
||||
|
||||
@@ -67,6 +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` |
|
||||
| `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` |
|
||||
@@ -139,6 +140,7 @@ colors:
|
||||
session_border: "#8B8682"
|
||||
status_bar_bg: "#1a1a2e"
|
||||
voice_status_bg: "#1a1a2e"
|
||||
selection_bg: "#333355"
|
||||
completion_menu_bg: "#1a1a2e"
|
||||
completion_menu_current_bg: "#333355"
|
||||
completion_menu_meta_bg: "#1a1a2e"
|
||||
|
||||
@@ -1,80 +1,116 @@
|
||||
---
|
||||
title: "Nous Tool Gateway"
|
||||
description: "Route web search, image generation, text-to-speech, and browser automation through your Nous subscription — no extra API keys needed"
|
||||
description: "One subscription, every tool. Web search, image generation, TTS, and cloud browsers — all routed through Nous Portal with no extra API keys."
|
||||
sidebar_label: "Tool Gateway"
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Nous Tool Gateway
|
||||
|
||||
:::tip Get Started
|
||||
The Tool Gateway is included with paid Nous Portal subscriptions. **[Manage your subscription →](https://portal.nousresearch.com/manage-subscription)**
|
||||
:::
|
||||
**One subscription. Every tool built in.**
|
||||
|
||||
The **Tool Gateway** lets paid [Nous Portal](https://portal.nousresearch.com) subscribers use web search, image generation, text-to-speech, and browser automation through their existing subscription — no need to sign up for separate API keys from Firecrawl, FAL, OpenAI, or Browser Use.
|
||||
The Tool Gateway is included with every paid [Nous Portal](https://portal.nousresearch.com) subscription. It routes Hermes' tool calls — web search, image generation, text-to-speech, and cloud browser automation — through infrastructure Nous already runs, so you don't have to sign up with Firecrawl, FAL, OpenAI, Browser Use, or anyone else just to make your agent useful.
|
||||
|
||||
## What's Included
|
||||
<div style={{display: 'flex', gap: '1rem', flexWrap: 'wrap', margin: '1.5rem 0'}}>
|
||||
<a href="https://portal.nousresearch.com/manage-subscription" style={{background: 'var(--ifm-color-primary)', color: 'white', padding: '0.75rem 1.5rem', borderRadius: '6px', textDecoration: 'none', fontWeight: 'bold'}}>Start or manage subscription →</a>
|
||||
</div>
|
||||
|
||||
| Tool | What It Does | Direct Alternative |
|
||||
|------|--------------|--------------------|
|
||||
| **Web search & extract** | Search the web and extract page content via Firecrawl | `FIRECRAWL_API_KEY`, `EXA_API_KEY`, `PARALLEL_API_KEY`, `TAVILY_API_KEY` |
|
||||
| **Image generation** | Generate images via FAL (9 models: FLUX 2 Klein/Pro, GPT-Image 1.5/2, Nano Banana Pro, Ideogram V3, Recraft V4 Pro, Qwen, Z-Image Turbo) | `FAL_KEY` |
|
||||
| **Text-to-speech** | Convert text to speech via OpenAI TTS | `VOICE_TOOLS_OPENAI_KEY`, `ELEVENLABS_API_KEY` |
|
||||
| **Browser automation** | Control cloud browsers via Browser Use | `BROWSER_USE_API_KEY`, `BROWSERBASE_API_KEY` |
|
||||
## What's included
|
||||
|
||||
All four tools bill to your Nous subscription. You can enable any combination — for example, use the gateway for web and image generation while keeping your own ElevenLabs key for TTS.
|
||||
| | Tool | What you get |
|
||||
|---|---|---|
|
||||
| 🔍 | **Web search & extract** | Agent-grade web search and full-page extraction via Firecrawl. No rate limits to worry about — the gateway handles scaling. |
|
||||
| 🎨 | **Image generation** | Nine models under one endpoint: **FLUX 2 Klein 9B**, **FLUX 2 Pro**, **Z-Image Turbo**, **Nano Banana Pro** (Gemini 3 Pro Image), **GPT Image 1.5**, **GPT Image 2**, **Ideogram V3**, **Recraft V4 Pro**, **Qwen Image**. Pick per-generation with a flag, or let Hermes default to FLUX 2 Klein. |
|
||||
| 🔊 | **Text-to-speech** | OpenAI TTS voices wired into the `text_to_speech` tool. Drop voice notes into Telegram, generate audio for pipelines, narrate anything. |
|
||||
| 🌐 | **Cloud browser automation** | Headless Chromium sessions via Browser Use. `browser_navigate`, `browser_click`, `browser_type`, `browser_vision` — all the agent-driving primitives, no Browserbase account required. |
|
||||
|
||||
## Eligibility
|
||||
All four are pay-as-you-use billed against your Nous subscription. Use any combination — run the gateway for web and images while keeping your own ElevenLabs key for TTS, or route everything through Nous.
|
||||
|
||||
The Tool Gateway is available to **paid** [Nous Portal](https://portal.nousresearch.com/manage-subscription) subscribers. Free-tier accounts do not have access — [upgrade your subscription](https://portal.nousresearch.com/manage-subscription) to unlock it.
|
||||
## Why it's here
|
||||
|
||||
To check your status:
|
||||
Building an agent that can actually *do things* means stitching together 5+ API subscriptions — each with their own signup, rate limits, billing, and quirks. The gateway collapses that into one account:
|
||||
|
||||
- **One bill.** Pay Nous; we handle the rest.
|
||||
- **One signup.** No Firecrawl, FAL, Browser Use, or OpenAI audio accounts to manage.
|
||||
- **One key.** Your Nous Portal OAuth covers every tool.
|
||||
- **Same quality.** Same backends the direct-key route uses — just fronted by us.
|
||||
|
||||
Bring your own keys anytime — per-tool, whenever you want to. The gateway isn't a lock-in, it's a shortcut.
|
||||
|
||||
## Get started
|
||||
|
||||
```bash
|
||||
hermes model # Pick Nous Portal as your provider
|
||||
```
|
||||
|
||||
When you select Nous Portal, Hermes offers to turn on the Tool Gateway. Accept, and you're done — every supported tool is live on the next run.
|
||||
|
||||
Check what's active at any time:
|
||||
|
||||
```bash
|
||||
hermes status
|
||||
```
|
||||
|
||||
Look for the **Nous Tool Gateway** section. It shows which tools are active via the gateway, which use direct keys, and which aren't configured.
|
||||
|
||||
## Enabling the Tool Gateway
|
||||
|
||||
### During model setup
|
||||
|
||||
When you run `hermes model` and select Nous Portal as your provider, Hermes automatically offers to enable the Tool Gateway:
|
||||
You'll see a section like:
|
||||
|
||||
```
|
||||
Your Nous subscription includes the Tool Gateway.
|
||||
|
||||
The Tool Gateway gives you access to web search, image generation,
|
||||
text-to-speech, and browser automation through your Nous subscription.
|
||||
No need to sign up for separate API keys — just pick the tools you want.
|
||||
|
||||
○ Web search & extract (Firecrawl) — not configured
|
||||
○ Image generation (FAL) — not configured
|
||||
○ Text-to-speech (OpenAI TTS) — not configured
|
||||
○ Browser automation (Browser Use) — not configured
|
||||
|
||||
● Enable Tool Gateway
|
||||
○ Skip
|
||||
◆ Nous Tool Gateway
|
||||
Nous Portal ✓ managed tools available
|
||||
Web tools ✓ active via Nous subscription
|
||||
Image gen ✓ active via Nous subscription
|
||||
TTS ✓ active via Nous subscription
|
||||
Browser ○ active via Browser Use key
|
||||
```
|
||||
|
||||
Select **Enable Tool Gateway** and you're done.
|
||||
Tools marked "active via Nous subscription" are going through the gateway. Anything else is using your own keys.
|
||||
|
||||
If you already have direct API keys for some tools, the prompt adapts — you can enable the gateway for all tools (your existing keys are kept in `.env` but not used at runtime), enable only for unconfigured tools, or skip entirely.
|
||||
## Eligibility
|
||||
|
||||
### Via `hermes tools`
|
||||
The Tool Gateway is a **paid-subscription** feature. Free-tier Nous accounts can use Portal for inference but don't include managed tools — [upgrade your plan](https://portal.nousresearch.com/manage-subscription) to unlock the gateway.
|
||||
|
||||
You can also enable the gateway tool-by-tool through the interactive tool configuration:
|
||||
## Mix and match
|
||||
|
||||
The gateway is per-tool. Turn it on for just what you want:
|
||||
|
||||
- **All tools through Nous** — easiest; one subscription, done.
|
||||
- **Gateway for web + images, bring your own TTS** — keep your ElevenLabs voice, let Nous handle the rest.
|
||||
- **Gateway only for things you don't have keys for** — "I already pay for Browserbase, but I don't want a Firecrawl account" works fine.
|
||||
|
||||
Switch any tool at any time via:
|
||||
|
||||
```bash
|
||||
hermes tools
|
||||
hermes tools # Interactive picker for each tool category
|
||||
```
|
||||
|
||||
Select a tool category (Web, Browser, Image Generation, or TTS), then choose **Nous Subscription** as the provider. This sets `use_gateway: true` for that tool in your config.
|
||||
Select the tool, pick **Nous Subscription** as the provider (or any direct provider you prefer). No config editing required.
|
||||
|
||||
### Manual configuration
|
||||
## Using individual image models
|
||||
|
||||
Set the `use_gateway` flag directly in `~/.hermes/config.yaml`:
|
||||
Image generation defaults to FLUX 2 Klein 9B for speed. Override per-call by passing the model ID to the `image_generate` tool:
|
||||
|
||||
| Model | ID | Best for |
|
||||
|---|---|---|
|
||||
| FLUX 2 Klein 9B | `fal-ai/flux-2/klein/9b` | Fast, good default |
|
||||
| FLUX 2 Pro | `fal-ai/flux-2/pro` | Higher fidelity FLUX |
|
||||
| Z-Image Turbo | `fal-ai/z-image/turbo` | Stylized, fast |
|
||||
| Nano Banana Pro | `fal-ai/gemini-3-pro-image` | Google Gemini 3 Pro Image |
|
||||
| GPT Image 1.5 | `fal-ai/gpt-image-1/5` | OpenAI image gen, text+image |
|
||||
| GPT Image 2 | `fal-ai/gpt-image-2` | OpenAI latest |
|
||||
| Ideogram V3 | `fal-ai/ideogram/v3` | Strong prompt adherence + typography |
|
||||
| Recraft V4 Pro | `fal-ai/recraft/v4/pro` | Vector-style, graphic design |
|
||||
| Qwen Image | `fal-ai/qwen-image` | Alibaba multimodal |
|
||||
|
||||
The set evolves — `hermes tools` → Image Generation shows the current live list.
|
||||
|
||||
---
|
||||
|
||||
## Configuration reference
|
||||
|
||||
Most users never need to touch this — `hermes model` and `hermes tools` cover every workflow interactively. This section is for writing config.yaml directly or scripting setups.
|
||||
|
||||
### Per-tool `use_gateway` flag
|
||||
|
||||
Each tool's config block takes a `use_gateway` boolean:
|
||||
|
||||
```yaml
|
||||
web:
|
||||
@@ -93,95 +129,48 @@ browser:
|
||||
use_gateway: true
|
||||
```
|
||||
|
||||
## How It Works
|
||||
Precedence: `use_gateway: true` routes through Nous regardless of any direct keys in `.env`. `use_gateway: false` (or absent) uses direct keys if available and only falls back to the gateway when none exist.
|
||||
|
||||
When `use_gateway: true` is set for a tool, the runtime routes API calls through the Nous Tool Gateway instead of using direct API keys:
|
||||
|
||||
1. **Web tools** — `web_search` and `web_extract` use the gateway's Firecrawl endpoint
|
||||
2. **Image generation** — `image_generate` uses the gateway's FAL endpoint
|
||||
3. **TTS** — `text_to_speech` uses the gateway's OpenAI Audio endpoint
|
||||
4. **Browser** — `browser_navigate` and other browser tools use the gateway's Browser Use endpoint
|
||||
|
||||
The gateway authenticates using your Nous Portal credentials (stored in `~/.hermes/auth.json` after `hermes model`).
|
||||
|
||||
### Precedence
|
||||
|
||||
Each tool checks `use_gateway` first:
|
||||
|
||||
- **`use_gateway: true`** → route through the gateway, even if direct API keys exist in `.env`
|
||||
- **`use_gateway: false`** (or absent) → use direct API keys if available, fall back to gateway only when no direct keys exist
|
||||
|
||||
This means you can switch between gateway and direct keys at any time without deleting your `.env` credentials.
|
||||
|
||||
## Switching Back to Direct Keys
|
||||
|
||||
To stop using the gateway for a specific tool:
|
||||
|
||||
```bash
|
||||
hermes tools # Select the tool → choose a direct provider
|
||||
```
|
||||
|
||||
Or set `use_gateway: false` in config:
|
||||
### Disabling the gateway
|
||||
|
||||
```yaml
|
||||
web:
|
||||
backend: firecrawl
|
||||
use_gateway: false # Now uses FIRECRAWL_API_KEY from .env
|
||||
use_gateway: false # Hermes now uses FIRECRAWL_API_KEY from .env
|
||||
```
|
||||
|
||||
When you select a non-gateway provider in `hermes tools`, the `use_gateway` flag is automatically set to `false` to prevent contradictory config.
|
||||
`hermes tools` automatically clears the flag when you pick a non-gateway provider, so this usually happens for you.
|
||||
|
||||
## Checking Status
|
||||
### Self-hosted gateway (advanced)
|
||||
|
||||
Running your own Nous-compatible gateway? Override endpoints in `~/.hermes/.env`:
|
||||
|
||||
```bash
|
||||
hermes status
|
||||
TOOL_GATEWAY_DOMAIN=your-domain.example.com
|
||||
TOOL_GATEWAY_SCHEME=https
|
||||
TOOL_GATEWAY_USER_TOKEN=your-token # normally auto-populated from Portal login
|
||||
FIRECRAWL_GATEWAY_URL=https://... # override one endpoint specifically
|
||||
```
|
||||
|
||||
The **Nous Tool Gateway** section shows:
|
||||
|
||||
```
|
||||
◆ Nous Tool Gateway
|
||||
Nous Portal ✓ managed tools available
|
||||
Web tools ✓ active via Nous subscription
|
||||
Image gen ✓ active via Nous subscription
|
||||
TTS ✓ active via Nous subscription
|
||||
Browser ○ active via Browser Use key
|
||||
Modal ○ available via subscription (optional)
|
||||
```
|
||||
|
||||
Tools marked "active via Nous subscription" are routed through the gateway. Tools with their own keys show which provider is active.
|
||||
|
||||
## Advanced: Self-Hosted Gateway
|
||||
|
||||
For self-hosted or custom gateway deployments, you can override the gateway endpoints via environment variables in `~/.hermes/.env`:
|
||||
|
||||
```bash
|
||||
TOOL_GATEWAY_DOMAIN=nousresearch.com # Base domain for gateway routing
|
||||
TOOL_GATEWAY_SCHEME=https # HTTP or HTTPS (default: https)
|
||||
TOOL_GATEWAY_USER_TOKEN=your-token # Auth token (normally auto-populated)
|
||||
FIRECRAWL_GATEWAY_URL=https://... # Override for the Firecrawl endpoint specifically
|
||||
```
|
||||
|
||||
These env vars are always visible in the configuration regardless of subscription status — they're useful for custom infrastructure setups.
|
||||
These knobs exist for custom infrastructure setups (enterprise deployments, dev environments). Regular subscribers never set them.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Do I need to delete my existing API keys?
|
||||
### Does it work with Telegram / Discord / the other messaging gateways?
|
||||
|
||||
No. When `use_gateway: true` is set, the runtime skips direct API keys and routes through the gateway. Your keys stay in `.env` untouched. If you later disable the gateway, they'll be used again automatically.
|
||||
Yes. Tool Gateway operates at the tool-execution layer, not the CLI. Every interface that can call a tool — CLI, Telegram, Discord, Slack, IRC, Teams, the API server, anything — benefits from it transparently.
|
||||
|
||||
### Can I use the gateway for some tools and direct keys for others?
|
||||
### What happens if my subscription expires?
|
||||
|
||||
Yes. The `use_gateway` flag is per-tool. You can mix and match — for example, gateway for web and image generation, your own ElevenLabs key for TTS, and Browserbase for browser automation.
|
||||
Tools routed through the gateway stop working until you renew or swap in direct API keys via `hermes tools`. Hermes shows a clear error pointing at the portal.
|
||||
|
||||
### What if my subscription expires?
|
||||
### Can I see usage or costs per tool?
|
||||
|
||||
Tools that were routed through the gateway will stop working until you [renew your subscription](https://portal.nousresearch.com/manage-subscription) or switch to direct API keys via `hermes tools`.
|
||||
Yes — the [Nous Portal dashboard](https://portal.nousresearch.com) breaks usage down by tool so you can see what's driving your bill.
|
||||
|
||||
### Does the gateway work with the messaging gateway?
|
||||
### Is Modal (serverless terminal) included?
|
||||
|
||||
Yes. The Tool Gateway routes tool API calls regardless of whether you're using the CLI, Telegram, Discord, or any other messaging platform. It operates at the tool runtime level, not the entry point level.
|
||||
Modal is available as an **optional add-on** through the Nous subscription, not part of the default Tool Gateway bundle. Configure it via `hermes setup terminal` or directly in `config.yaml` when you want a remote sandbox for shell execution.
|
||||
|
||||
### Is Modal included?
|
||||
### Do I need to delete my existing API keys when I enable the gateway?
|
||||
|
||||
Modal (serverless terminal backend) is available as an optional add-on through the Nous subscription. It's not enabled by the Tool Gateway prompt — configure it separately via `hermes setup terminal` or in `config.yaml`.
|
||||
No — keep them in `.env`. When `use_gateway: true`, Hermes skips direct keys and uses the gateway. Flip the flag back to `false` and your keys become the source again. The gateway isn't a lock-in.
|
||||
|
||||
@@ -148,8 +148,15 @@ You should see something like `10 results`. If you get a `403 Forbidden`, JSON f
|
||||
**7. Configure Hermes:**
|
||||
|
||||
```bash
|
||||
# ~/.hermes/config.yaml
|
||||
SEARXNG_URL: http://localhost:8888
|
||||
# ~/.hermes/.env
|
||||
SEARXNG_URL=http://localhost:8888
|
||||
```
|
||||
|
||||
Then select SearXNG as the search backend in `~/.hermes/config.yaml`:
|
||||
|
||||
```yaml
|
||||
web:
|
||||
search_backend: "searxng"
|
||||
```
|
||||
|
||||
Or set via `hermes tools` → Web Search & Extract → SearXNG.
|
||||
@@ -161,8 +168,8 @@ Or set via `hermes tools` → Web Search & Extract → SearXNG.
|
||||
Public SearXNG instances are listed at [searx.space](https://searx.space/). Filter by instances that have **JSON format enabled** (shown in the table).
|
||||
|
||||
```bash
|
||||
# ~/.hermes/config.yaml
|
||||
SEARXNG_URL: https://searx.example.com
|
||||
# ~/.hermes/.env
|
||||
SEARXNG_URL=https://searx.example.com
|
||||
```
|
||||
|
||||
:::caution Public instances
|
||||
|
||||
@@ -0,0 +1,370 @@
|
||||
---
|
||||
sidebar_position: 12
|
||||
title: "Google Chat"
|
||||
description: "Set up Hermes Agent as a Google Chat bot using Cloud Pub/Sub"
|
||||
---
|
||||
|
||||
# Google Chat Setup
|
||||
|
||||
Connect Hermes Agent to Google Chat as a bot. The integration uses Cloud Pub/Sub
|
||||
pull subscriptions for inbound events and the Chat REST API for outbound messages.
|
||||
Equivalent ergonomics to Slack Socket Mode or Telegram long-polling: your Hermes
|
||||
process does not need a public URL, a tunnel, or a TLS certificate. It connects,
|
||||
authenticates, and listens on a subscription — the same way a Telegram bot listens
|
||||
on a token.
|
||||
|
||||
:::note Workspace edition
|
||||
Google Chat is part of Google Workspace. You can use this integration with a
|
||||
personal Workspace (`@yourdomain.com` registered through Google) or a work
|
||||
Workspace where you have the Admin rights to publish an app. Gmail-only accounts
|
||||
cannot host Chat apps.
|
||||
:::
|
||||
|
||||
## Overview
|
||||
|
||||
| Component | Value |
|
||||
|-----------|-------|
|
||||
| **Libraries** | `google-cloud-pubsub`, `google-api-python-client`, `google-auth` |
|
||||
| **Inbound transport** | Cloud Pub/Sub pull subscription (no public endpoint) |
|
||||
| **Outbound transport** | Chat REST API (`chat.googleapis.com`) |
|
||||
| **Authentication** | Service Account JSON with `roles/pubsub.subscriber` on the subscription |
|
||||
| **User identification** | Chat resource names (`users/{id}`) + email |
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Create or pick a GCP project
|
||||
|
||||
You need a Google Cloud project to host the Pub/Sub topic. If you don't have one,
|
||||
create it at [console.cloud.google.com](https://console.cloud.google.com) —
|
||||
personal accounts get a free tier that easily covers bot traffic.
|
||||
|
||||
Note the project ID (e.g., `my-chat-bot-123`). You'll use it in every subsequent
|
||||
step.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Enable two APIs
|
||||
|
||||
In the console, go to **APIs & Services → Library** and enable:
|
||||
|
||||
- **Google Chat API**
|
||||
- **Cloud Pub/Sub API**
|
||||
|
||||
Both are free for the volumes a personal bot generates.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Create a Service Account
|
||||
|
||||
**IAM & Admin → Service Accounts → Create Service Account.**
|
||||
|
||||
- Name: `hermes-chat-bot`
|
||||
- Skip the "Grant this service account access to project" step. IAM on the specific
|
||||
subscription is all you need — do **NOT** grant project-level Pub/Sub roles.
|
||||
|
||||
After creation, open the SA, go to **Keys → Add Key → Create new key → JSON** and
|
||||
download the file. Save it somewhere only Hermes can read (e.g.,
|
||||
`~/.hermes/google-chat-sa.json`, `chmod 600`).
|
||||
|
||||
:::caution There is NO "Chat Bot Caller" role
|
||||
A common mistake is to search for a Chat-specific IAM role and grant it at the
|
||||
project level. That role doesn't exist. Chat bot authority comes from being
|
||||
installed in a space, not from IAM. All your SA needs is Pub/Sub subscriber on
|
||||
the subscription you create in the next step.
|
||||
:::
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Create the Pub/Sub topic and subscription
|
||||
|
||||
**Pub/Sub → Topics → Create topic.**
|
||||
|
||||
- Topic ID: `hermes-chat-events`
|
||||
- Leave the defaults for everything else.
|
||||
|
||||
After creation, the topic's detail page has a **Subscriptions** tab. Create one:
|
||||
|
||||
- Subscription ID: `hermes-chat-events-sub`
|
||||
- Delivery type: **Pull**
|
||||
- Message retention: **7 days** (so backlog survives a hermes restart)
|
||||
- Leave the rest default.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: IAM binding on the topic (critical)
|
||||
|
||||
On the **topic** (not the subscription), add an IAM principal:
|
||||
|
||||
- Principal: `chat-api-push@system.gserviceaccount.com`
|
||||
- Role: `Pub/Sub Publisher`
|
||||
|
||||
Without this, Google Chat cannot publish events to your topic and your bot will
|
||||
never receive anything.
|
||||
|
||||
---
|
||||
|
||||
## Step 6: IAM binding on the subscription
|
||||
|
||||
On the **subscription**, add your own Service Account as a principal:
|
||||
|
||||
- Principal: `hermes-chat-bot@<your-project>.iam.gserviceaccount.com`
|
||||
- Role: `Pub/Sub Subscriber`
|
||||
|
||||
Also grant `Pub/Sub Viewer` on the same subscription — Hermes calls
|
||||
`subscription.get()` at startup as a reachability check.
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Configure the Chat app
|
||||
|
||||
Go to **APIs & Services → Google Chat API → Configuration**.
|
||||
|
||||
- **App name**: whatever you want users to see ("Hermes" is reasonable).
|
||||
- **Avatar URL**: any public PNG (Google has some defaults).
|
||||
- **Description**: a short sentence shown in the app directory.
|
||||
- **Functionality**: enable **Receive 1:1 messages** and **Join spaces and group
|
||||
conversations**.
|
||||
- **Connection settings**: select **Cloud Pub/Sub**, enter the topic name
|
||||
`projects/<your-project>/topics/hermes-chat-events`.
|
||||
- **Visibility**: restrict to your workspace (or specific users) — do not publish
|
||||
to everyone while you're testing.
|
||||
|
||||
Save.
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Install the bot in a test space
|
||||
|
||||
Open Google Chat in a browser. Start a DM with your app by searching for its name
|
||||
in the **+ New Chat** menu. The first time you message it, Google sends an
|
||||
`ADDED_TO_SPACE` event that Hermes uses to cache the bot's own `users/{id}` for
|
||||
self-message filtering.
|
||||
|
||||
---
|
||||
|
||||
## Step 9: Configure Hermes
|
||||
|
||||
Add the Google Chat section to `~/.hermes/.env`:
|
||||
|
||||
```bash
|
||||
# Required
|
||||
GOOGLE_CHAT_PROJECT_ID=my-chat-bot-123
|
||||
GOOGLE_CHAT_SUBSCRIPTION_NAME=projects/my-chat-bot-123/subscriptions/hermes-chat-events-sub
|
||||
GOOGLE_CHAT_SERVICE_ACCOUNT_JSON=/home/you/.hermes/google-chat-sa.json
|
||||
|
||||
# Authorization — paste the emails of people allowed to talk to the bot
|
||||
GOOGLE_CHAT_ALLOWED_USERS=you@yourdomain.com,coworker@yourdomain.com
|
||||
|
||||
# Optional
|
||||
GOOGLE_CHAT_HOME_CHANNEL=spaces/AAAA... # default delivery destination for cron jobs
|
||||
GOOGLE_CHAT_MAX_MESSAGES=1 # Pub/Sub FlowControl; 1 serializes commands per session
|
||||
GOOGLE_CHAT_MAX_BYTES=16777216 # 16 MiB — cap on in-flight message bytes
|
||||
```
|
||||
|
||||
The project ID also falls back to `GOOGLE_CLOUD_PROJECT`, and the SA path falls
|
||||
back to `GOOGLE_APPLICATION_CREDENTIALS` — use whichever convention you prefer.
|
||||
|
||||
Install Hermes with the optional dependencies:
|
||||
|
||||
```bash
|
||||
pip install 'hermes-agent[google_chat]'
|
||||
```
|
||||
|
||||
Start the gateway:
|
||||
|
||||
```bash
|
||||
hermes gateway
|
||||
```
|
||||
|
||||
You should see a log line like:
|
||||
|
||||
```
|
||||
[GoogleChat] Connected; project=my-chat-bot-123, subscription=<redacted>,
|
||||
bot_user_id=users/XXXX, flow_control(msgs=1, bytes=16777216)
|
||||
```
|
||||
|
||||
Send "hola" in the test DM. The bot posts a "Hermes is thinking…" marker, then
|
||||
edits that same message in place with the real response — no "message deleted"
|
||||
tombstones.
|
||||
|
||||
---
|
||||
|
||||
## Formatting and capabilities
|
||||
|
||||
Google Chat renders a limited markdown subset:
|
||||
|
||||
| Supported | Not supported |
|
||||
|-----------|---------------|
|
||||
| `*bold*`, `_italic_`, `~strike~`, `` `code` `` | Headings, lists |
|
||||
| Inline images via URL | Interactive Card v2 buttons (v1 of this gateway) |
|
||||
| Native file attachments (after `/setup-files` — see Step 10) | Native voice notes / circular video notes |
|
||||
|
||||
The agent's system prompt includes a Google Chat–specific hint so it knows these
|
||||
limits and avoids formatting that won't render.
|
||||
|
||||
Message size limit: 4000 characters per message. Longer agent responses are
|
||||
automatically split across multiple messages.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Step 10: Native attachment delivery (optional)
|
||||
|
||||
Out of the box the bot can post text, inline images via URL, and download cards
|
||||
for audio/video/documents. To deliver **native** Chat attachments — the same
|
||||
file widget you get when a human drags-and-drops a file — each user authorizes
|
||||
the bot once via a per-user OAuth flow.
|
||||
|
||||
### Why a separate flow
|
||||
|
||||
Google Chat's `media.upload` endpoint hard-rejects service-account auth:
|
||||
|
||||
> This method doesn't support app authentication with a service account.
|
||||
> Authenticate with a user account.
|
||||
|
||||
There's no IAM role or scope that fixes this. The endpoint only accepts user
|
||||
credentials. So the bot has to act *as a user* whenever it uploads a file —
|
||||
specifically, as the user who asked for the file.
|
||||
|
||||
### One-time host setup
|
||||
|
||||
1. Go to **APIs & Services → Credentials** in the same GCP project.
|
||||
2. **Create credentials → OAuth client ID → Desktop app**.
|
||||
3. Download the JSON. Move it onto the host that runs Hermes.
|
||||
4. On the host, register the client with Hermes:
|
||||
|
||||
```bash
|
||||
python -m gateway.platforms.google_chat_user_oauth \
|
||||
--client-secret /path/to/client_secret.json
|
||||
```
|
||||
|
||||
That writes `~/.hermes/google_chat_user_client_secret.json`. This is shared
|
||||
infrastructure — it identifies the OAuth *app*, not any individual user. One
|
||||
file per host is enough no matter how many users authorize later.
|
||||
|
||||
### Per-user authorization (in chat)
|
||||
|
||||
Each user runs the flow once, in their own DM with the bot:
|
||||
|
||||
1. They send `/setup-files` to the bot. It replies with status and the next
|
||||
step.
|
||||
2. They send `/setup-files start`. The bot replies with an OAuth URL.
|
||||
3. They open the URL, click **Allow**, and watch the browser fail to load
|
||||
`http://localhost:1/?...&code=...`. That failure is expected — the auth
|
||||
code is in the URL bar.
|
||||
4. They copy the failed URL (or just the `code=...` value) and paste it back
|
||||
into chat as `/setup-files <PASTED_URL>`. The bot exchanges it for a
|
||||
refresh token.
|
||||
|
||||
The token lands at `~/.hermes/google_chat_user_tokens/<sanitized_email>.json`.
|
||||
Subsequent file requests in that user's DM use *their* token, so the bot
|
||||
uploads as them and the message lands in their space.
|
||||
|
||||
To revoke later: `/setup-files revoke` deletes only that user's token. Other
|
||||
users' tokens are untouched.
|
||||
|
||||
### Scope
|
||||
|
||||
The flow requests exactly one scope: `chat.messages.create`. That covers both
|
||||
`media.upload` and the `messages.create` that references the uploaded
|
||||
`attachmentDataRef`. No Drive, no broader Chat scopes — this is least-privilege
|
||||
on purpose.
|
||||
|
||||
### Multi-user behavior
|
||||
|
||||
When the asker has no per-user token yet, the bot falls back to a legacy
|
||||
single-user token at `~/.hermes/google_chat_user_token.json` (if present from
|
||||
a pre-multi-user install). When neither is available, the bot posts a clear
|
||||
text notice telling the asker to run `/setup-files`.
|
||||
|
||||
A user revoking only clears their own slot. A 401/403 from one user's token
|
||||
evicts only that user's cache. Users don't disrupt each other.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Bot stays silent after sending "hola."**
|
||||
|
||||
1. Check the Pub/Sub subscription has undelivered messages in the console.
|
||||
If it does, Hermes isn't authenticated — verify `GOOGLE_CHAT_SERVICE_ACCOUNT_JSON`
|
||||
and that the SA is listed as `Pub/Sub Subscriber` on the subscription.
|
||||
2. If the subscription has zero messages, Google Chat isn't publishing.
|
||||
Double-check the IAM binding on the **topic**:
|
||||
`chat-api-push@system.gserviceaccount.com` must have `Pub/Sub Publisher`.
|
||||
3. Check `hermes gateway` logs for `[GoogleChat] Connected`. If you see
|
||||
`[GoogleChat] Config validation failed`, the error message tells you which
|
||||
env var to fix.
|
||||
|
||||
**Bot replies but an error message appears instead of the agent's answer.**
|
||||
|
||||
Check logs for `[GoogleChat] Pub/Sub stream died` — if these repeat, your SA
|
||||
credentials may have been rotated or the subscription deleted. After 10 attempts
|
||||
the adapter marks itself fatal.
|
||||
|
||||
**"403 Forbidden" on every outbound message.**
|
||||
|
||||
The bot was removed from the space, or you revoked it in the Chat API console.
|
||||
Re-install it in the space (the next `ADDED_TO_SPACE` event will re-enable
|
||||
messaging automatically).
|
||||
|
||||
**Too many "Rate limit hit" warnings.**
|
||||
|
||||
The Chat API's default quotas allow 60 messages per space per minute. If your
|
||||
agent produces long streaming responses that exceed that, the adapter retries
|
||||
with exponential backoff — but you'll still see user-visible latency. Consider
|
||||
concise responses or raising the quota in the GCP console.
|
||||
|
||||
**Bot keeps posting the "/setup-files" notice instead of files.**
|
||||
|
||||
The asker has no per-user OAuth token and there's no legacy fallback. Run
|
||||
`/setup-files` in their DM and follow Step 10. After the exchange completes
|
||||
the next file request uploads natively without a gateway restart.
|
||||
|
||||
**`/setup-files start` says "No client credentials stored on the host."**
|
||||
|
||||
The one-time host setup wasn't done. From a terminal on the host that runs
|
||||
Hermes:
|
||||
|
||||
```bash
|
||||
python -m gateway.platforms.google_chat_user_oauth \
|
||||
--client-secret /path/to/client_secret.json
|
||||
```
|
||||
|
||||
Then send `/setup-files start` again.
|
||||
|
||||
**`/setup-files <PASTED_URL>` says "Token exchange failed."**
|
||||
|
||||
The auth code is single-use and short-lived (typically a few minutes). Send
|
||||
`/setup-files start` to get a fresh URL and retry.
|
||||
|
||||
---
|
||||
|
||||
## Security notes
|
||||
|
||||
- **Service Account scope**: the adapter requests `chat.bot` and `pubsub` scopes.
|
||||
IAM should be the actual enforcement — grant your SA the minimum
|
||||
(`roles/pubsub.subscriber` + `roles/pubsub.viewer` on the subscription), not
|
||||
project-level or org-level Pub/Sub roles.
|
||||
- **Attachment download protection**: Hermes will only attach the SA bearer
|
||||
token to URLs whose host matches a short allowlist of Google-owned domains
|
||||
(`googleapis.com`, `drive.google.com`, `lh[3-6].googleusercontent.com`, and
|
||||
a few others). Any other host is rejected before the HTTP request, to
|
||||
protect against SSRF scenarios where a crafted event could redirect the
|
||||
bearer token to the GCE metadata service.
|
||||
- **Redaction**: Service Account emails, subscription paths, and topic paths
|
||||
are stripped from log output by `agent/redact.py`. The debug envelope dump
|
||||
(`GOOGLE_CHAT_DEBUG_RAW=1`) routes through the same redaction filter and
|
||||
logs at DEBUG level.
|
||||
- **Compliance**: if you plan to connect this bot to a regulated workspace
|
||||
(anything with a data-residency or AI-governance policy), get that approval
|
||||
before the first install.
|
||||
- **User OAuth scope**: the per-user attachment flow requests *only*
|
||||
`chat.messages.create` — the minimum that covers `media.upload` plus the
|
||||
follow-up `messages.create`. Tokens are persisted as plain JSON at
|
||||
`~/.hermes/google_chat_user_tokens/<sanitized_email>.json` (filesystem
|
||||
permissions are the protection — same model as the SA key file). Each
|
||||
token is owned by exactly one user; revoke is scoped to that user.
|
||||
@@ -17,6 +17,7 @@ For the full voice feature set — including CLI microphone mode, spoken replies
|
||||
| Telegram | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ |
|
||||
| Discord | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Slack | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Google Chat | — | ✅ | ✅ | ✅ | — | ✅ | — |
|
||||
| WhatsApp | — | ✅ | ✅ | — | — | ✅ | ✅ |
|
||||
| Signal | — | ✅ | ✅ | — | — | ✅ | ✅ |
|
||||
| SMS | — | — | — | — | — | — | — |
|
||||
@@ -46,6 +47,7 @@ flowchart TB
|
||||
dc[Discord]
|
||||
wa[WhatsApp]
|
||||
sl[Slack]
|
||||
gc[Google Chat]
|
||||
sig[Signal]
|
||||
sms[SMS]
|
||||
em[Email]
|
||||
@@ -74,6 +76,7 @@ flowchart TB
|
||||
dc --> store
|
||||
wa --> store
|
||||
sl --> store
|
||||
gc --> store
|
||||
sig --> store
|
||||
sms --> store
|
||||
em --> store
|
||||
@@ -383,6 +386,7 @@ Each platform has its own toolset:
|
||||
| Discord | `hermes-discord` | Full tools including terminal |
|
||||
| WhatsApp | `hermes-whatsapp` | Full tools including terminal |
|
||||
| Slack | `hermes-slack` | Full tools including terminal |
|
||||
| Google Chat | `hermes-google-chat` | Full tools including terminal |
|
||||
| Signal | `hermes-signal` | Full tools including terminal |
|
||||
| SMS | `hermes-sms` | Full tools including terminal |
|
||||
| Email | `hermes-email` | Full tools including terminal |
|
||||
@@ -406,6 +410,7 @@ Each platform has its own toolset:
|
||||
- [Telegram Setup](telegram.md)
|
||||
- [Discord Setup](discord.md)
|
||||
- [Slack Setup](slack.md)
|
||||
- [Google Chat Setup](google_chat.md)
|
||||
- [WhatsApp Setup](whatsapp.md)
|
||||
- [Signal Setup](signal.md)
|
||||
- [SMS Setup (Twilio)](sms.md)
|
||||
|
||||
@@ -18,7 +18,13 @@ flowchart LR
|
||||
B -->|SSE streaming response| A
|
||||
```
|
||||
|
||||
Open WebUI connects to Hermes Agent's API server just like it would connect to OpenAI. Your agent handles the requests with its full toolset — terminal, file operations, web search, memory, skills — and returns the final response.
|
||||
Open WebUI connects to Hermes Agent's API server just like it would connect to OpenAI. Hermes handles the requests with its full toolset — terminal, file operations, web search, memory, skills — and returns the final response.
|
||||
|
||||
:::important Runtime location
|
||||
The API server is a **Hermes agent runtime**, not a pure LLM proxy. For each request, Hermes creates a server-side `AIAgent` on the API-server host. Tool calls run where that API server is running.
|
||||
|
||||
For example, if a laptop points Open WebUI or another OpenAI-compatible client at a Hermes API server on a remote machine, `pwd`, file tools, browser tools, local MCP tools, and other workspace tools run on the remote API-server host, not on the laptop.
|
||||
:::
|
||||
|
||||
Open WebUI talks to Hermes server-to-server, so you do not need `API_SERVER_CORS_ORIGINS` for this integration.
|
||||
|
||||
@@ -205,13 +211,15 @@ Open WebUI currently manages conversation history client-side even in Responses
|
||||
When you send a message in Open WebUI:
|
||||
|
||||
1. Open WebUI sends a `POST /v1/chat/completions` request with your message and conversation history
|
||||
2. Hermes Agent creates an AIAgent instance with its full toolset
|
||||
3. The agent processes your request — it may call tools (terminal, file operations, web search, etc.)
|
||||
2. Hermes Agent creates a server-side `AIAgent` instance using the API server's profile, model/provider config, memory, skills, and configured API-server toolsets
|
||||
3. The agent processes your request — it may call tools (terminal, file operations, web search, etc.) on the API-server host
|
||||
4. As tools execute, **inline progress messages stream to the UI** so you can see what the agent is doing (e.g. `` `💻 ls -la` ``, `` `🔍 Python 3.12 release` ``)
|
||||
5. The agent's final text response streams back to Open WebUI
|
||||
6. Open WebUI displays the response in its chat interface
|
||||
|
||||
Your agent has access to all the same tools and capabilities as when using the CLI or Telegram — the only difference is the frontend.
|
||||
Your agent has access to the same tools and capabilities as that API-server Hermes instance. If the API server is remote, those tools are remote too.
|
||||
|
||||
If you need tools to run against your **local** workspace today, run Hermes locally and point it at a pure LLM provider or pure OpenAI-compatible model proxy (for example vLLM, LiteLLM, Ollama, llama.cpp, OpenAI, OpenRouter, etc.). A future split-runtime mode for "remote brain, local hands" is being tracked in [#18715](https://github.com/NousResearch/hermes-agent/issues/18715); it is not the behavior of the current API server.
|
||||
|
||||
:::tip Tool Progress
|
||||
With streaming enabled (the default), you'll see brief inline indicators as tools run — the tool emoji and its key argument. These appear in the response stream before the agent's final answer, giving you visibility into what's happening behind the scenes.
|
||||
|
||||
@@ -395,6 +395,8 @@ If a secret is configured but no recognized signature header is present, the req
|
||||
|
||||
Every route must have a secret — either set directly on the route or inherited from the global `secret`. Routes without a secret cause the adapter to fail at startup with an error. For development/testing only, you can set the secret to `"INSECURE_NO_AUTH"` to skip validation entirely.
|
||||
|
||||
`INSECURE_NO_AUTH` is only accepted when the gateway is bound to a loopback host (`127.0.0.1`, `localhost`, `::1`). If it is combined with a non-loopback bind such as `0.0.0.0` or a LAN IP, the adapter refuses to start — this prevents accidentally exposing an unauthenticated endpoint on a public interface.
|
||||
|
||||
### Rate limiting
|
||||
|
||||
Each route is rate-limited to **30 requests per minute** by default (fixed-window). Configure this globally:
|
||||
|
||||
Reference in New Issue
Block a user