{"slug": "show-hn-a2acast-let-ai-agents-on-different-computers-work-together", "title": "Show HN: A2acast – Let AI agents on different computers work together", "summary": "A2acast, a new open-source tool by developer husker, enables AI agents on different computers to communicate directly without a central server, accounts, or open ports, using a single stdlib-only Python file and end-to-end encryption. It supports Claude Code, ChatGPT's Codex CLI, and GitHub Copilot, allowing them to exchange messages and A2A tasks across machines. The tool is available on PyPI and GitHub, with plugins for each agent harness.", "body_md": "**Messaging between AI agents on different machines — no server, no\naccounts, no open ports.** One stdlib-only Python file. End-to-end\nencrypted. Claude Code on a Linux laptop, ChatGPT (Codex CLI) on a MacBook,\nCopilot on a Windows PC — all exchanging messages and\n[A2A](https://a2a-protocol.org) tasks.\n\n**Machine A** — create the mesh:\n\n```\nuv tool install a2acast   # or: pipx install a2acast — it's on PyPI\nmesh init home   # prints a block to paste on machine B, then starts listening\n```\n\n(Bleeding edge instead: `pipx install git+https://github.com/husker/a2acast`\n\n.)\n\n**Machine B** — paste the block `mesh init`\n\nprinted. It looks like:\n\n```\ncurl -fsSLO https://raw.githubusercontent.com/husker/a2acast/vX.Y.Z/mesh.py\npython3 mesh.py join mesh1-XXXX...\n```\n\nThat's it: downloaded, joined (named after its hostname), listening.\nMachine A prints `MESH_NODE_JOINED`\n\nthe moment B joins.\n\n**Talk** (from a new terminal, on either machine):\n\n```\nmesh send all \"hello mesh\"     # B's watcher prints it about a second later\nmesh ping <b-name>             # → MESH_PONG node=<b-name> rtt=~400ms\nmesh ask <b-name> \"run the tests and summarize failures\" --wait 300\n```\n\nNo machine list to declare up front: **any machine with the join code can\njoin**, picks its own name, and every node learns about it automatically.\nShare the join code privately — it IS the mesh secret.\n\nInstall the plugin (teaches sessions the protocol and auto-reminds them when a project is a mesh node):\n\n```\n# Claude Code\n/plugin marketplace add husker/a2acast\n/plugin install a2acast\nmesh claude-setup             # once per project — arms presence at session start\n\n# Codex CLI / ChatGPT desktop\ncodex plugin marketplace add husker/a2acast\ncodex plugin add a2acast@a2acast\nmesh codex-setup              # once per machine — arms presence at session start\n\n# GitHub Copilot CLI\ncopilot plugin marketplace add husker/a2acast\ncopilot plugin install a2acast@a2acast\nmesh copilot-setup            # once per project — pins the watcher to this node\n```\n\nEach plugin loads the mesh safety rules at session start. Claude uses\nasynchronous `Stop`\n\nwith `asyncRewake`\n\n; Codex uses `Stop`\n\n. Copilot runs the\nwatcher as an **MCP server** (`mesh mcp-serve`\n\n, wired per project by\n`mesh copilot-setup`\n\n— see below) that Copilot starts with the session and\nstops when it ends — including Ctrl-C and\ncrash. Because it isn't an agent shell, the session shows no \"working\" spinner\nwhile it listens. When a message arrives the server wakes the idle session on\nits own (via MCP sampling) and the session handles it with the `mesh_pending`\n\n/ `mesh_reply`\n\n/ `mesh_send`\n\ntools — a real turn, so a `MESH_TASK`\n\ngets done,\nnot just acknowledged. (The first time, Copilot may ask once to approve the\nserver for sampling; approve it and later wakes run silently.)\n\nThe loop each session runs:\n\n- With the plugin, follow the harness-specific setup above. Claude and Codex\nneed no manual watcher; on Copilot, run\n`mesh copilot-setup`\n\nonce in the project (Copilot hands a plugin MCP server no project info and there's no portable way to guess it, so this pins the node in a workspace`.github/mcp.json`\n\n). After that its MCP-server watcher listens and wakes the session automatically — nothing to arm. Handling happens out of band (no \"working\" spinner); the next prompt you send opens with a one-line note of anything a2acast handled while you were away. - Do your work. After pushing something the other machine should act on:\n`mesh send <node> \"one-line summary — pull\"`\n\n. - When a\n`MESH_TASK`\n\nline arrives, do the work and answer with`mesh reply <task-id> \"<result>\"`\n\n.\n\nWorks with any number of nodes; each node has an inbox topic and `all`\n\nbroadcasts. `mesh claude-setup`\n\nregisters the a2acast presence watcher by\nwriting the project's `.mcp.json`\n\n— the CLAUDE.md protocol snippet itself\nnow lives at `mesh integrate --format claude`\n\n. (You still run `mesh init`\n\n/`mesh join`\n\nonce per machine either way: the plugin teaches sessions\nthe protocol, it doesn't create the mesh. On Copilot you also run `mesh copilot-setup`\n\nonce per project. The plugin's\nhooks and MCP server invoke the `mesh`\n\nCLI on your PATH — the same one\n`mesh init`\n\ninstalled — so it works the same on macOS, Linux, and Windows.\nOn Windows, private state relies on your account's ACLs rather than POSIX\nfile modes, and evidence files are opened with verified-identity checks in\nplace of kernel `O_NOFOLLOW`\n\n; the full test suite runs green on\nwindows-latest in CI. Keep it current: `pipx upgrade a2acast`\n\n(or `uv tool upgrade a2acast`\n\n) when you update the plugin.)\n\n**Autonomous Codex nodes (opt-in).** Claude and Copilot wake an idle session\nin-process to handle a `MESH_TASK`\n\n; Codex has no such path, so a joined Codex\nmachine can instead run a background **supervisor** that executes delegated\ntasks with no session open. Enable it with `mesh codex-setup --supervise`\n\n,\nthen name the peers you trust to run code on this machine with\n`mesh codex-allow <node>`\n\n. Nothing runs until you do both — the supervisor is\noff by default and its allowlist starts empty. That grant is keyed to the\nsender-name string, not a conversational session or, by itself, a verified\nnode key. See the security model below.\n\nThe worker pool runs repository tasks through local Codex, Copilot, and\nGoose/Ollama CLIs. Run it on a joined worker host only after all three CLIs are\ninstalled; authenticate Codex and Copilot for the current user, and start\nOllama with the configured model available (default `qwen3:4b`\n\n). The\ncoordinator must already be a current known mesh identity; confirm it with\n`mesh status`\n\nbefore setup.\n\n```\nmesh pool-setup --workspace-root ~/Projects \\\n  --coordinator jamess-macbook-air-2\nmesh pool-start\nmesh pool-status\nmesh delegate auto \"add a regression test\" --repo /abs/repo --wait 300\n```\n\n`pool-setup`\n\npermits repositories only below the listed workspace roots and\nsets `exec_allow`\n\nto the single named coordinator. It does not start a worker;\n`pool-start`\n\nis the explicit activation step. Because every mesh member has\nthe shared key and can assert any sender name, this allowlist is not per-node\ncryptographic identity. Configure only a coordinator you trust.\n\nOn macOS, `pool-start`\n\nmanages current-user LaunchAgents. On other operating\nsystems, `pool-start`\n\nand `pool-stop`\n\nprint foreground supervisor commands for\nyou or your service manager to run; they do not install a service.\n\nFor normal `auto`\n\njobs, dispatch selects the first eligible backend in\nGoose/Ollama, Copilot, then Codex order, skipping workers that are blocked,\nbusy, unavailable, or cooling down. Nonblocking CLI calls (the default\n`--wait 0`\n\n) and the nonblocking MCP tool dispatch that one worker and do not\nauto-redispatch. With a positive `--wait`\n\n, the CLI can try the next eligible\nbackend only after an authenticated `quota`\n\nor `unavailable`\n\nresult, within\nthe same total wait budget. Security and integration jobs select only Codex\nunless a backend is explicitly named.\n\nEach job runs in a separate Git worktree. A worktree prevents checkout collisions; a worktree is not a security sandbox. Worker processes still have the local user's OS permissions, and repository tasks are untrusted input. Results report an outcome, branch, commit, worktree, summary, and verification. A branch and commit contain proposed production changes; they are not integrated until you review and merge or cherry-pick them yourself.\n\nThe pool creates worktrees and local commits. Its worker instructions forbid merge, push, PR, deploy, publish, and worktree deletion, and a2acast performs none of those as automatic postprocessing. Cleanup is an explicit command:\n\n```\nmesh pool-clean --integrated-into main\nmesh pool-stop\n```\n\nNormal cleanup removes only terminal, clean worktrees with consistent durable\nrecords whose commits are integrated into the named ref; uncertain or\nunintegrated work is preserved. `mesh pool-clean --task <id> --force`\n\nis an\nexplicit escape hatch for exactly one terminal task and may discard its\nunintegrated or dirty worktree.\n\nOne file, `mesh.py`\n\n, Python stdlib only. Messages travel through an\n[ntfy](https://ntfy.sh) relay (default: the public ntfy.sh; self-host with\n`mesh init --server`\n\n) over **outbound-only HTTPS connections on both ends**\n— which is why two laptops behind NAT can talk with no port forwarding, no\nVPN, and no server of yours. Topics are derived from the mesh secret and\nthe node name, so nothing is ever registered anywhere; delivery latency is\nabout a second. `mesh watch --follow`\n\nholds one streaming connection and\nprints each message as it lands.\n\nIn a terminal, `init`\n\nand `join`\n\nflow straight into that watcher when they\nfinish — programs calling mesh (scripts, agent harnesses; anything without\na TTY) get the plain return-immediately behavior instead.\n\n**Message durability depends on size.** Messages under the relay's ~4 KB\ninline limit are cached with normal retention; larger payloads ride as\nrelay *attachments* with a **~3 hour TTL** — a node offline past that\nwindow still gets the wake, but the content is gone and the sender must\nresend (`mesh`\n\nwarns on both ends when this can happen). For bulk that\nmust survive long offline windows, keep payloads small or use a durable\nchannel such as a shared repo (#66 tracks a chunking fix).\n\nNodes don't just ping each other — they exchange real\n[A2A protocol](https://a2a-protocol.org) tasks in JSON-RPC envelopes:\n\n```\nmesh ask desktop \"run the test suite and summarize failures\" --wait 300\n# → MESH_TASK_RESULT from=desktop state=completed: 2 failures, both in auth...\n\n# on the receiving side (its agent sees this via `mesh watch --follow`):\n# MESH_TASK from=laptop task=5e52304e... state=submitted: run the test suite...\nmesh reply 5e52304e \"2 failures, both in auth: ...\"\n\nmesh tasks             # ledger of everything asked/answered\nmesh card desktop      # its A2A agent card\n```\n\nAnd because the wire format is real A2A, `mesh a2a-serve`\n\nruns a\n**localhost bridge** so any A2A-capable framework (LangGraph, Google ADK,\nMicrosoft Agent Framework, …) can talk to remote mesh nodes as ordinary A2A\nservers — discovery via agent cards, `message/send`\n\n, `tasks/get`\n\n:\n\n```\nmesh a2a-serve         # → http://127.0.0.1:4737/agents/<node> per remote node\n```\n\nSee [docs/AGENTS.md](/husker/a2acast/blob/main/docs/AGENTS.md) for per-harness wiring (Codex CLI,\nCopilot CLI, Gemini CLI, A2A frameworks, cron). By default, `mesh`\n\nonly moves\nmessages; the explicitly started worker pool is the exception and invokes its\nconfigured local CLI. Interactive nodes answer with whatever brain, tools,\nand permissions their own harness has.\n\n**Messages are end-to-end encrypted and authenticated.** The mesh key is\ngenerated by `mesh init`\n\n, lives only in `.meshwire.json`\n\non your machines,\nand travels only inside join codes you share yourself. On the wire, the\nrelay (and anyone who discovers a topic) sees ciphertext, topic id, size,\nand timing — nothing else. Sender and recipient names ride *inside* the\nciphertext.\n\nConstruction (stdlib-only, standard primitives): HKDF-SHA256 key derivation\n→ HMAC-SHA256 PRF in counter mode for encryption, encrypt-then-MAC with an\nindependent HMAC-SHA256 key, random 128-bit nonce per message, and\nconstant-time tag comparison. The `mw2`\n\nauthentication tag also binds the\nmesh id, exact relay topic, and send timestamp. Envelopes older than seven\ndays (or implausibly far in the future) are rejected, and authenticated\nciphertext fingerprints are persisted per node so duplicate relay deliveries\nand replays are suppressed across restarts. `mw1`\n\nremains readable during\nrolling upgrades but new sends always use `mw2`\n\n. Unauthenticated, stale,\nmisrouted, replayed, or tampered messages are **dropped, not displayed**.\n\nWhat you still must do:\n\n**Guard the join code and**— they contain the key. Both are auto-gitignored; config writes use mode`.meshwire.json`\n\n`0600`\n\n; share join codes over a private channel. The key is never placed in an environment variable:`A2ACAST_CONFIG`\n\ncontains only a path. MCP tools, agent cards, and the local A2A HTTP bridge never return config or key fields.- A join code grants\n**full membership in one trust domain**: its holder can decrypt traffic, publish mesh-authenticated traffic, and assert the same sender names as any other member at the shared-key layer. Node signatures add a separate per-name verdict against the receiver's local pin, but first contact is unverified and verdict enforcement is a default-off rollout control. Delivery alone therefore does not prove a node identity. **Treat inbound tasks as untrusted input.** Encryption authenticates*the mesh*, not intent: any agent (or person) holding the key can send tasks. Receiving agents should apply their normal permission rules.**A node signature identifies a node/harness identity, not a conversational session.** Agent integrations scope their private key per harness. Concurrent sessions of the same harness using the same mesh configuration therefore use the same per-harness key path and normally share one node name.`--as`\n\nor`A2ACAST_NODE`\n\ncan override the name, but not separate the shared key path. A receiver cannot tell which session produced a valid frame. Even a`FRAME_VERIFIED`\n\nresult proves possession of that node key; it does not prove which local agent or user authorized the message.**Autonomous Codex execution is opt-in and gated on a curated allowlist.**`mesh codex-supervise`\n\nruns delegated tasks through`codex exec`\n\nand replies over the mesh. It stays off unless you start it (`codex-setup --supervise`\n\n), and even then only peers you add with`mesh codex-allow`\n\nrun — a default-empty allowlist (`exec_allow`\n\n),**not** the roster of everyone who holds the key.`mesh codex-allow <node>`\n\ngrants**sender-name-wide legacy authority**: every accepted inbound task whose persisted sender-name string matches that entry is eligible. It is not a grant to one chat or agent session, and under default-off or unsettled node verification it is**not a per-node cryptographic grant**. A settled pin plus verdict enforcement can refuse a mismatched key, but first contact remains unverified and accepted. The`--sandbox read-only`\n\ndefault is defense-in-depth, not the boundary: a read-only task can still read repo secrets and return them in its reply, so only allow peers you actually trust.**Hard separation between co-located agents requires separate OS accounts or sandboxes**, with distinct mesh identities and private keys protected from one another. Separate processes, temporary key directories, and Git worktrees do not contain a malicious process running as the same OS user. They may improve routing or collision isolation, but must not be treated as an authorization boundary.- Someone who learns a topic id (but not the key) can't read or forge\nmessages, but can post garbage that your watcher silently drops.\nSelf-hosting ntfy with auth (\n`mesh init --server https://ntfy.example`\n\n) closes even that. - Upgrade all machines together when moving to a new a2acast version — it's one file. (v0.4 meshes interoperate; v0.4 clients just render the new join/ping control messages as odd one-off messages.)\n\nIf a join code, config, or member machine is compromised, rotate both the key and topic capability from one trusted node:\n\n```\nmesh rotate-key\n```\n\nThat command switches new commands on the current node to a fresh key and\nfresh topic id, then prints a private `mesh rotate-key mesh1-...`\n\ncommand. Run\nthat exact command on every remaining trusted node over a private channel. Do\nnot send it through the compromised mesh. Restart long-running `mesh watch`\n\n,\nMCP/harness, and supervisor processes on each node so they drop their in-memory\ncopy of the old config. Nodes that have not applied the new code remain on the\nrevoked mesh and cannot read or publish on the new topics. Compare the\n`key: sha256:...`\n\nline from `mesh status`\n\nout of band to confirm every trusted\nnode completed the cutover.\n\n```\nmesh init <name> [--as NODE] [--server URL]   create a mesh; in a terminal, prints the invite block and keeps listening\nmesh join <code> [--as NODE]   join from a code, announce, and (in a terminal) keep listening\nmesh invite                    print the join code + paste-able bootstrap block\nmesh rotate-key [mesh1-code]   rotate the key/topics, or apply a peer rotation\nmesh iam <node>                set/change this machine's identity\nmesh send <node|all> <msg...> [--intent request|inform|ack] [--reply-to ID]\n                               message a node (or broadcast) with reply intent\nmesh presence listening|working|blocked   set and broadcast agent status\nmesh watch --follow            stream messages forever (preferred; background task)\nmesh watch [--timeout N]       one-shot: block until one message, print, exit\nmesh ping <node> [--timeout N] liveness + round-trip time (answered by watchers)\nmesh ask <node> <text...> [--wait SECS]   delegate an A2A task\nmesh run ensemble [--timeout N] -- \"<prompt>\"   fan out and collate replies\nmesh run cross-review [--timeout N] -- <diff-or-ref>   two independent reviews\nmesh reply <task-id> <text...> [--state completed|failed|...]   answer one\nmesh tasks [get <id>]          task ledger\nmesh tasks --wait <id> [--timeout N]   wait for a terminal task result\nmesh card [node] [--name N --description D]   A2A agent card\nmesh a2a-serve [--port 4737] [--wait 60]      localhost A2A HTTP bridge\nmesh peek [node] [--since S]   show recent messages without consuming\nmesh peek --wait [--from NODE] [--timeout N]   wait for the next arrival\nmesh status                    mesh, identity, known peers + last seen\nmesh fleet [--as NODE]         read-only fleet receiver-health table\nmesh integrate [--format codex|copilot|claude|mcp|skill]   print setup for a harness/route\nmesh mcp [--config PATH]       stdio MCP tool server for any MCP client (Claude Desktop, Cursor, …)\nmesh claude-setup              register the Claude Code presence watcher (writes .mcp.json)\nmesh codex-setup [--supervise] arm Codex presence; --supervise also launches the autonomous actor\nmesh copilot-setup             register the Copilot CLI presence watcher\nmesh codex-allow <node> [--revoke|--list]   trust (or untrust) a peer to run delegated tasks here\nmesh codex-supervise [--once] [--sandbox S] [--interval N] [--stop]   the autonomous task actor\nmesh pool-setup --workspace-root DIR --coordinator NODE [--model MODEL]   configure the worker pool\nmesh pool-start|pool-status|pool-stop   manage or inspect worker supervisors\nmesh delegate auto|codex|copilot|goose <task...> --repo ABS [--base COMMIT]\n  [--kind implementation|analysis] [--class normal|security|integration]\n  [--verify TEXT (repeatable)] [--wait 0..300] [--as NODE]   run an isolated task\nmesh pool-clean [--integrated-into REF] [--task ID [--force]]   remove eligible worktrees\n```\n\nThe blocking commands use shell-friendly exit codes: `tasks --wait`\n\nexits\n`0`\n\nfor `completed`\n\n, `1`\n\nfor any other terminal state, and `124`\n\non timeout;\n`peek --wait`\n\nexits `0`\n\non an arrival and `124`\n\non timeout.\n\n`mesh run ensemble`\n\ncreates one correlated task per other node in the dynamic\nroster, waits for all terminal replies or the time window, then prints each\nanswer plus a no-reply list. `mesh run cross-review`\n\nprefers two available\npeers (blocked nodes rank last), gives both the same independent review brief,\nand collates their findings. Recipes exit `0`\n\nwhen every task completes, `1`\n\nfor dispatch or terminal failures, and `124`\n\nwhen any dispatched node does not\nreply before the timeout.\n\nNew messages carry a stable message id and default to `inform`\n\nwhen intent is\nmissing (including messages from older clients). Use `request`\n\nwhen a reply is\nrequired, `inform`\n\nfor FYI traffic, and `ack`\n\nto close a loop; `--reply-to`\n\ncorrelates a response to the displayed message id. Agent integrations follow a\nfixed rule: always answer `request`\n\n, answer `inform`\n\nonly when it adds value,\nnever answer `ack`\n\n, and do not send filler greetings or thanks.\n\nPresence controls carry `listening`\n\n, `working`\n\n, or `blocked`\n\nthrough normal\nannounce/ping/pong/ack traffic and explicit `mesh presence`\n\nbeacons. Session\nand stop hooks maintain `working`\n\n/`listening`\n\n; approval integrations can run\n`mesh presence blocked`\n\nwhile waiting for permission. `mesh status`\n\nand\n`mesh_list_agents`\n\nshow the latest reported state, and `mesh ask`\n\nwarns before\nsending to a blocked node.\n\nA healthy-looking signal proves only the property it measured. Do not promote one positive observation into a broader liveness or migration claim:\n\n| Signal | What it proves | What it does not prove |\n|---|---|---|\nFresh receiver heartbeat / `healthy` |\nThat receive process handled a frame recently | That the node can publish a reply |\n`unknown` health after five quiet minutes |\nNo fresh self-report is available | That the node is dead; a quiet healthy node ages to `unknown` |\n`mesh status` shows the new relay |\nA new command read the updated config file | That an already-running watcher or MCP server reloaded it |\n| A send was accepted by the relay | The relay accepted the publication | That the intended listener is subscribed there |\n\nUse `mesh ping <node>`\n\nwhen you need end-to-end evidence: a matching pong proves\nthe target heard the ping and successfully published a reply through the route\nthe caller is currently using. After any `.meshwire.json`\n\nserver or key change,\nrestart long-lived receivers (`mesh watch`\n\n, `mcp-serve`\n\n/ MCP hosts,\n`codex-supervise`\n\n, and pool workers); transient lifecycle-hook receivers reload\nconfig on their next invocation.\n\nFor a relay migration, verify the old connection is actually gone rather than\ntrusting `mesh status`\n\n. On POSIX, capture or resolve the old relay addresses and\ncheck the process table; the following should print no matching socket after\nall long-lived receivers have been cycled:\n\n```\nold_host=old-relay.example\nold_ips=\"$(dig +short A \"$old_host\"; dig +short AAAA \"$old_host\")\"\ntest -n \"$old_ips\" || { echo \"could not resolve $old_host\" >&2; exit 1; }\nold_sockets=0\nfor old_ip in $old_ips; do\n  if lsof -nP -iTCP | grep -F -- \"$old_ip:\"; then old_sockets=1; fi\ndone\ntest \"$old_sockets\" -eq 0\n```\n\nUse `Resolve-DnsName`\n\nplus `Get-NetTCPConnection`\n\nfor the equivalent Windows\ncheck. During the cutover, keep private old- and new-relay config files and\npublish coordination messages once through each (`A2ACAST_CONFIG=/path/to/...`\n\n)\nuntil every node has been cycled and passes a ping on the new relay. Config files\ncontain the mesh key; protect both copies and remove the old one after the\nmigration is verified.\n\nTo set a stable node name use `mesh iam <name>`\n\n(writes a per-harness pin).\nPrefer this over the `A2ACAST_NODE`\n\nenv var, which is not reliably inherited\nby harness-spawned processes.\n\nTo keep the mesh key outside a project, point `A2ACAST_CONFIG`\n\nat an existing\nconfig file. The explicit path takes precedence over ancestor discovery and is\ninherited by lifecycle hooks:\n\n```\nexport A2ACAST_CONFIG=/absolute/path/to/mesh-node/.meshwire.json\n# PowerShell: $env:A2ACAST_CONFIG = 'C:\\path\\to\\mesh-node\\.meshwire.json'\n```\n\n`mesh claude-setup`\n\nand `mesh copilot-setup`\n\nstill write their MCP workspace\nfiles in the current project while pinning that isolated config path.\n\nTo select a specific OpenSSH binary, set `A2ACAST_SSH_KEYGEN`\n\nto its path.\nThis is useful on Windows builds where the in-box OpenSSH 9.5p2\n`ssh-keygen -Y sign`\n\ncan take about one minute and race a2acast's signing\ntimeout. For example:\n\n```\n$env:A2ACAST_SSH_KEYGEN = 'C:\\Program Files\\Git\\usr\\bin\\ssh-keygen.exe'\n```\n\nSet the same variable in an MCP server's `env`\n\nblock for long-running\nintegrations. The override drives both signing and verification; an invalid\nvalue fails loudly and never falls back to the slower PATH binary.\n\n**Onboarding & MCP clients.** `mesh integrate`\n\nprints the right setup for\nwhatever you run — a harness plugin (`--format codex`\n\n/`copilot`\n\n), a CLAUDE.md\nsnippet (`--format claude`\n\n), a paste-in skill (`--format skill`\n\n), or the MCP\nconfig (`--format mcp`\n\n). For GUI/desktop agents, `mesh mcp`\n\nruns a stdio MCP\n**tool** server (add it with `mesh integrate --format mcp`\n\n) exposing\n`mesh_send`\n\n/ `mesh_pending`\n\n/ `mesh_ask`\n\n/ `mesh_reply`\n\n/ `mesh_delegate`\n\n/\n`mesh_list_agents`\n\n, so Claude Desktop, Cursor, or any MCP host can talk to\nthe mesh — no plugin needed. (This is the pull-mode tool server; the Copilot plugin's `mcp-serve`\n\nis the push-mode watcher that wakes an idle session.)\n\n| a2acast | shared MCP queue | SSH + headless agent | plain git polling | |\n|---|---|---|---|---|\n| Infrastructure | none | server to run | SSH + reachable host | none |\n| Wake latency | ~1 s | poll interval | seconds | poll interval |\n| Payload channel | message or your repo | the queue | the SSH pipe | your repo |\n| Audit trail | git history | custom | none | git history |\n| N nodes | yes | yes | pairwise | yes |\n\nMIT", "url": "https://wpnews.pro/news/show-hn-a2acast-let-ai-agents-on-different-computers-work-together", "canonical_source": "https://github.com/husker/a2acast", "published_at": "2026-08-28 22:12:35+00:00", "updated_at": "2026-08-28 22:18:57.118910+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "ai-infrastructure", "artificial-intelligence"], "entities": ["A2acast", "husker", "Claude Code", "Codex CLI", "GitHub Copilot", "PyPI", "GitHub", "A2A"], "alternates": {"html": "https://wpnews.pro/news/show-hn-a2acast-let-ai-agents-on-different-computers-work-together", "markdown": "https://wpnews.pro/news/show-hn-a2acast-let-ai-agents-on-different-computers-work-together.md", "text": "https://wpnews.pro/news/show-hn-a2acast-let-ai-agents-on-different-computers-work-together.txt", "jsonld": "https://wpnews.pro/news/show-hn-a2acast-let-ai-agents-on-different-computers-work-together.jsonld"}}