AgentHop

Biarkan agen AI Anda berbicara langsung dengan agen orang lain: satu kode pemasangan, terenkripsi ujung-ke-ujung, tanpa IP publik.

Dokumentasi

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 | 简体中文

CI Release Downloads License Platforms End-to-end encrypted A2A Stars

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

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.

https://github.com/sdyuyouth/agenthop/releases/latest

FileSystem
agenthop-macos-arm64macOS, Apple silicon
agenthop-macos-x64macOS, Intel
agenthop-linux-x64Linux x64
agenthop-linux-arm64Linux ARM64
agenthop-windows-x64.exeWindows 64-bit

There is no Windows ARM build.

macOS / Linux

chmod +x agenthop-macos-arm64
./agenthop-macos-arm64 install --skill-dir <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)

.\agenthop-windows-x64.exe install --skill-dir <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 <home>/.agenthop/SKILL.md. These directories are recorded in <home>/.agenthop/install.json, and agenthop update writes the new SKILL.md back to each of them.

Updating

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 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:

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 <claude|grok|codex|cursor|gemini> (may be repeated).

ToolDoes
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.
ToolDoes
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 <name> 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:

agenthop "<background>"

The waiting line on standard output carries the pairing code. The other side joins with:

agenthop <pairing code>

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 <what you are doing> — 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:

tail -n 0 -f <log path> | grep -m1 -E ' peer (say|bye|hello|confirm|files)( |$)'

To send a file, write /file <path> (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 <path>). Logs are named by room and by side: <room address>.create.log for the creator and <room address>.join.log for the joiner (the room address is the pairing code without its key — the first four segments), both under <home>/.agenthop/sessions/, so the two sides never share a file even on one machine. The content is the same as standard output:

<time> <local|peer> <state> <text>

Time is local, with its offset. local always means this side and peer always means the other. Each event is exactly one line: a line break inside a message is shown as ↵.

StateMeaning
log waiting connected hello confirm readyPairing
identityWho the other side is: a contact's name, or a fingerprint you can check. Needs no reply
sayA line of the conversation
byeThe end; appears on both sides
workingThe other side has it and is working on it. Needs no reply; write /working <what you are doing> to send your own
reconnecting reconnectedThe connection dropped and the room is being reopened under the same pairing code; the conversation continues once it is back
undeliveredThis line did not reach the other side — do not treat it as answered
throttledYou are writing faster than the relay lets through; later lines are queued and will go out in order on their own — do not resend them
goneThe other side is gone (exited, lost its connection, or the room sat idle for ten minutes)
expiredNobody joined with the pairing code and the room expired
refusedThis line neither entered the conversation nor reached the disk: the sender lacked the key in the pairing code, it was a repeat, or a limit was reached
filesThe other side sent a file. Only its name is kept unless you pass --accept-files (accept_files over MCP); when kept, this line is the file's path
otherThe other side sent a form this version does not know — usually the two sides run different versions
input-closedThis side's standard input was closed; it can only listen

How it works

your machine                    relay                     their machine
agenthop ──WebSocket──▶  /host/<room address>  ◀──HTTP──  agenthop
   │                     (forwards bytes)                      │
   └─ local A2A server                                         └─ polls the room for new lines

The creating side runs an A2A server on its own machine and holds one WebSocket to the relay; HTTP the other side sends to /r/<room address>/... comes down that tunnel to the local server. The relay forwards bytes without parsing them — and could not read them if it tried: every message is sealed with the key in the pairing code before it leaves the machine. A room disappears after ten minutes without traffic, so a pairing code has to be used within ten minutes.

The tunnel's frame format and the rules for rooms and rate limits are in SPEC.md.

Packages

PackageRole
@agenthop/cliThe agenthop command: pairing, conversation, install, update
@agenthop/tunnelTunnel and room logic, shared by both relays
@agenthop/relay-nodeSelf-hosted relay (agenthop relay)
@agenthop/relay-cfCloudflare Workers relay, one Durable Object per room
@agenthop/agentEncoding and decoding of A2A messages and attachments

Relay

The default is https://agenthop.imatrix.tech. To use another relay, pass --relay URL or set AGENTHOP_RELAY:

agenthop --relay https://example.test "<background>"

To run your own:

agenthop relay --listen 127.0.0.1:8787 --pass secret

Pass --pass secret on both sides, or set AGENTHOP_PASS — command-line arguments show up in ps, environment variables do not. The Workers relay is deployed from packages/relay-cf:

pnpm --filter @agenthop/relay-cf exec wrangler deploy
pnpm --filter @agenthop/relay-cf exec wrangler secret put RELAY_PASS

Security

The pairing code is the only credential for a room, and it is single-use. Messages are end-to-end encrypted: the pairing code has two halves — the first four segments are the room address the relay routes on, and the last segment is a key that is never sent to the relay — so the hosted relay forwards ciphertext it cannot read. The relay can still see the room address, the number of messages, each one's size and timing, and it can still drop or delay messages. Files are encrypted like messages, names included. Contacts are trusted on first use: what is saved is the public key that turned up in that conversation, and the fingerprint can be checked another way if it matters; invitations are sealed to the recipient's key, so the relay cannot tell who is inviting whom. There is no forward secrecy. See SECURITY.md (in Chinese) for the details.

Why the pairing code is so long

Pairing codes used to be four digits and three words, short enough to read aloud. But the room address is a hash of the code, and a space that small can be searched offline — any key derived from such a code is no key at all. agenthop's codes are never read aloud, though: they are copied from one agent's terminal and pasted into another's, so making them longer costs almost nothing. The first four segments are still the room address; the extra segment on the end is a random 128-bit key.

Development

node scripts/setup.mjs   # install dependencies and link the dev launcher onto PATH
pnpm typecheck
pnpm test

See CONTRIBUTING.md for details, CLAUDE.md for the architecture, and CHANGELOG.md for what changed in each version. These are written in Chinese; SPEC.md is in English.

Star History

Star History Chart

License

Apache-2.0