packages feed

pms-infra-agent-socket 0.0.1.0 → 0.0.2.0

raw patch · 6 files changed

+170/−17 lines, 6 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- PMS.Infra.Agent.Socket.DS.Utility: hexToBytes :: String -> Either String ByteString

Files

CHANGELOG.md view
@@ -1,5 +1,9 @@ # Revision history for pms-infra-agent-socket +## 0.0.2.0 -- 2026-06-15++* Added pms-infra-agent-server: TCP server listen/accept functionality for AI agents.+ ## 0.0.1.0 -- 2026-05-31  * First version.
README.md view
@@ -65,6 +65,134 @@  ## 💡 Usage Notes +### AI-to-AI communication++Any two AI agents that each have a `pty-mcp-server` instance can communicate with each other directly over TCP — no shared infrastructure, message broker, or custom integration required.++One agent uses the `agent-server-*` tools to act as a TCP server; the other uses the `agent-socket-*` tools (this library) to act as a TCP client. The agents exchange messages using a lightweight text protocol, and each agent interprets and responds to the other's messages autonomously.++```+┌─────────────────────────────┐         ┌─────────────────────────────┐+│        AI Agent A           │         │        AI Agent B           │+│  (e.g. Claude on Machine A) │         │  (e.g. GPT on Machine B)    │+│                             │         │                             │+│  pty-mcp-server             │  TCP    │  pty-mcp-server             │+│  agent-server-listen :19999 │◄───────►│  agent-socket-open          │+│  agent-server-write         │         │  agent-socket-read          │+│  agent-server-events        │         │  agent-socket-write         │+└─────────────────────────────┘         └─────────────────────────────┘+         Server role                              Client role+```++#### What this enables++- **Collaborative task solving** — Agent A breaks down a problem and delegates subtasks to Agent B, collecting results over the socket.+- **Cross-model review** — One model generates code or text; another model on a different machine reviews it and sends back comments.+- **Heterogeneous agent pipelines** — Chain agents of different models or specialisations (Claude, GPT, Gemini, …) into a processing pipeline across machines.+- **Autonomous negotiation** — Agents can exchange proposals, counter-proposals, and decisions without human involvement in each round-trip.+- **Distributed tool use** — Agent B may have access to tools (databases, sensors, local files) that Agent A does not. Agent A requests operations from Agent B over the socket.++#### Communication protocol++The `pty-mcp-server` ecosystem ships a lightweight handshake and messaging protocol for AI-to-AI sessions. Prompt skills (`skill_agent_server.md` / `skill_agent_client.md`) are provided so each agent knows exactly how to play its role.++**Handshake sequence:**+```+Server → Client : HELLO? name?\r\n+Server ← Client : NAME: <name>\r\n+Server → Client : RULES: MSG:<content>\r\n | REPLY:<content>\r\n | BYE\r\n | HEX:<hex>\r\n+Server ← Client : ACK\r\n+```++**Conversation:**+```+Server → Client : MSG: <content>\r\n+Server ← Client : REPLY: <content>\r\n+```++**Graceful shutdown (server-initiated):**+```+Server → Client : BYE\r\n+Server ← Client : ACK\r\n    ← server waits for this before closing+Server           : agent-server-close+```++#### Full session example++The following shows a complete exchange where Agent A (server role) delegates a task to Agent B (client role).++**Agent A — server side**+```+agent-server-listen host=172.16.0.43 port=19999+  → "listening."++--- Agent B connects ---++agent-server-events+  → [{ "tag": "ClientConnected" }]++agent-server-write-byte  "48454C4C4F3F206E616D653F0D0A"   ("HELLO? name?\r\n")++agent-server-events  (poll until BytesReceived)+  → bytes: "4E414D453A20416765742D420D0A"   ("NAME: Agent-B\r\n")++agent-server-write-byte  "<hex of RULES: MSG:... | REPLY:... | BYE\r\n>"++agent-server-events  (poll for ACK)+  → bytes: "41434B0D0A"   ("ACK\r\n")++--- handshake complete ---++agent-server-write-byte  "<hex of MSG: Please summarise this text: ...\r\n>"++agent-server-events  (poll for REPLY)+  → bytes: "<hex of REPLY: Here is the summary: ...\r\n>"++agent-server-write-byte  "<hex of BYE\r\n>"++agent-server-events  (poll for ACK)+  → bytes: "41434B0D0A"   ("ACK\r\n")++agent-server-close+```++**Agent B — client side**+```+agent-socket-open host=172.16.0.43 port=19999+  → socket connected to 172.16.0.43:19999++agent-socket-read length=256+  → "HELLO? name?\r\n"++agent-socket-write-byte  "<hex of NAME: Agent-B\r\n>"++agent-socket-read length=256+  → "RULES: MSG:<content>\r\n | REPLY:<content>\r\n | BYE\r\n | HEX:<hex>\r\n"++agent-socket-write-byte  "<hex of ACK\r\n>"++--- handshake complete ---++agent-socket-read length=1024+  → "MSG: Please summarise this text: ...\r\n"++--- Agent B processes the request autonomously ---++agent-socket-write-byte  "<hex of REPLY: Here is the summary: ...\r\n>"++agent-socket-read length=256+  → "BYE\r\n"++agent-socket-write-byte  "<hex of ACK\r\n>"++agent-socket-close+```++> 💡 Always use `agent-socket-write-byte` (not `agent-socket-write`) to ensure `\r\n` is sent as correct CRLF bytes.  +> Generate hex strings with: `python3 -c "print('your message\r\n'.encode().hex())"`++---+ ### IAC / Telnet negotiation Telnet IAC processing is the **responsibility of the agent**, not the library.   Use `agent-socket-read-byte` and `agent-socket-write-byte` to handle IAC sequences at the byte level.@@ -78,6 +206,39 @@ ← "localhost login: "         (Login prompt — read as UTF-8 string) → "phoityne\r\n"              (Username — send as bytes: 70686F6974796E650D0A) ← "Password: "                (Password prompt)+```++Full login session example (confirmed with a real Linux Telnet server):+```+agent-socket-open host=172.16.0.171 port=23+  → socket connected to 172.16.0.171:23++← FFFD18FFFD20FFFD23FFFD27+    (IAC DO TERMINAL-TYPE / DO TERMINAL-SPEED / DO X-DISPLAY / DO NEW-ENVIRON)+→ FFFC18FFFC20FFFC23FFFC27+    (IAC WONT x4)++← FFFB03FFFD01FFFD1FFFFB05FFFD21+    (IAC WILL SGA / DO ECHO / DO NAWS / WILL STATUS / DO REMOTE-FLOW-CONTROL)+→ FFFD03FFFB01FFFC1FFFFD05FFFC21+    (IAC DO SGA / WILL ECHO / WONT NAWS / DO STATUS / WONT REMOTE-FLOW-CONTROL)++← FFFE01FFFB01 + "Kernel 6.12.0-...\r\nlocalhost login: "+→ 61692D6167656E740D0A   ("ai-agent\r\n"  — username as bytes)++← "ai-agent\r\nPassword: "+→ 61692D6167656E740D0A   ("ai-agent\r\n"  — password as bytes)++← "Last login: ...\r\n[ai-agent@localhost ~]$ "++→ 686F73746E616D650D0A   ("hostname\r\n")+← "hostname\r\nlocalhost.localdomain\r\n[ai-agent@localhost ~]$ "++→ 657869740D0A   ("exit\r\n")+← "exit\r\nlogout\r\n"++agent-socket-close+  → socket is closed. ```  ### Unix Domain Socket on Windows
pms-infra-agent-socket.cabal view
@@ -20,7 +20,7 @@ -- PVP summary:     +-+------- breaking API changes --                  | | +----- non-breaking API additions --                  | | | +--- code changes with no API change-version:            0.0.1.0+version:            0.0.2.0  -- A short (one-line) description of the package. synopsis:           pms-infra-agent-socket
src/PMS/Infra/Agent/Socket/DS/Core.hs view
@@ -374,7 +374,7 @@   handleTMVar <- view handleAppData <$> ask   argsDat     <- liftEither $ eitherDecode argsBS   let hex = argsDat^.dataSocketWriteByteToolParams-  bs          <- liftEither $ hexToBytes hex+  bs          <- liftEither $ DM.hexToBytes hex   return $ socketWriteByteTask cmdData resQ handleTMVar bs  -- |
src/PMS/Infra/Agent/Socket/DS/Utility.hs view
@@ -12,10 +12,6 @@ import Control.Monad.Reader import qualified Control.Concurrent.STM as STM import Network.Socket-import qualified Data.ByteString as BS-import qualified Data.ByteString.Base16 as B16-import qualified Data.ByteString.Char8 as C8-import Data.Char (toUpper)  import qualified PMS.Domain.Model.DM.Type as DM import qualified PMS.Domain.Model.DS.Utility as DM@@ -58,15 +54,6 @@   liftIOE $ STM.atomically $ STM.writeTQueue resQ res  ------------------------------------------------------------------------------------ | Decode an uppercase/lowercase hex string to a ByteString.--- Input is uppercased before decoding to tolerate mixed case (e.g. "000a1BFF").--- Returns Left with an error message if the input is not valid hex.-hexToBytes :: String -> Either String BS.ByteString-hexToBytes hex =-  case B16.decode (C8.pack (map toUpper hex)) of-    Right bs  -> Right bs-    Left  err -> Left $ "hexToBytes: invalid hex string: " ++ err- -- | Convert a TCP host/port to a Handle. -- The socket must not be used directly after socketToHandle. createSocketHandle :: HostName -> ServiceName -> IO Handle
test/PMS/Infra/Agent/Socket/App/ControlSpec.hs view
@@ -20,6 +20,7 @@ import qualified System.Log.FastLogger as FastLogger  import qualified PMS.Domain.Model.DM.Type as DM+import qualified PMS.Domain.Model.DS.Utility as DS import qualified PMS.Infra.Agent.Socket.App.Control as SUT import qualified PMS.Infra.Agent.Socket.DM.Type as SUT import qualified PMS.Infra.Agent.Socket.DS.Core as Core@@ -250,7 +251,7 @@                        { DM._jsonrpcAgentSocketWriteByteCommandData   = mkJsonRpc "agent-socket-write-byte"                        , DM._argumentsAgentSocketWriteByteCommandData = DM.RawJsonByteString ""                        }-        Right bs <- return $ Util.hexToBytes hex+        Right bs <- return $ DS.hexToBytes hex         withConnectedPair $ \clientHdl serverSock -> do           _ <- STM.atomically $ STM.swapTMVar (appDat^.SUT.handleAppData) (Just clientHdl)           Core.socketWriteByteTask cmdDat@@ -273,7 +274,7 @@                        , DM._argumentsAgentSocketWriteByteCommandData = DM.RawJsonByteString ""                        }         mapM_ (\hex -> do-          Right bs <- return $ Util.hexToBytes hex+          Right bs <- return $ DS.hexToBytes hex           appDat' <- SUT.defaultAppData           withConnectedPair $ \clientHdl serverSock -> do             _ <- STM.atomically $ STM.swapTMVar (appDat'^.SUT.handleAppData) (Just clientHdl)