Hermes bus integration plugin — auto-start, auto-register, message injection, bus tools
Project description
hermes-bus-plugin
Role in the Hermes messaging ecosystem: hermes-bus-plugin is the receive-side agent plugin (Layer 3) — it consumes bus messages and routes them to terminal output, LLM context injection, or command execution. The other two packages are:
- hermes-notify — CLI senders (Layer 1) that inject messages into the ecosystem
- hermes-bus — transport daemon (Layer 2) that routes JSON messages between endpoints
Together: notify → bus → plugin → Gateway adapters → users. Zero hermes-agent code changes at the channel routing layer.
Install
# Via Hermes plugin manager
hermes plugins install hermes-bus-plugin
# Or copy to ~/.hermes/hermes-agent/plugins/
cp -r hermes-bus-plugin ~/.hermes/hermes-agent/plugins/
hermes plugins enable hermes-bus-plugin
Session Naming
Each CLI window registers a unique bus endpoint on startup. The default endpoint is hermes-bus (first session), with hermes-bus-2, hermes-bus-3, etc. for additional sessions. To give your session a stable name that survives reconnection:
/title my-agent-name
The plugin uses the title set by /title as the bus endpoint name.
| Action | When | Description |
|---|---|---|
| Start bus daemon | Plugin load | Ensures hermes-bus is running |
| Register listener | Plugin load | Opens a bus endpoint for incoming messages |
| Print notifications | On bus message | print: true → terminal (only when context is NOT true) |
| Inject context + push | On bus message | context: true → inject into LLM context + push to Agent via pending_input (overrides print, token-heavy — use sparingly) |
| Execute command | On bus message | command → async subprocess (audio, scripts, etc.) — runs inside Hermes process, no external daemon needed |
Provided Tools
bus_send — send a message through the bus to any endpoint:
bus_send(target="notifier", type="progress", text="50% done")
bus_status — check bus health and connected endpoints:
bus_status()
bus_info — show current session's bus connection details:
bus_info()
Route Rules
Messages arriving at the bus are matched against ~/.hermes/bus-rules.yaml rules.
Each rule can trigger three independent actions:
| Field | Behavior | Default |
|---|---|---|
print |
Print to terminal | false |
print_format |
Template or script for terminal output | {text} |
context |
Inject into LLM context + push to Agent | false |
context_format |
Template or script for context/push text | {text} |
command |
Execute shell command (audio, script, etc.) | none |
Priority Logic
context and print are mutually exclusive:
context: true→ inject context + push to Agent (print is ignored). ⚠️ Token-heavy — each push triggers an Agent turn.print: true→ terminal output only (only when context is NOT true)command→ always executed if defined, independent of context/print
Format Templates
print_format and context_format support these placeholders:
| Placeholder | Description |
|---|---|
{from} |
Sender endpoint name |
{text} |
Message body text |
{type} |
Message type |
{ts} |
Unix timestamp (raw) |
{ts:%Y-%m-%d %H:%M:%S} |
strftime formatted |
{color:cyan} |
ANSI foreground color (black/red/green/yellow/blue/magenta/cyan/white) |
{color:bold_green} |
Bold color variant |
{bold} |
Bold text |
{reset} |
Reset all styles |
Script Support
If print_format or context_format starts with ~ or / and points to an executable file, the script is run with FROM, TYPE, TEXT as environment variables and its stdout is used as the rendered output (supports ANSI colors).
#!/bin/bash
# format-notify.sh — example format script
GREEN="\033[1;32m"
YELLOW="\033[1;33m"
RESET="\033[0m"
case "$TYPE" in
task_done) echo -e "${GREEN}✔ ${FROM}${RESET} — ${TEXT}" ;;
task_error) echo -e "${YELLOW}✖ ${FROM} failed${RESET}\n ${TEXT}" ;;
*) echo -e "${FROM}: ${TEXT}" ;;
esac
# bus-rules.yaml
- match_type: task_done
print: true
print_format: "~/scripts/format-notify.sh"
Example Rules
callbacks:
# Notification only (no context)
- match_type: ack
print: true
print_format: "{color:cyan}📬 {from}{reset} {text} [{ts:%H:%M}]"
context: false
# Silent context injection
- match_type: progress
print: false
context: true
# Context + terminal + audio
- match_type: task_done
print: true
print_format: "{color:bold_green}✔ {from}{reset} → {text} {color:cyan}[{ts:%H:%M:%S}]{reset}"
context: true
command: "afplay ~/sounds/done.mp3"
Notification Protocol
This section documents the complete end-to-end notification protocol — how agents send messages, how the bus routes them, and how cross-agent communication works. Zero changes to hermes-agent code are required.
1. notify-agent — Send to a CLI Session
Sends a message directly to a tmux session. Used for inter-agent communication within the same machine.
The first argument is the tmux session name, not an agent name or bus endpoint. You create sessions with tmux new-session -s <name>. The notify-agent tool sends keystrokes to the target session's active pane.
# Start two agent sessions
tmux new-session -d -s lead-agent 'claude'
tmux new-session -d -s worker-alpha 'claude'
# Send a message to the lead agent
notify-agent lead-agent "Task queue is empty, ready for next assignment"
# Send with explicit sender name
notify-agent --from worker-alpha lead-agent "Build complete, 3 tests passing"
| Argument | Required | Description |
|---|---|---|
<session> |
yes | Target tmux session name — the name passed to tmux new-session -s |
--from |
no | Sender display name (e.g. worker-alpha). Auto-detected from session name if omitted |
"message" |
yes | Plain text message (positional, last argument) |
Important: notify-agent sends text to a tmux pane directly. It does NOT go through the bus. The target must be a running tmux session. Use notify-hermes for bus-routed messages.
2. notify-hermes — Send Through the Bus
Sends a message through the hermes-bus daemon. The bus delivers it to all registered endpoints. bus-rules.yaml callbacks control how each endpoint processes the message (print to terminal, inject into LLM context, execute a command).
# Format
notify-hermes --to <endpoint> [options] "message text"
# Or with full JSON body
notify-hermes --to <endpoint> --body '{"text":"hello","key":"value"}'
# Examples
notify-hermes --to hermes-bus --type ack "Acknowledged, starting work"
notify-hermes --to hermes-bus --type task_done "All tasks complete"
notify-hermes --to hermes-bus --type task_error --channel wecom_ops "Production outage, manual intervention needed"
Message body (constructed from CLI args)
| Argument | Body field | Default | Description |
|---|---|---|---|
"message" |
text |
— | Plain text (positional, mutually exclusive with --body) |
--body |
(raw JSON) | — | Full JSON body dict (mutually exclusive with positional message) |
--type |
type |
none | Message type: ack, task_start, progress, task_done, plan_ready, task_error, need_decision, directive |
--channel |
channel |
none | Reply routing token (see Channel Protocol below) |
--from |
from_ep |
auto-detected | Override sender name (auto-detected from tmux session via role_map) |
--to |
(routing) | required | Target bus endpoint name |
--type values and their behavior
The values below are common conventions —
--typeaccepts any string,bus-rules.yamlmatches them exactly.
--type |
Meaning | Typical context |
Typical print |
Voice |
|---|---|---|---|---|
directive |
Task assignment (coordinator → worker) | true | false | no |
ack |
Acknowledgement ("received, working") | false | true | no |
task_start |
Task started | true | false | no |
progress |
Intermediate progress update | true | false | no |
task_done |
Task completed | true | true | yes |
plan_ready |
Plan ready for review | true | true | yes |
task_error |
Error / escalation | true | true | yes |
need_decision |
Decision needed | true | true | yes |
3. --channel Protocol — Reply Routing
The --channel parameter enables reply routing across chat platforms. It flows through the entire notification chain and allows the system to reply back to the original conversation.
Format
<platform>:<chat_id>
| Value | Resolves to |
|---|---|
feishu:oc_abc123 |
Feishu, specific chat oc_abc123 |
wecom:ww456 |
WeCom, specific chat ww456 |
dingtalk:cid789 |
DingTalk, specific chat cid789 |
feishu |
Feishu, fallback to FEISHU_HOME_CHANNEL env var |
wecom |
WeCom, fallback to WECOM_HOME_CHANNEL env var |
Resolution logic
- Split
channelon first: - If
chat_idpresent → use directly - If
chat_idabsent → resolve from*_HOME_CHANNELenvironment variable (set by Gateway platform adapters) - Map
platformto the live Gateway adapter viaGatewayRunner.adapters - Call
adapter.send(chat_id, content)— async, bridged viaasyncio.run_coroutine_threadsafe
Channel pass-through
When the bus-plugin receives a message with channel set, the channel token is preserved through the entire chain:
incoming body.channel = "feishu:oc_abc123"
→ agent sees the channel in context
→ agent includes --channel feishu:oc_abc123 in its notify-hermes calls
→ bus-plugin forwards reply to feishu adapter
→ user sees response in the original chat
The channel field is an opaque routing token. It is never interpreted or modified by agents — they simply echo it back. Only the bus-plugin (at the final delivery point) acts on it.
4. Bus-Plugin Receive-Side Routing
When hermes-bus delivers a message to the plugin's registered endpoint, _process_bus_message() dispatches it based on bus-rules.yaml callbacks:
Bus message arrives
│
├─ match_type → callback rule
│
├─ context: true
│ └─ Render context_format → queue for on_pre_llm_call() injection
│ → push to Agent via pending_input (triggers Agent turn)
│ ⚠️ print is IGNORED when context is true
│
├─ print: true (context is false, OR runs alongside context)
│ └─ Render print_format → terminal output (ANSI via prompt_toolkit)
│ → If body.channel is set: try Gateway adapter.send() first
│ (asyncio.run_coroutine_threadsafe from listen_bus thread)
│ → Fallback: _cprint() to terminal
│
└─ command (always executed if defined)
└─ subprocess.Popen(shell=True)
Env vars: MESSAGE (full JSON), TYPE, FROM, CHANNEL, TEXT, TS
→ Example: play-notify-sound, gateway-forward
Env vars available to command scripts
| Env var | Content |
|---|---|
MESSAGE |
Full bus message as JSON string |
TYPE |
Message type (e.g. task_done) |
FROM |
Sender endpoint name |
CHANNEL |
Channel string if --channel was used (empty otherwise) |
TEXT |
Message body text |
TS |
Unix timestamp |
5. AI Agent Notification Lifecycle
The complete lifecycle for agent-to-agent communication via the bus:
User message arrives (e.g., via Feishu → Gateway → Agent)
│ body.channel = "feishu:oc_abc123" ← Gateway sets this
▼
Agent processes message, reports progress to lead agent via bus
│ notify-hermes --to lead-agent --type progress "Phase 1 done" --channel feishu:oc_abc123
│ └─ channel preserved from incoming message
▼
Bus routes to lead-agent endpoint
│ bus-rules.yaml matches type=progress → context=true (silent injection)
▼
Lead agent's LLM sees context, decides to dispatch a worker
│ Agent calls notify-hermes --to worker-beta --type directive "run task X" --channel feishu:oc_abc123
│ └─ channel forwarded to worker
▼
Worker agent receives directive via bus, starts work
│ Worker completes, reports back:
│ notify-hermes --to lead-agent --type task_done "X complete" --channel feishu:oc_abc123
▼
Bus routes task_done → lead-agent endpoint
│ bus-rules.yaml: print=true + context=true + command=play-notify-sound;gateway-forward
│ command branch → CHANNEL=feishu:oc_abc123 → gateway-forward → adapter.send()
▼
Original user receives reply in Feishu: "X complete"
Key principle: channel is an opaque routing token. Agents pass it through without interpreting it. The bus-plugin handles final delivery. Agent reasoning stays simple — echo the channel you received.
6. Complete End-to-End Example
A concrete walkthrough using three tmux sessions (lead-agent, worker-alpha, worker-beta) and the bus.
Setup
# Start three agent sessions
tmux new-session -d -s lead-agent 'claude'
tmux new-session -d -s worker-alpha 'claude'
tmux new-session -d -s worker-beta 'claude'
Flow
── 1. Coordinator dispatches ──────────────────────────────────────
A user request arrives via Gateway (channel = "feishu:oc_abc123").
lead-agent's LLM decides: "worker-alpha should handle this."
Agent calls:
notify-hermes --to worker-alpha --type directive \
--channel feishu:oc_abc123 \
"Refactor auth middleware — extract token validation into a shared module"
→ Bus routes to worker-alpha endpoint
→ bus-rules.yaml: directive → context=true (silent injection)
→ worker-alpha's Agent sees: "[directive] lead-agent: Refactor auth middleware..."
→ channel=feishu:oc_abc123 carried through
── 2. Worker acknowledges ─────────────────────────────────────────
notify-hermes --to lead-agent --type ack \
--channel feishu:oc_abc123 \
"Received, starting auth middleware refactor"
── 3. Worker reports progress ─────────────────────────────────────
notify-hermes --to lead-agent --type progress \
--channel feishu:oc_abc123 \
"Token validation extracted, 3 of 5 endpoints migrated"
→ lead-agent sees: "worker-alpha progress: Token validation extracted..."
── 4. Worker completes ────────────────────────────────────────────
notify-hermes --to lead-agent --type task_done \
--channel feishu:oc_abc123 \
"Auth middleware refactor complete. 5/5 endpoints migrated, all tests pass"
→ Bus routes task_done → lead-agent endpoint
→ bus-rules.yaml: print=true + context=true + command
→ Terminal: "worker-alpha completed: Auth middleware refactor complete..."
→ command: play-notify-sound (audio cue)
→ command: gateway-forward → CHANNEL=feishu:oc_abc123
→ adapter.send(chat_id="oc_abc123", content="...")
→ User sees in Feishu: "Auth middleware refactor complete. 5/5 endpoints migrated"
── 5. Status check via bus (zero I/O) ─────────────────────────────
notify-hermes --to worker-alpha --type ack "Status check: still alive?"
# worker-alpha responds within 30s per protocol
Channel Lifecycle Summary
Gateway sets channel ──→ Agent echoes channel ──→ Bus carries channel
(incoming platform msg) (in all notify-hermes) (in message body)
Bus-plugin receives ──→ command script reads CHANNEL ──→ adapter.send()
(body.channel preserved) (env var injection) (reply to user)
The channel token never changes. Every agent passes it through unmodified. Only the bus-plugin (at final delivery) acts on it.
Requirements
hermes-bus(pip)hermes-notify(pip)
Both are auto-detected — the plugin degrades gracefully if they're missing.
Architecture
External process ──→ hermes-bus ──→ hermes-bus-plugin ──→ LLM context
(socket) ├─ pre_llm_call hook
└─ async subprocess (command: audio/script)
Hermes session ──→ bus_send tool ──→ hermes-bus ──→ target endpoint
command execution (audio playback, shell scripts) runs inside the Hermes process via subprocess.Popen. No external daemon (bus-notifier) needed — one less process to manage, no point-to-point routing issues, no silent failures.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hermes_bus_plugin-0.5.0.tar.gz.
File metadata
- Download URL: hermes_bus_plugin-0.5.0.tar.gz
- Upload date:
- Size: 21.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4038131d88623d85263ae49964897ca3184cc8658d95da6e15a4667c6473d790
|
|
| MD5 |
a86192e40366c21148e659780078cfe2
|
|
| BLAKE2b-256 |
f0de217c9e36f886761dee0c61314cf25ee2bf40fc2e9f34eb28134ceb4f2eb5
|
File details
Details for the file hermes_bus_plugin-0.5.0-py3-none-any.whl.
File metadata
- Download URL: hermes_bus_plugin-0.5.0-py3-none-any.whl
- Upload date:
- Size: 17.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.15
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e07627e24f095d033890c40ee0ca0449bcaa9969b3bca353ad6c1d1e58a1ffbe
|
|
| MD5 |
5b275f20b4f5de6c88cee97d1d094634
|
|
| BLAKE2b-256 |
365205b900d289cb26395516770c66530434685d8ff5784a79ccdbfb273159dc
|