mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
docs(platforms): document env_enablement_fn + cron_deliver_env_var hooks (#21331)
Following PR #21306 which added the new generic plugin-platform hooks, update the three platform-authoring docs so plugin authors find them: - website/docs/developer-guide/adding-platform-adapters.md: expand the 'What the Plugin System Handles Automatically' table with env-only auto-enable + cron delivery + hermes-config UI entries rows. Add three new sections — 'Env-Driven Auto-Configuration', 'Cron Delivery', 'Surfacing Env Vars in hermes config' — covering the hook signatures, plugin.yaml rich-dict format, and the home_channel-key special case. Update the main register() example to pass env_enablement_fn + cron_deliver_env_var inline so readers see them on their first pass. Upgrade the PLUGIN.yaml snippet to show bare-string + rich-dict + optional_env. - website/docs/guides/build-a-hermes-plugin.md: the thin platform example in the build-a-plugin tour now includes env_enablement_fn and cron_deliver_env_var, plus an optional_env block in the inline plugin.yaml. Keeps pointing to the developer-guide page for the full treatment. - gateway/platforms/ADDING_A_PLATFORM.md: the in-repo reference shallow-points at the docsite but now names the three new hooks explicitly so contributors reading the source tree know what they're for. Also adds teams + google_chat as reference implementations alongside irc.
This commit is contained in:
parent
5b121c6e35
commit
5c08b851df
3 changed files with 174 additions and 10 deletions
|
|
@ -4,18 +4,34 @@ There are two ways to add a platform to the Hermes gateway:
|
||||||
|
|
||||||
## Plugin Path (Recommended for Community/Third-Party)
|
## Plugin Path (Recommended for Community/Third-Party)
|
||||||
|
|
||||||
Create a plugin directory in `~/.hermes/plugins/` with a `PLUGIN.yaml` and
|
Create a plugin directory in `~/.hermes/plugins/` (or under `plugins/platforms/`
|
||||||
`adapter.py`. The adapter inherits from `BasePlatformAdapter` and registers
|
for bundled plugins) with a `plugin.yaml` and `adapter.py`. The adapter
|
||||||
via `ctx.register_platform()` in the `register(ctx)` entry point. This
|
inherits from `BasePlatformAdapter` and registers via
|
||||||
requires **zero changes to core Hermes code**.
|
`ctx.register_platform()` in the `register(ctx)` entry point. This requires
|
||||||
|
**zero changes to core Hermes code**.
|
||||||
|
|
||||||
The plugin system automatically handles: adapter creation, config parsing,
|
The plugin system automatically handles: adapter creation, config parsing,
|
||||||
user authorization, cron delivery, send_message routing, system prompt hints,
|
user authorization, cron delivery, send_message routing, system prompt hints,
|
||||||
status display, gateway setup, and more.
|
status display, gateway setup, and more.
|
||||||
|
|
||||||
See `plugins/platforms/irc/` for a complete reference implementation, and
|
**Three optional hooks cover the edges most adapters need:**
|
||||||
|
|
||||||
|
- `env_enablement_fn: () -> Optional[dict]` — seeds `PlatformConfig.extra`
|
||||||
|
(and an optional `home_channel` dict) from env vars BEFORE the adapter is
|
||||||
|
constructed. Without this, env-only setups don't surface in
|
||||||
|
`hermes gateway status` or `get_connected_platforms()` until the SDK
|
||||||
|
instantiates.
|
||||||
|
- `cron_deliver_env_var: str` — name of the `*_HOME_CHANNEL` env var. When
|
||||||
|
set, `deliver=<name>` cron jobs route to this var without editing
|
||||||
|
`cron/scheduler.py`'s hardcoded sets.
|
||||||
|
- `plugin.yaml` `requires_env` / `optional_env` rich-dict entries —
|
||||||
|
auto-populate `OPTIONAL_ENV_VARS` in `hermes_cli/config.py` so the setup
|
||||||
|
wizard surfaces proper descriptions, prompts, password flags, and URLs.
|
||||||
|
|
||||||
|
See `plugins/platforms/irc/`, `plugins/platforms/teams/`, and
|
||||||
|
`plugins/platforms/google_chat/` for complete working examples, and
|
||||||
`website/docs/developer-guide/adding-platform-adapters.md` for the full
|
`website/docs/developer-guide/adding-platform-adapters.md` for the full
|
||||||
plugin guide with code examples.
|
plugin guide with code examples and hook documentation.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,13 +40,25 @@ The plugin system lets you add a platform adapter without modifying any core Her
|
||||||
|
|
||||||
### PLUGIN.yaml
|
### PLUGIN.yaml
|
||||||
|
|
||||||
|
Plugin metadata. The `requires_env` and `optional_env` blocks auto-populate `hermes config` UI entries (see [Surfacing Env Vars](#surfacing-env-vars-in-hermes-config) below).
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: my-platform
|
name: my-platform
|
||||||
|
label: My Platform
|
||||||
|
kind: platform
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
description: My custom messaging platform adapter
|
description: My custom messaging platform adapter
|
||||||
|
author: Your Name
|
||||||
requires_env:
|
requires_env:
|
||||||
- MY_PLATFORM_TOKEN
|
- MY_PLATFORM_TOKEN # bare string works
|
||||||
- MY_PLATFORM_CHANNEL
|
- name: MY_PLATFORM_CHANNEL # or rich dict for better UX
|
||||||
|
description: "Channel to join"
|
||||||
|
prompt: "Channel"
|
||||||
|
password: false
|
||||||
|
optional_env:
|
||||||
|
- name: MY_PLATFORM_HOME_CHANNEL
|
||||||
|
description: "Default channel for cron delivery"
|
||||||
|
password: false
|
||||||
```
|
```
|
||||||
|
|
||||||
### adapter.py
|
### adapter.py
|
||||||
|
|
@ -90,6 +102,18 @@ def validate_config(config) -> bool:
|
||||||
return bool(os.getenv("MY_PLATFORM_TOKEN") or extra.get("token"))
|
return bool(os.getenv("MY_PLATFORM_TOKEN") or extra.get("token"))
|
||||||
|
|
||||||
|
|
||||||
|
def _env_enablement() -> dict | None:
|
||||||
|
token = os.getenv("MY_PLATFORM_TOKEN", "").strip()
|
||||||
|
channel = os.getenv("MY_PLATFORM_CHANNEL", "").strip()
|
||||||
|
if not (token and channel):
|
||||||
|
return None
|
||||||
|
seed = {"token": token, "channel": channel}
|
||||||
|
home = os.getenv("MY_PLATFORM_HOME_CHANNEL")
|
||||||
|
if home:
|
||||||
|
seed["home_channel"] = {"chat_id": home, "name": "Home"}
|
||||||
|
return seed
|
||||||
|
|
||||||
|
|
||||||
def register(ctx):
|
def register(ctx):
|
||||||
"""Plugin entry point — called by the Hermes plugin system."""
|
"""Plugin entry point — called by the Hermes plugin system."""
|
||||||
ctx.register_platform(
|
ctx.register_platform(
|
||||||
|
|
@ -100,6 +124,14 @@ def register(ctx):
|
||||||
validate_config=validate_config,
|
validate_config=validate_config,
|
||||||
required_env=["MY_PLATFORM_TOKEN"],
|
required_env=["MY_PLATFORM_TOKEN"],
|
||||||
install_hint="pip install my-platform-sdk",
|
install_hint="pip install my-platform-sdk",
|
||||||
|
# Env-driven auto-configuration — seeds PlatformConfig.extra from
|
||||||
|
# env vars before adapter construction. See "Env-Driven Auto-
|
||||||
|
# Configuration" section below.
|
||||||
|
env_enablement_fn=_env_enablement,
|
||||||
|
# Cron home-channel delivery support. Lets deliver=my_platform cron
|
||||||
|
# jobs route without editing cron/scheduler.py. See "Cron Delivery"
|
||||||
|
# section below.
|
||||||
|
cron_deliver_env_var="MY_PLATFORM_HOME_CHANNEL",
|
||||||
# Per-platform user authorization env vars
|
# Per-platform user authorization env vars
|
||||||
allowed_users_env="MY_PLATFORM_ALLOWED_USERS",
|
allowed_users_env="MY_PLATFORM_ALLOWED_USERS",
|
||||||
allow_all_env="MY_PLATFORM_ALLOW_ALL_USERS",
|
allow_all_env="MY_PLATFORM_ALLOW_ALL_USERS",
|
||||||
|
|
@ -149,7 +181,9 @@ When you call `ctx.register_platform()`, the following integration points are ha
|
||||||
| Config parsing | `Platform._missing_()` accepts any platform name |
|
| Config parsing | `Platform._missing_()` accepts any platform name |
|
||||||
| Connected platform validation | Registry `validate_config()` called |
|
| Connected platform validation | Registry `validate_config()` called |
|
||||||
| User authorization | `allowed_users_env` / `allow_all_env` checked |
|
| User authorization | `allowed_users_env` / `allow_all_env` checked |
|
||||||
| Cron delivery | `Platform()` resolves any registered name |
|
| Env-only auto-enable | `env_enablement_fn` seeds `PlatformConfig.extra` + `home_channel` |
|
||||||
|
| Cron delivery | `cron_deliver_env_var` makes `deliver=<name>` work |
|
||||||
|
| `hermes config` UI entries | `requires_env` / `optional_env` in `plugin.yaml` auto-populate |
|
||||||
| send_message tool | Routes through live gateway adapter |
|
| send_message tool | Routes through live gateway adapter |
|
||||||
| Webhook cross-platform delivery | Registry checked for known platforms |
|
| Webhook cross-platform delivery | Registry checked for known platforms |
|
||||||
| `/update` command access | `allow_update_command` flag |
|
| `/update` command access | `allow_update_command` flag |
|
||||||
|
|
@ -163,6 +197,100 @@ When you call `ctx.register_platform()`, the following integration points are ha
|
||||||
| Token lock (multi-profile) | Use `acquire_scoped_lock()` in your `connect()` |
|
| Token lock (multi-profile) | Use `acquire_scoped_lock()` in your `connect()` |
|
||||||
| Orphaned config warning | Descriptive log when plugin is missing |
|
| Orphaned config warning | Descriptive log when plugin is missing |
|
||||||
|
|
||||||
|
## Env-Driven Auto-Configuration
|
||||||
|
|
||||||
|
Most users set up a platform by dropping env vars into `~/.hermes/.env` rather than editing `config.yaml`. The `env_enablement_fn` hook lets your plugin pick those env vars up **before** the adapter is constructed, so `hermes gateway status`, `get_connected_platforms()`, and cron delivery see the correct state without instantiating the platform SDK.
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _env_enablement() -> dict | None:
|
||||||
|
"""Seed PlatformConfig.extra from env vars.
|
||||||
|
|
||||||
|
Called by the platform registry during load_gateway_config().
|
||||||
|
Return None when the platform isn't minimally configured — the
|
||||||
|
caller then skips auto-enabling. Return a dict to seed extras.
|
||||||
|
|
||||||
|
The special 'home_channel' key is extracted and becomes a proper
|
||||||
|
HomeChannel dataclass on the PlatformConfig; every other key is
|
||||||
|
merged into PlatformConfig.extra.
|
||||||
|
"""
|
||||||
|
token = os.getenv("MY_PLATFORM_TOKEN", "").strip()
|
||||||
|
channel = os.getenv("MY_PLATFORM_CHANNEL", "").strip()
|
||||||
|
if not (token and channel):
|
||||||
|
return None
|
||||||
|
seed = {"token": token, "channel": channel}
|
||||||
|
home = os.getenv("MY_PLATFORM_HOME_CHANNEL")
|
||||||
|
if home:
|
||||||
|
seed["home_channel"] = {
|
||||||
|
"chat_id": home,
|
||||||
|
"name": os.getenv("MY_PLATFORM_HOME_CHANNEL_NAME", "Home"),
|
||||||
|
}
|
||||||
|
return seed
|
||||||
|
|
||||||
|
|
||||||
|
def register(ctx):
|
||||||
|
ctx.register_platform(
|
||||||
|
name="my_platform",
|
||||||
|
label="My Platform",
|
||||||
|
adapter_factory=lambda cfg: MyPlatformAdapter(cfg),
|
||||||
|
check_fn=check_requirements,
|
||||||
|
validate_config=validate_config,
|
||||||
|
env_enablement_fn=_env_enablement,
|
||||||
|
# ... other fields
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cron Delivery
|
||||||
|
|
||||||
|
To let `deliver=my_platform` cron jobs route to a configured home channel, set `cron_deliver_env_var` to the env var name that holds the default chat/room/channel ID:
|
||||||
|
|
||||||
|
```python
|
||||||
|
ctx.register_platform(
|
||||||
|
name="my_platform",
|
||||||
|
...
|
||||||
|
cron_deliver_env_var="MY_PLATFORM_HOME_CHANNEL",
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The scheduler reads this env var when resolving the home target for `deliver=my_platform` jobs, and also treats the platform as a valid cron target in `_KNOWN_DELIVERY_PLATFORMS`-style checks. If your `env_enablement_fn` seeds a `home_channel` dict (see above), that takes precedence — `cron_deliver_env_var` is the fallback for cron jobs that run before env seeding.
|
||||||
|
|
||||||
|
## Surfacing Env Vars in `hermes config`
|
||||||
|
|
||||||
|
`hermes_cli/config.py` scans `plugins/platforms/*/plugin.yaml` at import time and auto-populates `OPTIONAL_ENV_VARS` from `requires_env` and (optional) `optional_env` blocks. Use the rich-dict form to contribute proper descriptions, prompts, password flags, and URLs — the CLI setup UI picks them up for free.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# plugins/platforms/my_platform/plugin.yaml
|
||||||
|
name: my_platform-platform
|
||||||
|
label: My Platform
|
||||||
|
kind: platform
|
||||||
|
version: 1.0.0
|
||||||
|
description: >
|
||||||
|
My Platform gateway adapter for Hermes Agent.
|
||||||
|
author: Your Name
|
||||||
|
requires_env:
|
||||||
|
- name: MY_PLATFORM_TOKEN
|
||||||
|
description: "Bot API token from the My Platform console"
|
||||||
|
prompt: "My Platform bot token"
|
||||||
|
url: "https://my-platform.example.com/bots"
|
||||||
|
password: true
|
||||||
|
- name: MY_PLATFORM_CHANNEL
|
||||||
|
description: "Channel to join (e.g. #hermes)"
|
||||||
|
prompt: "Channel"
|
||||||
|
password: false
|
||||||
|
optional_env:
|
||||||
|
- name: MY_PLATFORM_HOME_CHANNEL
|
||||||
|
description: "Default channel for cron delivery (defaults to MY_PLATFORM_CHANNEL)"
|
||||||
|
prompt: "Home channel (or empty)"
|
||||||
|
password: false
|
||||||
|
- name: MY_PLATFORM_ALLOWED_USERS
|
||||||
|
description: "Comma-separated user IDs allowed to talk to the bot"
|
||||||
|
prompt: "Allowed users (comma-separated)"
|
||||||
|
password: false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Supported dict keys:** `name` (required), `description`, `prompt`, `url`, `password` (bool; auto-detected from `*_TOKEN` / `*_SECRET` / `*_KEY` / `*_PASSWORD` / `*_JSON` suffix when omitted), `category` (defaults to `"messaging"`).
|
||||||
|
|
||||||
|
Bare-string entries (`- MY_PLATFORM_TOKEN`) still work — they get a generic description auto-derived from the plugin's `label`. If a hardcoded entry for the same var already exists in `OPTIONAL_ENV_VARS`, it wins (back-compat); the plugin.yaml form acts as the fallback.
|
||||||
|
|
||||||
### Reference Implementation
|
### Reference Implementation
|
||||||
|
|
||||||
See `plugins/platforms/irc/` in the repo for a complete working example — a full async IRC adapter with zero external dependencies.
|
See `plugins/platforms/irc/` in the repo for a complete working example — a full async IRC adapter with zero external dependencies.
|
||||||
|
|
|
||||||
|
|
@ -747,6 +747,13 @@ def check_requirements():
|
||||||
import os
|
import os
|
||||||
return bool(os.environ.get("MYPLATFORM_TOKEN"))
|
return bool(os.environ.get("MYPLATFORM_TOKEN"))
|
||||||
|
|
||||||
|
def _env_enablement():
|
||||||
|
import os
|
||||||
|
tok = os.getenv("MYPLATFORM_TOKEN", "").strip()
|
||||||
|
if not tok:
|
||||||
|
return None
|
||||||
|
return {"token": tok}
|
||||||
|
|
||||||
def register(ctx):
|
def register(ctx):
|
||||||
ctx.register_platform(
|
ctx.register_platform(
|
||||||
name="myplatform",
|
name="myplatform",
|
||||||
|
|
@ -754,6 +761,11 @@ def register(ctx):
|
||||||
adapter_factory=lambda cfg: MyPlatformAdapter(cfg),
|
adapter_factory=lambda cfg: MyPlatformAdapter(cfg),
|
||||||
check_fn=check_requirements,
|
check_fn=check_requirements,
|
||||||
required_env=["MYPLATFORM_TOKEN"],
|
required_env=["MYPLATFORM_TOKEN"],
|
||||||
|
# Auto-populate PlatformConfig.extra from env so env-only setups
|
||||||
|
# show up in `hermes gateway status` without SDK instantiation.
|
||||||
|
env_enablement_fn=_env_enablement,
|
||||||
|
# Opt in to cron delivery: `deliver=myplatform` routes to this var.
|
||||||
|
cron_deliver_env_var="MYPLATFORM_HOME_CHANNEL",
|
||||||
emoji="💬",
|
emoji="💬",
|
||||||
platform_hint="You are chatting via MyPlatform. Keep responses concise.",
|
platform_hint="You are chatting via MyPlatform. Keep responses concise.",
|
||||||
)
|
)
|
||||||
|
|
@ -762,10 +774,18 @@ def register(ctx):
|
||||||
```yaml
|
```yaml
|
||||||
# plugins/platforms/myplatform/plugin.yaml
|
# plugins/platforms/myplatform/plugin.yaml
|
||||||
name: myplatform-platform
|
name: myplatform-platform
|
||||||
|
label: MyPlatform
|
||||||
kind: platform
|
kind: platform
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
description: MyPlatform gateway adapter
|
description: MyPlatform gateway adapter
|
||||||
requires_env: [MYPLATFORM_TOKEN]
|
requires_env:
|
||||||
|
- name: MYPLATFORM_TOKEN
|
||||||
|
description: "Bot token from the MyPlatform console"
|
||||||
|
password: true
|
||||||
|
optional_env:
|
||||||
|
- name: MYPLATFORM_HOME_CHANNEL
|
||||||
|
description: "Default channel for cron delivery"
|
||||||
|
password: false
|
||||||
```
|
```
|
||||||
|
|
||||||
**Full guide:** [Adding Platform Adapters](/docs/developer-guide/adding-platform-adapters) — complete `BasePlatformAdapter` contract, message routing, auth gating, setup wizard integration. Look at `plugins/platforms/irc/` for a stdlib-only working example.
|
**Full guide:** [Adding Platform Adapters](/docs/developer-guide/adding-platform-adapters) — complete `BasePlatformAdapter` contract, message routing, auth gating, setup wizard integration. Look at `plugins/platforms/irc/` for a stdlib-only working example.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue