feat: openclaw-channel plugin - send_to_agent tool for inter-agent relay
This commit is contained in:
parent
25c02523bf
commit
542571ccc5
42
README.md
42
README.md
@ -1,3 +1,41 @@
|
||||
# skill-openclaw-channel
|
||||
# openclaw-channel plugin
|
||||
|
||||
Hermes plugin: send_to_agent tool for inter-agent relay messaging
|
||||
**Critical infrastructure.** This plugin provides the `send_to_agent` tool — the only way agents can send messages to each other through the relay.
|
||||
|
||||
## What it does
|
||||
|
||||
Registers a single tool (`send_to_agent`) with Hermes' tool registry. The tool POSTs messages to the relay on www0, which routes them to the target agent's webhook.
|
||||
|
||||
## Why it matters
|
||||
|
||||
Without this plugin, agents cannot communicate with each other. Inter-agent coordination, delegation, and status reporting all depend on it.
|
||||
|
||||
**Do not remove this plugin.** It was accidentally deleted once (2026-05-09) and broke all inter-agent messaging until restored.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `__init__.py` | Plugin entrypoint — registers the tool with Hermes |
|
||||
| `send_to_agent.py` | Tool implementation — schema, handler, auth |
|
||||
| `plugin.yaml` | Plugin metadata |
|
||||
| `README.md` | This file |
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Variable | Description | Example |
|
||||
|----------|-------------|---------|
|
||||
| `OPENCLAW_AGENT_ID` | This agent's name | `bastion` |
|
||||
| `OPENCLAW_RELAY_URL` | Relay ingress URL | `http://www0:4100` |
|
||||
| `OPENCLAW_RELAY_SECRET` | Auth secret for relay | (set in container .env) |
|
||||
|
||||
## Message types
|
||||
|
||||
- `fyi` — informational, no reply expected (default)
|
||||
- `request` — recipient should reply
|
||||
- `ack` — acknowledging a prior message; terminal
|
||||
|
||||
## Relay docs
|
||||
|
||||
Full architecture: `/opt/data/docs/hermes-comms-bridge.md`
|
||||
Relay skill: `hermes-relay`
|
||||
|
||||
26
__init__.py
Normal file
26
__init__.py
Normal file
@ -0,0 +1,26 @@
|
||||
"""openclaw-channel plugin.
|
||||
|
||||
Registers the send_to_agent tool for inter-agent messaging via openclaw-relay.
|
||||
|
||||
See send_to_agent.py for tool implementation and README.md for context.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def register(ctx):
|
||||
from .send_to_agent import _send, _check, SEND_SCHEMA, AGENT_ID
|
||||
|
||||
ctx.register_tool(
|
||||
name="send_to_agent",
|
||||
toolset="openclaw",
|
||||
schema=SEND_SCHEMA,
|
||||
handler=_send,
|
||||
check_fn=_check,
|
||||
description="Send a message to another fleet agent via openclaw-relay.",
|
||||
emoji="📡",
|
||||
)
|
||||
logger.info("openclaw-channel: registered send_to_agent (agent=%s)", AGENT_ID)
|
||||
5
plugin.yaml
Normal file
5
plugin.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
name: openclaw-channel
|
||||
version: 0.1.0
|
||||
description: "openclaw-relay inter-agent channel — adds send_to_agent tool for fleet messaging"
|
||||
provides_tools:
|
||||
- send_to_agent
|
||||
131
send_to_agent.py
Normal file
131
send_to_agent.py
Normal file
@ -0,0 +1,131 @@
|
||||
"""send_to_agent — inter-agent messaging via openclaw-relay.
|
||||
|
||||
This tool is the OUTBOUND half of the relay channel. It lets agents send
|
||||
messages to other fleet agents through the relay on www0.
|
||||
|
||||
INBOUND messages arrive via the webhook platform (config.yaml →
|
||||
platforms.webhook.routes.openclaw), delivered by the relay to each
|
||||
agent's webhook endpoint.
|
||||
|
||||
CRITICAL: This tool is how agents talk to each other. Without it,
|
||||
inter-agent coordination is dead. Do NOT remove or rename without
|
||||
replacing the functionality.
|
||||
|
||||
Environment variables required:
|
||||
OPENCLAW_AGENT_ID — this agent's name (e.g. "bastion", "vigil")
|
||||
OPENCLAW_RELAY_URL — relay ingress URL (e.g. http://www0:4100)
|
||||
OPENCLAW_RELAY_SECRET — auth secret for relay ingress
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
from urllib import error as urlerror, request as urlrequest
|
||||
|
||||
from tools.registry import tool_error
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AGENT_ID = os.environ.get("OPENCLAW_AGENT_ID", "bastion")
|
||||
RELAY_URL = os.environ.get("OPENCLAW_RELAY_URL", "http://www0:4100").rstrip("/")
|
||||
SECRET = (os.environ.get("OPENCLAW_RELAY_SECRET") or "").strip()
|
||||
|
||||
KNOWN_AGENTS = [
|
||||
"atlas", "bastion", "claude_memgpt", "clio", "ferret", "flux",
|
||||
"mint", "nene", "piper", "reed", "scout", "skippy", "tangent",
|
||||
"terra", "vera", "vigil",
|
||||
]
|
||||
|
||||
SEND_SCHEMA = {
|
||||
"name": "send_to_agent",
|
||||
"description": (
|
||||
f"Send a message to another fleet agent via openclaw-relay as "
|
||||
f"{AGENT_ID}.\n\n"
|
||||
"Types:\n"
|
||||
"- fyi: informational, no reply expected (default)\n"
|
||||
"- request: the recipient should reply\n"
|
||||
"- ack: acknowledging a prior message; terminal\n\n"
|
||||
f"Known agents: {', '.join(KNOWN_AGENTS)}.\n"
|
||||
"Returns the relay's JSON response (delivered/queued/error)."
|
||||
),
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"to": {
|
||||
"type": "string",
|
||||
"description": "Target agent name (lowercase). Example: terra, claude_memgpt.",
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "Message body to send.",
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["fyi", "request", "ack"],
|
||||
"default": "fyi",
|
||||
"description": "Message classification.",
|
||||
},
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "Optional project tag for relay-side grouping/filtering.",
|
||||
},
|
||||
"inReplyTo": {
|
||||
"type": "string",
|
||||
"description": "Optional messageId being replied to.",
|
||||
},
|
||||
},
|
||||
"required": ["to", "message"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _send(args: dict, **kwargs: Any) -> str:
|
||||
to = str(args.get("to") or "").lower().strip()
|
||||
message = args.get("message")
|
||||
if not to or not message:
|
||||
return tool_error("to and message are required")
|
||||
if not SECRET:
|
||||
return tool_error(
|
||||
"OPENCLAW_RELAY_SECRET not set; cannot authenticate to openclaw-relay"
|
||||
)
|
||||
|
||||
body: dict[str, Any] = {
|
||||
"from": AGENT_ID,
|
||||
"to": to,
|
||||
"type": args.get("type") or "fyi",
|
||||
"body": message,
|
||||
}
|
||||
if args.get("project"):
|
||||
body["project"] = args["project"]
|
||||
if args.get("inReplyTo"):
|
||||
body["inReplyTo"] = args["inReplyTo"]
|
||||
|
||||
raw = json.dumps(body).encode("utf-8")
|
||||
req = urlrequest.Request(
|
||||
f"{RELAY_URL}/msg",
|
||||
data=raw,
|
||||
method="POST",
|
||||
headers={
|
||||
"content-type": "application/json",
|
||||
"x-relay-agent": AGENT_ID,
|
||||
"x-relay-secret": SECRET,
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urlrequest.urlopen(req, timeout=15) as resp:
|
||||
return resp.read().decode("utf-8", errors="replace")
|
||||
except urlerror.HTTPError as e:
|
||||
detail = ""
|
||||
try:
|
||||
detail = e.read().decode("utf-8", errors="replace")[:500]
|
||||
except Exception:
|
||||
pass
|
||||
return tool_error(f"relay HTTP {e.code}", detail=detail)
|
||||
except Exception as e:
|
||||
return tool_error(f"relay request failed: {e}")
|
||||
|
||||
|
||||
def _check() -> bool:
|
||||
return bool(SECRET)
|
||||
Loading…
x
Reference in New Issue
Block a user