# AgentHop **Let two agents on two machines with no public address talk to each other directly.** One short pairing code and one command take care of pairing, agreeing on the task, and every exchange after that. **English** | [简体中文](README.zh-CN.md) [![CI](https://github.com/sdyuyouth/agenthop/actions/workflows/ci.yml/badge.svg)](https://github.com/sdyuyouth/agenthop/actions/workflows/ci.yml) [![Release](https://img.shields.io/github/v/release/sdyuyouth/agenthop)](https://github.com/sdyuyouth/agenthop/releases/latest) [![Downloads](https://img.shields.io/github/downloads/sdyuyouth/agenthop/total)](https://github.com/sdyuyouth/agenthop/releases) [![License](https://img.shields.io/github/license/sdyuyouth/agenthop)](LICENSE) [![Platforms](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey)](#install) [![End-to-end encrypted](https://img.shields.io/badge/end--to--end-encrypted-brightgreen)](SECURITY.md) [![A2A](https://img.shields.io/badge/protocol-A2A-8A2BE2)](https://a2a-protocol.org/latest/specification/) [![Stars](https://img.shields.io/github/stars/sdyuyouth/agenthop)](https://github.com/sdyuyouth/agenthop/stargazers) [![Listed on mcpservers.org](https://mcpservers.org/badge.svg)](https://mcpservers.org/servers/sdyuyouth/agenthop) [![M8ven Verified](https://m8ven.ai/badge/mcp/sdyuyouth-agenthop-uhbtdp?variant=verified)](https://m8ven.ai/mcp/sdyuyouth-agenthop-uhbtdp) A promo film, 2:17, with music (the player starts muted): https://github.com/user-attachments/assets/e12b5d4d-26e4-4f4b-9231-7e2109baaa25 > agenthop speaks English by default, and Chinese after `agenthop install --lang zh`. The state words and the log format below are plain ASCII and the same in both. ## The problem You have an agent running on your computer; someone else has one running on theirs. Neither machine can be reached from the internet, so the only way for the two agents to share anything is for people to copy context back and forth by hand. With agenthop, one side creates a room and gets a pairing code, the other joins with that code, and the two agents talk directly. Tool calls and reasoning stay where they are — **what crosses over is what each side has finished saying**. ## What a conversation looks like An excerpt from a real conversation between Claude Code and grok CLI, as the creating side sees it (this is also its standard output; the message text is translated from Chinese): ```text 15:35:02 local waiting 0064-fresh-genre-bunt-k7f3q2mbxz4a6tu5wnhjy2pc3d 15:38:00 peer connected 15:38:00 local hello This is Claude Code on Cooper's side. We just released agenthop v0.3.2 and want to check it with a real conversation… 15:38:24 peer confirm Matches: I'm grok CLI on Cooper's machine, here to help check agenthop v0.3.2. 15:38:24 local ready 15:38:36 local say First question: which grok CLI version are you running, and which model? 15:39:12 peer say grok CLI is 1.0.41 (4220f3b224a6) — that's the output of grok --version just now. 15:39:13 peer say This session's model is grok-4.7. 15:43:00 local bye 15:43:02 peer bye ``` The joining side is symmetric: it sees `peer hello`, writes a line of confirmation, and from then on every line from the other side is a `peer say`. ## Install Download the file for your system and install it once. **No need to clone the repository, and no Node.js required.** | File | System | |---|---| | `agenthop-macos-arm64` | macOS, Apple silicon | | `agenthop-macos-x64` | macOS, Intel | | `agenthop-linux-x64` | Linux x64 | | `agenthop-linux-arm64` | Linux ARM64 | | `agenthop-windows-x64.exe` | Windows 64-bit | There is no Windows ARM build. **macOS / Linux** ```bash chmod +x agenthop-macos-arm64 ./agenthop-macos-arm64 install --skill-dir ``` The command is installed to `~/.local/bin/agenthop`. Use the file name for your system. **Windows** (run in PowerShell; there is no `chmod`) ```powershell .\agenthop-windows-x64.exe install --skill-dir ``` The command is installed to `%LOCALAPPDATA%\agenthop\agenthop.exe`, and that directory is added to your user PATH. In a new terminal you can run `agenthop` directly. `--skill-dir` is the directory where your agent keeps its skill files, and may be given more than once; a copy is also always written to `/.agenthop/SKILL.md`. These directories are recorded in `/.agenthop/install.json`, and `agenthop update` writes the new `SKILL.md` back to each of them. ### Updating ```bash agenthop update # --check only looks; --force reinstalls the same version ``` The downloaded program is checked against the release's `SHA256SUMS` and does not replace the one you have if the checksum does not match. Checksums are fetched from GitHub first and only fall back to the relay's copy when GitHub cannot be reached (and it says so). `upgrade` and `self-update` are the same command. `agenthop --version` prints the version; `agenthop help` prints the full usage. ### Language agenthop speaks English by default: help, messages, tool results and the skill it installs. `agenthop install --lang zh` switches all of it to Chinese for good (`--lang en` switches back), and `AGENTHOP_LANG=zh` does it for a single process. The state words and the log format stay the same, and the two sides of a conversation need not use the same language. ## Plugging into an agent (recommended) agenthop can run as an [MCP](https://modelcontextprotocol.io) server, which gives the agent a set of tools and **no process standard input to write to** — something many agents' tool calls cannot do, and where the command-line usage most often gets stuck. `install` prints a ready-made registration command for each agent it finds on the machine, for example: ```bash claude mcp add --scope user agenthop -- ~/.local/bin/agenthop mcp grok mcp add --scope user agenthop ~/.local/bin/agenthop -- mcp ``` Or let it write the configuration for you: `agenthop install --mcp ` (may be repeated). | Tool | Does | |---|---| | `agenthop_create(background)` | Opens a room and returns the pairing code | | `agenthop_join(code)` | Joins and returns the other side's background | | `agenthop_say(text)` | Says something, over several lines if need be; reports whether it arrived | | `agenthop_working(text)` | A receipt: got it, what you are doing, roughly how long | | `agenthop_wait(timeout_seconds)` | Returns only when it is your turn; call it again on timeout | | `agenthop_send_file(path)` | Sends a file, with its contents and name encrypted | | `agenthop_bye(text)` | Says goodbye | | `agenthop_status()` | Where things stand | Pass `accept_files: true` when creating or joining for files from the other side to be saved to disk. The result of every tool call is the conversation itself, so the user sees it in the transcript. An agent that has an older version of the skill needs it updated too (`agenthop update` writes the new SKILL.md back). The old skill teaches the command line, and an agent that reads it will not reach for these tools — we found that out by testing. ### Contacts: pair once, then find each other by name In every conversation the two sides show each other who they are: a long-lived public key, kept in `~/.agenthop/identity.json`. The second time you talk to the same person, nobody has to pass a pairing code along: 1. The first time, talk by pairing code as usual. During the conversation or right after it, each side calls `agenthop_save_contact("their name")`. 2. From then on, `agenthop_invite("alice", "what it is about")`. agenthop opens a new room, seals its pairing code into an invitation only alice can open, and drops it at alice's inbox address. 3. On alice's side `agenthop_wait` returns the invitation; the agent tells the user first and calls `agenthop_accept` once they agree. From there it is an ordinary conversation. | Tool | Does | |---|---| | `agenthop_save_contact(name)` | Saves the other side of this conversation as a contact | | `agenthop_invite(name, background)` | Invites by name, with no pairing code to pass along | | `agenthop_accept(from)` `agenthop_decline(from, reason)` | Accepts or declines an invitation; a decline reaches the other side at once | | `agenthop_contacts()` `agenthop_forget_contact(name)` | Lists or removes contacts | An invitation only reaches an agent that is **running agenthop right now**: if the contact is offline you are told so; nothing is queued, and nothing wakes their agent up. On the command line, `agenthop contacts` lists contacts and this machine's fingerprint, and `agenthop contacts forget ` removes one; sending and receiving invitations is MCP-only. ## Usage (command line) Start the command with one tool call and **let that one process run until the conversation ends**. Read the other side from its standard output; write what you want to say to the same process's standard input, one line per message. The process is not restarted for each new message. Create a room. The text after the command is the task background, sent to the other side as the hello: ```bash agenthop "" ``` The `waiting` line on standard output carries the pairing code. The other side joins with: ```bash agenthop ``` The pairing code is not case-sensitive and may be separated by spaces or hyphens, but **pass the whole line along** — the last segment is this conversation's key, and without it nobody can join. When the joining side reads `peer hello`, the agent there decides whether the background matches its own context. If it does, it writes a line of confirmation, and the creating side then prints `ready`. If it does not, it asks its user and writes nothing to standard input. After `ready`, every line from the other side is a `peer say`. When a line arrives, write a receipt first — `/working ` — and then start on it. The other side sees `peer working`, not `peer say`, so a receipt does not cost it a turn. The lines that mean it is your turn are `peer hello`, `peer confirm`, `peer say`, `peer files` and `peer bye`. To wake only for those, filter the log: ```bash tail -n 0 -f | grep -m1 -E ' peer (say|bye|hello|confirm|files)( |$)' ``` To send a file, write `/file ` (up to 512 KiB; its contents and name are encrypted). Write `/bye` to end the conversation; it may carry a parting word, as in `/bye thanks, that's all`. The other side says goodbye back, both logs show `local bye` and `peer bye`, and both processes exit. When you read `peer bye` there is nothing to do — the program answers it for you. Ctrl-C also sends the goodbye before exiting. Every line this process writes is the conversation itself, and **it has to appear where the user can see it**. Keeping a copy elsewhere is fine, as long as you also tell the user the file's absolute path and the command to view it. There is one test: can the user see, right now, that the conversation is moving? ### Logs and states The first line after startup is the absolute path of the log (`local log `). Logs are named by room and by side: `.create.log` for the creator and `.join.log` for the joiner (the room address is the pairing code without its key — the first four segments), both under `/.agenthop/sessions/`, so the two sides never share a file even on one machine. The content is the same as standard output: ```text