scripts._irc_send

Reference › scripts › _irc_send

Sends one message to the #agents IRC channel and, on a best-effort basis, captures it as a SPEC-021 conversation message through _irc_conversation. It is the sending counterpart of _irc_listen. No test imports it, and it has no command-line parser.

send_message takes the text, and optionally the host, port, nick, channel and environment. The nick defaults to the IRC_NICK variable (else claude) with an -out suffix, because the persistent listener already holds the bare nick and the server would reject a colliding registration, which would swallow the message. It opens a connection (connect), registers, joins the channel, sends the message and quits (send, read). Only after the send does it look up the current listener session in Redis and enqueue the text as a user message from the irc platform; if no listener session is active it skips capture and says so on standard error, so capture never delays or blocks the send.

At a glance

Source scripts/_irc_send.py
Lines of code 109
Classes 0
Functions 4
Imports (internal) none
Imported by none
Tests that import it none

Class diagram

No classes; see Functions.

Dependency diagram

No internal imports and no importers.

API

Functions

Function Signature Summary
connect connect(host: str = HOST, port: int = PORT) -> socket.socket
send send(sock: socket.socket, msg: str) -> None
read read(sock: socket.socket) -> None
send_message send_message(text: str, *, host: str = HOST, port: int = PORT, nick: str | None = None, chan: str = '#agents', env: dict[str, str] | None = None, queue_key: str = 'orchestrata:conversation:queue') -> None Send text as a PRIVMSG to chan, then (best-effort) capture it.

Source

Permalink: https://gitlab.com/MSamy/orchestrata-ultimate-architecture/-/blob/a4cbc2bdf/scripts/_irc_send.py

"""Send one message to #agents and (best-effort) capture it as a SPEC-021
conversation turn.

Per plans/spec-021-multiplatform-producers.md § "kimi/IRC producer": this
sender reads the CURRENT session (allocated by ``_irc_listen.py`` at
connect+join) via ``_irc_conversation.read_current_session_seq`` so an
outbound line lands in the SAME conversation the fleet's replies are being
captured under. If no listener session is active (key absent, or Redis is
unreachable), the IRC line is still SENT — delegation traffic must never be
blocked by conversation capture — and capture is skipped with a message to
stderr.
"""

import os
import socket
import sys
import time

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import _irc_conversation as convo  # noqa: E402

HOST, PORT = "127.0.0.1", 6667


def connect(host: str = HOST, port: int = PORT) -> socket.socket:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    s.settimeout(2.0)
    return s


def send(sock: socket.socket, msg: str) -> None:
    sock.sendall((msg + "\r\n").encode())


def read(sock: socket.socket) -> None:
    try:
        data = sock.recv(4096)
        for line in data.decode("utf-8", errors="replace").split("\r\n"):
            if line:
                print(line)
    except OSError:
        # fire-and-forget console reader: a recv timeout/reset is expected and
        # irrelevant to this one-shot sender — nothing to recover.
        pass


def send_message(
    text: str,
    *,
    host: str = HOST,
    port: int = PORT,
    nick: str | None = None,
    chan: str = "#agents",
    env: dict[str, str] | None = None,
    queue_key: str = "orchestrata:conversation:queue",
) -> None:
    """Send ``text`` as a PRIVMSG to ``chan``, then (best-effort) capture it.

    The IRC send always happens first and unconditionally; capture (which
    depends on a live listener session in Redis) is attempted only after,
    and never delays or blocks the send.

    ``nick`` defaults to ``IRC_NICK`` from the environment (fleet identity)
    with an ``-out`` suffix — the persistent listener already holds the bare
    nick, and miniircd rejects a colliding registration (433), which silently
    swallows the PRIVMSG. A distinct one-shot nick keeps the send deliverable
    while still attributable to the owning agent.
    """
    if nick is None:
        base = (env if env is not None else os.environ).get("IRC_NICK", "claude")
        nick = f"{base}-out"
    s = connect(host, port)
    send(s, f"NICK {nick}")
    send(s, f"USER {nick} 0 * :Done")
    time.sleep(0.3)
    read(s)
    send(s, f"JOIN {chan}")
    time.sleep(0.3)
    read(s)
    send(s, f"PRIVMSG {chan} :{text}")
    time.sleep(0.5)
    read(s)
    send(s, "QUIT :done")
    s.close()

    session_seq = convo.read_current_session_seq(env=env)
    if session_seq is None:
        print(
            "[irc-send] capture skipped: no active kimi listener session",
            file=sys.stderr,
        )
        return
    convo.enqueue_message(
        role="user",
        platform="irc",
        content=text,
        session_seq=session_seq,
        conversation_id=convo.conversation_id_for(session_seq),
        env=env,
        queue_key=queue_key,
    )


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("usage: _irc_send.py <message ...>", file=sys.stderr)
        raise SystemExit(2)
    send_message(" ".join(sys.argv[1:]))