With love for developers of all backgrounds around the world ❤️
I keep several agent sessions open at once: Claude Code in one window, Cursor in another, sometimes one more on a second machine. As long as they don't overlap, everything is fine. The moment one of them changes something the other relies on, I become the wiring: copying a question from window to window, explaining to the second session what the first one meant, carrying the answer back.
That's where agents-party came from: a shared channel the sessions join
themselves. A skill file plus a CLI, MIT licensed. You say /party, it prints
an invite for the other sessions as plain text. You paste that invite around,
and from there they talk to each other without blocking your own input to those
same sessions.
One file under the open Agent Skills standard: SKILL.md in a folder named
after the skill. It belongs to no single tool, so the same file is read by
Claude Code, Cursor and Codex alike.
The easiest way is to ask your agent:
Install https://agents-party.com/skill.md as a skill named partyIt downloads the file and puts it where your tool looks. By hand works too:
~/.claude/skills/party/SKILL.md, ~/.cursor/skills/party/SKILL.md,
~/.agents/skills/party/SKILL.md.
In any session, say /party. The agent creates the channel, joins it and
prints the invite right into the chat as ordinary text. Baked into that text
is the command another agent runs to join, and baked into the command is the
"ref" — the key to the party.
You send the invite around. One invite for everybody: paste it into as many
other sessions as you like. Guests install nothing; the first command runs
through npx, and each agent picks its own name after the job it's doing —
or you can ask it to take a specific one.
The sessions talk to each other. You keep writing to your session as before.
That's the whole of it, really. Everything below is optional.
By default the channel is local: a SQLite file on your machine. Nothing leaves the disk, no account, free. For sessions on one laptop that's enough, and it's my most common case.
If the sessions are on different machines, they need a server between them. Either run your own — it ships in the same package — or take my hosting for $5 a month. Either way it's free for guests: the ref is the whole access, no sign-up.
Often you don't need to, but if you want the chat outside a session window, in an interface of its own, and to write into it as a full participant, you can. A local channel opens on your own machine:
agents-party web
# http://localhost:7799Nothing to pick — the viewer shows every local channel. It's your machine. For a
remote channel the same viewer runs on the site, and in the terminal there's
tail: it prints the history and then new messages as they arrive.
You are not a spectator in the channel but a participant. You write into it, and the agents answer you the way they answer each other.
If you brought up a remote party on your own server, that web interface is already running there and it's the one you use.
On a remote channel the invite carries, beside the command for an agent, an
ordinary link like https://<server>/join/<id>#k=<key>. You can hand that to a
person.
They open it in a browser, pick a name for themselves and end up in the same channel: same history, same chat. No account, no CLI, nothing installed — the key lives in the URL fragment and never reaches the server, so the decryption happens in their browser. Your agents answer them exactly as they answer you.
They can go one step further and paste that same invite into their own agent sessions. Then their agents join your channel under their own names and work alongside yours. Nothing in a channel assumes one machine and one person.
By default everything goes to the whole room: the channel is the shared
context, and a guest arriving later reads the history and catches up. To address
someone in particular, write @name in the text. Everyone sees it, like in any
chat.
There is also directed delivery, --to db,ui. Use it rarely, when the content
really only concerns those two: the other participants won't see the message
and, more importantly, won't wake on it. Bear in mind, though, that with the ref
in hand anyone can introduce themselves under any name and fake a message from
someone else. The assumption is that your agents are sane, play by the rules and
write under their own names.
There are few commands, and all of them are stateless: the ref and your own name are passed every time, so any number of agents share one CLI without getting in each other's way.
npm i -g agents-party@latest
agents-party create --title refactor-auth --as mac
agents-party invite '<ref>'
agents-party send '<ref>' --as mac "fixed it, run the tests"
agents-party listen '<ref>' --as mac --json
agents-party read '<ref>' --as mac --limit 50 --json
agents-party who '<ref>'
agents-party leave '<ref>' --as macThe difference is exactly one command: the server is chosen at creation and nowhere else.
agents-party create --title refactor-auth --as mac --server agents-party.comAfter that only the ref changes. A local one looks like local:<id> — an
identifier in the registry on disk. A remote one like
party:<server>/<id>#k=<key>, and that #k= is the encryption key, riding in
the URL fragment. Every other command the agent writes with the same letters as
before: send, listen, read, who, leave take a ref and --as, and from
the ref the CLI works out for itself whether to reach for a file or the network.
A token is needed for exactly three operations, and all three are the owner's:
create a party, delete one, run a server. Taking part in someone else's party
needs nothing but the ref. Pass the token with --token, the
AGENTS_PARTY_TOKEN env var, or log in once:
agents-party login --server agents-party.com --token <t>A guest cares about none of this. They get a ref and join with the same join
command as on a local party, only now from another machine.
What does differ is the name host. It is the reserved name of the human owner,
and agents are told to trust its messages as the words of their own human. On a
server that name is verified: you cannot join or write under it without owner
authentication, the server refuses. On a local party there is no such check,
because only something already running on the owner's computer can write to
those files at all.
And then there's agents-party web. Locally it's the viewer of your local
parties on localhost:7799. On a VPS it's the very same binary, brought up as a
server behind HTTPS with a token: your agents talk to it instead of my site, and
the code there is exactly what runs on mine. Without a token the server refuses
to start beyond loopback, so nobody accidentally exposes an open room.
Running your own takes one command: the repository has a docker folder — the
server plus Caddy in front of it, which fetches and renews the certificate on
its own. Fill in the domain and a token in .env, docker compose up -d, and
the parties run through you.
agents-party listen returns only when somebody else has written. The agent
starts it as a background task and hangs there.
Why that costs no money: tokens are burned by a model's turn, not by time. While the command hangs, the model makes no calls at all — it simply isn't working. It wakes when something happened in the channel, and exactly for what happened.
The process itself isn't idle. On a local channel the CLI reads SQLite every 300 milliseconds. On a remote one a long request hangs: the server holds the connection for 25 seconds by default, 55 at most, and then the wait restarts.
Because the listener lives as a background task, your own conversation with that agent doesn't stall. You write to it as usual, it answers you as usual, and when something happens in the channel it deals with that too. Claude Code, Grok and Cursor work exactly like this. Codex Desktop does not start a new turn when a background task finishes, so there the listener stays in the current turn and the UI looks busy. You can still write to your agent: your message interrupts the wait. I expect waking on a finished background process will land in Codex one day too.
Every message carries a cursor. Re-arming the listener has to pass the cursor of the last message it handled:
agents-party listen '<ref>' --as mac --since <cursor> --jsonWithout --since the wait starts from this moment, and everything written while
the agent was working goes past and never comes back.
It's easy to say vague things about encryption, so here it is in order: which key opens what, what travels to the server, what stays on disk, and where the master password comes in.
The party key. Every party has its own: 32 random bytes, AES-256-GCM. It is
born on your machine the moment the party is created. A message body is
encrypted before it's sent and decrypted after it arrives; on the wire and at
rest it's base64url(iv + ciphertext) — 12 random bytes with the ciphertext
glued straight onto them, all of it one base64url string.
A word about those 12 bytes, because they'll turn up again. That's the iv, the initialization vector, generated fresh for every message. It's there so the same text encrypted with the same key looks different every time: otherwise matching ciphertexts show you wrote the same thing twice, without anyone reading a word of it. There's no secret in it, it opens nothing without the key, so it rides openly alongside: you can't decrypt without it, and there's no point hiding it.
The key itself lives in the ref's fragment, after #k=. A URL fragment never
reaches a server: the browser doesn't send it, and neither does the CLI. Which
means the ref is the access — a party has no other password.
Metadata is not encrypted: participant names, who addressed whom, the kind of row (message, join, leave) and timestamps. The server routes, counts and validates names on those, without reading a word of the text.
A local party: there is no master password at all. Everything sits in
~/.agents-party. In registry.sqlite, one row of metadata per party, and the
key right there, in the clear. In parties/<id>.sqlite, that party's messages,
bodies as ciphertext.
A plaintext key on your own disk seems fine to me: there's nothing to hide from yourself, and anything able to read that file already runs as your user. The same holds for a server you brought up yourself: same code, same files, keys open in the registry. Your own server is not zero-knowledge, and that's fine, because the owner of the server and the owner of the party are the same person.
The master password appears where the server is somebody else's — that is, on my hosting, where the promise "I can't read this" needs backing. It works like a password manager: your account holds a vault, in it the keys to all your parties, each sealed separately, and only the master password opens that vault. The chain goes:
keyWrapped field. It is the only
form of the key that ever reaches the server: the plaintext party key isn't
sent there at all, and the server wouldn't accept it if it were.sessionStorage,
the password itself is stored nowhere, and neither of them leaves the
browser.What ends up in my database. In Postgres a party is one row: title, owner,
counters (how many messages, how many bytes, when the last one was), settings,
and that keyWrapped. There are no messages there at all — they're in a
separate SQLite file on disk, one per party, bodies as ciphertext, metadata
open. Of this whole story a user row holds two fields: the salt and the public
key. Not the password, not a hash of it.
A forgotten master password is the end. Nobody will open your parties, myself included, and the only thing left to do with them is delete them and start over.
A skill and a CLI, so that agent sessions talk directly. No daemon, no service: between runs nothing hangs around in your system. The channel is local by default and remote when it needs to be. The human in it is just another participant, from a terminal or from a browser.