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