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