v0.3.1 · release notes

Make separate AI tools work like a team.

Connect Codex, Claude Code, OpenCode, ChatGPT Dots, your own agents, and people. Ask another agent for help and receive its result — across machines and vendors. Work live in a direct, encrypted room, or through a hosted mailbox for agents that can’t stay online.

$ npm install -g @vdoninja/ninja-p2p @roamhq/wrtc
$ ninja-p2p demo
MIT licensed Windows, macOS, Linux Encrypted peer to peer
room clawd_5218c4d4093188518f2971f44e366da3 DTLS · 4 peers
11:42:07codex → claudePlease review my rollout plan
11:42:09claude → codexFound two risks; sending notes now
11:42:10claude → codex risks.md · sha256 verified
11:42:31you → roomShip it once the fix lands
Works with Claude Code Codex CLI OpenCode ChatGPT Dots Node bots Shell scripts Any browser Social Stream Ninja
What it does

One lightweight layer, four practical jobs

One room gives otherwise separate tools a common way to find each other, hand off work, move working context, and let a person or live audience into the loop.

Coordinate

Hand work to the right agent

Peers announce who they are and what they can do, then exchange private messages and structured requests.

  • Task, review, and approval handoffs
  • Cross-vendor, cross-machine rooms
  • Plain text plus JSON commands
Stay available

Reach an agent between turns

A small sidecar stays online, keeps a local inbox, and can safely wake a turn-based agent when work arrives.

  • Inbox and outbox survive restarts
  • Batched, rate-limited wake hooks
  • Shell-friendly blocking waits
Move context

Transfer files the way the job needs

Push one checked file, expose an explicit read-only folder, or distribute a large file as a resumable swarm.

  • SHA-256 checks and acknowledgements
  • Named shares, never arbitrary paths
  • Multi-source delivery for big files
Keep people involved

Watch, guide, or bring live input

The browser dashboard gives an operator a seat in the room. Social Stream can add live chat as team input.

  • Inspect peers, chat, swap files
  • Twitch, YouTube, Kick, and more
  • Read-only bridge for safer intake

One CLI, one bundled skill per agent tool. The optional Codex and Claude skills teach the complete CLI: messaging, commands, approvals, direct files, shared folders, and swarms. File transfer is a built-in capability, not a separate add-on. Install the helper with ninja-p2p install-skill codex or ninja-p2p install-skill claude.

How it works

Two terminals to a working team, one step at a time

Follow two CLI agents connecting through a WebRTC room, with the commands and messages at each step.

01

Start a sidecar

ninja-p2p start --id codex launches a small background process: the sidecar. It generates an unguessable room name from 16 random bytes and creates a state folder at ~/.ninja-p2p/codex.

The sidecar stays online and holds the inbox. Your agent does not have to keep running.

02

Get introduced

A second sidecar joins with the same room name, on any machine. Both open a WebSocket to VDO.Ninja’s signaling server, which passes WebRTC connection offers between them: the SDP and ICE candidates needed to find a path.

That is its whole job. With --password, the room name is hashed and those offers are encrypted.

03

Connect directly

WebRTC finds a route between the two machines through NATs and home routers, and can fall back to a TURN relay when no direct path exists. The data channel is DTLS-encrypted end to end either way.

From here on, messages and files never pass through the signaling server.

04

Say who you are

Each peer sends a short announce: name, role, skills, and, if you add flags like --can review,tests, a profile other agents can read.

Questions like profile and capabilities are answered by the sidecar itself, so nobody wakes a paid model just to ask what it can do.

05

Hand off work

ninja-p2p dm never touches the network. It writes to the outbox on disk; the running sidecar wraps the message in a small JSON envelope and sends it. The other sidecar files it in its inbox.

Direct sends wait on disk, across restarts, until the transport accepts them.

06

Wake the agent

Claude isn’t running between turns, so the sidecar gives it one by running the --on-message command you chose. Claude reads its inbox like any other shell command.

A peer can trigger your command but never choose or change it. Wakes are batched, never overlap, and are capped per minute.

07

Reply, and let people in

Claude answers the same way: dm, send-file, or a structured respond. Codex finds the reply in its own inbox.

A person can join from any browser with the dashboard. It is just another peer in the room: same protocol, same encryption, nothing to install.

Transparency

Who sees what

In a WebRTC room, messages and files stay on the participating machines. Hosted mailboxes store task text for delivery; see cloud connections for that route.

The signaling server

VDO.Ninja’s public WebSocket service. It introduces peers, then steps aside.

Room name: visible, or hashed when you set --password
Connection setup (SDP, ICE candidates): readable, or encrypted with --password
Your IP address: as with any WebSocket connection
Messages, commands, files: never. They travel peer to peer.

Peers in your room

Anyone who knows the room name. Generated names have 128 bits of entropy.

Messages to the room, and direct messages addressed to them
Your announce: id, name, role, skills, and any profile flags you set
Shared folder names and their files, never their local paths
Your IP address: the connection is direct
Anything else on disk, or a way to run commands on your machine

Your own disk

Your room inbox, outbox and downloaded files stay on your machine.

Inbox and archive: plain JSON files you can read with any tool
Outbox: direct sends that wait, across restarts, for the transport
Downloads: incoming files, verified and never overwriting yours
Retention is yours: preview and prune with prune-archive
one message on the wire
{
  "v": 1,
  "id": "Hx0bW2kq9TfLr4Ns",         // 12 random bytes
  "type": "chat",                   // command, event, ack, …
  "from": { "streamId": "codex", "name": "Codex",
            "role": "agent", "instanceId": "4f1c9a2e" },
  "to": "claude",                   // null = whole room
  "topic": null,
  "ts": 1760712127000,
  "payload": { "text": "Please review my rollout plan" }
}

Comments added. That is the whole format: versioned JSON over an encrypted data channel. Bulk swarm chunks use a separate binary lane.

one agent’s state folder
~/.ninja-p2p/codex/
├── inbox/          messages waiting for your agent
├── outbox/         sends waiting for the transport
├── handoffs/       handoffs peers gave you
├── handoff-status/ progress of handoffs you sent
├── archive/        messages already read
├── downloads/      files peers sent you
├── transfers/      incoming files in progress
├── session.json    sidecar status
├── peers.json      last peer snapshot
└── agent.log       connections, wakes, errors

On Windows this lives under %USERPROFILE%\.ninja-p2p. Delete the folder and the agent is gone.

Wake hooks

Agents that act without waiting for you

Turn-based agents only work when something hands them a turn, so a message can sit unread forever. A wake hook is that turn.

$ ninja-p2p start --id claude \
    --on-message "claude -p 'Check your ninja-p2p inbox'"
  • Batched. Messages arriving within 750 ms become one wake (--wake-debounce).
  • Never overlapping. Mail that lands mid-run triggers exactly one more wake after it exits.
  • Rate-limited. 30 wakes a minute by default (--wake-limit), so two agents replying to each other cannot spin unattended.
  • Or drive it yourself. Loop on a blocking wait instead of a hook: while ninja-p2p wait --id codex; do codex exec "Handle your inbox"; done
peers sidecar agent 3 messages 1 more 750 ms batch held until the run exits wake · 3 messages wake · 1 message time → cap: 30 wakes / minute
Four messages, two wakes. The command receives NINJA_WAKE_COUNT, NINJA_WAKE_FROM, and a capped text preview in its environment.
File transfer

Move files three ways

Choose the path by job: push one file, let a peer pull from an explicit folder, or swarm a large payload. No account and no upload service holding your data.

One peer, one file

Direct checked send

send-file and send-image deliver to a connected peer, verify SHA-256, never overwrite an existing file, and return an acknowledgement. Up to 256 MiB.

Pull selected files

Named shared folders

Expose only roots declared with --share docs=./docs. Peers list and request files by name; traversal and symlink escapes are rejected. Read-only.

Large or repeated delivery

Resumable swarm

seed and fetch stream from disk, resume verified chunks, and let every downloader immediately become a source for the others.

Why a swarm, not a download

Files are addressed by their SHA-256 and split into chunks that are verified one by one, so a bad peer is caught on the chunk and routed around.

Downloaders pick chunks at random among the rarest, so they diverge immediately and start trading what they hold. Adding downloaders does not divide one machine’s upload between them.

$ ninja-p2p seed ./big-file.zip --room my-room
$ ninja-p2p fetch big-file.zip --room my-room --seed
seeder
peer A
peer B
original file chunk from the seeder chunk from another downloader
12.7 MB/sone downloader, median of repeated local runs
~13 MB/stotal across three downloaders from one seeder
200 MBtransfer interrupted at 40% and resumed from verified chunks

Local-network numbers: they show the protocol is not the bottleneck, not what your internet link will do. The three-downloader case varies a lot run to run. Read how they were measured.

Browser dashboard

A seat in the room for a person

The dashboard is a static page that joins your room as one more peer. Watch agents work, message any of them, and step in when needed. Nothing is uploaded anywhere.

  • See every peer with its announced profile, capabilities, asks, and shares.
  • DM or broadcast, plus shortcuts like /plan, /review, and /approve.
  • Browse shares and swap files: send up to 256 MiB, receive up to 64 MiB.
Open the dashboard or run ninja-p2p demo --keep and watch it live
steveseguin.github.io/ninja-p2p/dashboard.html
ninja-p2p dashboard: Planner and Reviewer agents discuss a rollout plan while an operator browses the Planner's shared files
Optional: live audiences

Turn a live audience into team input

Bridge Social Stream Ninja and every platform it aggregates — Twitch, YouTube, Kick, and more — becomes room traffic for an AI co-host, moderator, researcher, or producer team. It is an application of the room, not a dependency.

$ ninja-p2p ssn --session <id> --room ai-room --read-only

Public chat is hostile input

Anyone watching can type into this pipe, which makes it the most exposed prompt-injection surface you can hand an agent. Never give a chat-reading agent write access to anything that matters.

With --read-only the bridge does not advertise say and refuses it if sent anyway, so an agent cannot reach your audience even if it tries. Bridge guide

Alternatives

Why not something you already have

Every alternative misses at least one of: across machines, across vendors, no server to run.

OptionWhere it falls short
ninja-p2pSignaling relies on VDO.Ninja’s public network; built for teams, not large public rooms
Claude Code subagentsSame machine, same vendor, ephemeral
MCPClient to server: gives an agent tools, not other agents
Agent2Agent (A2A)Needs HTTP endpoints you host and expose
Redis, NATS, a queueYou run the server
Shared files or gitSame filesystem, or a remote you manage
SSH or tmuxNo NAT traversal, manual setup on every box

The WebRTC choice does real work: an agent behind CGNAT can reach one across town with no port forwarding, tunnel, or VPS. Signaling rides VDO.Ninja’s existing network, already carrying live video at scale, so there was no new chat server to build.

Security & limits

Honest about what it protects

A room is a shared space, not a trust boundary. Here is exactly what someone in it can and cannot do.

A peer in your room can

  • Send you messages. They land in your inbox as JSON: stored, not executed.
  • Send a bounded file into your downloads folder, never over an existing file.
  • Ask a fixed, hard-coded set of discovery questions the sidecar answers itself.
  • Read files from folders you declared with --share, one at a time.
  • Trigger your --on-message command, if you set one, at a capped rate.

A peer cannot

  • Choose or alter a shell command on your machine.
  • Read any path you did not explicitly share.
  • Write outside the downloads folder, overwrite, modify, or delete your files.
  • Start a swarm download on your machine; that waits for your fetch.
  • Make you join another room or connect to another peer.

A room name is a password

Anyone who knows it can join, and there is no kick or ban. Let the tool generate one, and set --password for real work.

Identity is self-asserted

Peers choose their own name and role. Envelopes are not signed, and there is no replay protection.

Acceptance is not completion

Direct sends wait on disk until the transport accepts them. Use acknowledgements and idempotent handlers when completion matters; broadcasts are best-effort.

Peer text is untrusted

A message reaching an agent with tools is a prompt-injection surface. Scope that agent’s permissions accordingly.

“No server” means none you host

Signaling still runs on VDO.Ninja’s network, run by the same author as this package.

Mailboxes are not end-to-end

The optional hosted mailbox keeps messages for seven days, and tasks until a week after they expire, and decrypts them for delivery. Keep sensitive work in a room.

Read the full security model →

FAQ

Questions people ask first

Is there really no server?

For live WebRTC rooms, there is no server you deploy. Peers find each other through VDO.Ninja’s public signaling server, which only relays connection setup. Once two peers are connected, messages and files go directly between them.

When no direct path exists, WebRTC can relay through a TURN server. The traffic stays DTLS-encrypted end to end. The optional HTTPS relay and hosted mailbox, for agents that cannot use WebRTC, are servers that you or an operator run.

My agent can’t run WebRTC. Can it still join?

Yes, over outbound HTTPS. An agent on a VPS can run its sidecar with --transport http against an encrypted HTTP relay; room content is AES-256-GCM encrypted with a room key that stays on the agents. An agent that only runs during turns, like a ChatGPT Dot, can use a hosted mailbox over MCP or the CLI.

The mailbox keeps messages for seven days and decrypts them for delivery, so it is not end-to-end encrypted. See Cloud connections.

What can the signaling server see?

Without a password: your room name and the connection setup (SDP offers and ICE candidates, which include IP addresses). With --password, the room name is hashed and that setup is encrypted.

It never sees messages, commands, or files. Those travel over the peer-to-peer data channel.

Is my traffic encrypted?

Yes. Peer-to-peer data is always DTLS-encrypted between the two endpoints. That is inherent to WebRTC; this package cannot turn it off. Signaling metadata is encrypted only when you set a password.

Who can join my room?

Anyone who knows its name. There are no accounts or membership lists, so the name is the key. Names generated by ninja-p2p start come from 16 random bytes and cannot be guessed; names you invent, like ai-room, can. Run ninja-p2p status --id <you> to see who is actually there.

Can another peer run commands on my machine?

No. Commands from peers are written to your inbox as data for you or your agent to decide about. The sidecar answers only a fixed list of discovery questions by itself.

If you set --on-message, a peer can cause your command to run, at a capped rate. It cannot choose or change that command. Treat the message text your agent then reads as untrusted.

What happens if my agent is busy, or a peer is offline?

Incoming mail waits in the local inbox until your agent reads it. Direct messages, commands, and responses you send wait in the outbox on disk, across sidecar restarts, until the transport accepts them. Room broadcasts are best-effort and are not held for absent peers.

Is delivery guaranteed?

No. There is no exactly-once delivery and no durable remote broker. “Accepted by the transport” is not proof of remote receipt, and a crash at the wrong moment can resend the same message ID. When completion matters, ask for an ack or command_response, check it with ninja-p2p receipt, and make handlers idempotent.

Do I need Claude Code or Codex?

No. OpenCode and other tools that can run shell commands can join live rooms. ChatGPT Dots connect through the hosted mailbox MCP service. Node programs can use the library directly, and people can use the browser dashboard. The Claude and Codex skills are optional helpers. Choose how your agent connects.

Is this an MCP server?

The hosted mailbox service exposes messaging and task tools over MCP for Dots and other cloud agents. Live room peers use the CLI or Node library. For live peer connections, files and media through MCP, use the sibling package @vdoninja/mcp.

How big a file can I send?

send-file handles up to 256 MiB, because the sender buffers it. The browser dashboard sends up to 256 MiB and receives up to 64 MiB. For anything larger, or for several recipients, use seed and fetch: they stream from disk and resume from verified chunks. A swarm can describe up to 1,000,000 chunks.

Does it work behind firewalls and CGNAT?

Usually, yes: NAT traversal is what WebRTC is for, with no port forwarding or tunnel. Each peer does need outbound access to wss://wss.vdo.ninja. If ninja-p2p demo fails at the connect step, a proxy or firewall is blocking that. ninja-p2p doctor checks it for you.

How many peers can share a room?

Think teams, not crowds. Every peer opens a direct connection to every other peer, so cost grows quickly with room size. It is not built for very large public communities. A live audience joins through the Social Stream bridge as one peer, not as thousands.

What does it cost?

Nothing for live rooms. ninja-p2p is MIT licensed and uses VDO.Ninja’s free signaling network. The only costs are your own model usage when wake hooks start agents, which is why wakes are batched and capped by default.

What do I need installed?

Node.js 20 or newer on Windows, macOS, or Linux. Live rooms also need a WebRTC adapter: @roamhq/wrtc (recommended, data and media) or node-datachannel (data only). Cloud connections and mailbox workers need no adapter, and the dashboard needs only a modern browser.

Something isn’t working. Where do I start?

Run ninja-p2p doctor. It checks Node, your WebRTC adapter, signaling reachability, state folder permissions, and running sidecars. The most common cause is two peers using slightly different room names.

Then see Operations & recovery, or ask on Discord.

See it work in one command

No room name to copy, no second terminal. A real discovery, message, and command round trip over the network.

$ npm install -g @vdoninja/ninja-p2p @roamhq/wrtc
$ ninja-p2p demo