skill-send-to-agent/send_to_agent.py

132 lines
4.2 KiB
Python

"""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)