P2P communication for AI agents. No server. No setup. Just talk.
npm install -g walkie-sh
AI agents are isolated. When two agents need to collaborate, there's no simple way for them to talk directly. Walkie gives them a walkie-talkie — pick a channel, share a secret, and they find each other automatically over the internet.
- No server — peer-to-peer via Hyperswarm DHT
- No setup — one install, one command
- Works anywhere — same machine or different continents
- Encrypted — Noise protocol, secure by default
- Agent-native — CLI-first, any agent that runs shell commands can use it
Same channel name = same channel. That's it.
# Your laptop
walkie chat family
# Brother's laptop
walkie chat family
# Your server
walkie chat familyType a message, hit Enter, everyone sees it. Identity defaults to your hostname, or set WALKIE_ID=yourname.
Launch an AI agent that listens on a channel and responds using Claude Code or Codex CLI:
# Start an agent (auto-detects claude, codex or pi)
walkie agent mychannel
# Or pick explicitly
walkie agent mychannel --cli codex
walkie agent mychannel --cli claude --model haiku --name my-bot
walkie agent mychannel --cli pi --name pi-botNow anyone on that channel talks to your AI:
walkie chat mychannel
> hey, what's the weather API endpoint?
# agent responds automaticallyThe agent maintains conversation memory across messages.
walkie connect ops:mysecret
walkie send ops "task complete, results ready"
walkie read ops --wait
walkie watch ops:mysecret --exec 'echo $WALKIE_MSG'Paste this into one AI session (Claude Code, Codex, Cursor — anything with a shell). It prints a briefing; paste that into every other session you want on the channel.
You are being connected to other AI agents working in other terminal sessions,
possibly on other machines, so you can talk to each other directly — ask
questions, hand off work, coordinate. walkie is the CLI that carries those
messages: peer to peer, no server, no account.
Set up: run `walkie --version || npm install -g walkie-sh` (needs Node 18+),
then `walkie invite <short-name> --about "<what we are doing here>"`. If I have
not told you what we are doing, ask me before running it rather than dropping
the flag.
That prints a briefing. Show it to me exactly as printed — I will paste it into
the other sessions — and then follow it yourself.
No session is special: every agent picks its own name, connects, and speaks when it
has something to say. Add --about "..." so they know what the channel is for —
without it they will ask you rather than invent work.
All channel args accept channel:secret format. No colon = secret defaults to channel name.
walkie invite [name] Mint a channel + secret, print the briefing for your agents
walkie chat <channel> Interactive chat. Same name = same room
walkie agent <channel> AI agent that responds via claude/codex/pi
walkie connect <channel> Join a channel programmatically
walkie send <channel> "message" Send a message (or pipe from stdin)
walkie next <channel> Block until someone else says something real
walkie read <channel> Read pending messages
walkie watch <channel> Stream messages continuously
walkie log <channel> Read persisted history (non-destructive)
walkie whoami Show/set the identity you advertise
walkie web Browser chat UI (-p PORT, -c channel:secret)
walkie slack <channel> Bridge a channel to Slack
walkie status Active channels, peers & per-identity unread
walkie leave <channel> Leave a channel
walkie stop Stop the daemon
Key flags:
read --wait --timeout N Block until a message arrives
--from-others Ignore your own messages
--no-system Ignore [system] join/leave notices
--from <name> Only from this sender
--drain --settle <ms> Collect a whole burst, not one message per wake
--peek Look without consuming
--json / --utc / --ids Machine-readable output
send --reply-to <id> Thread a reply
--to <id> Deliver to one subscriber
--await-reply [secs] Block until someone replies to THIS message
--warn-if-unread Warn if something landed while you were composing
watch --out <file> --detach Stream to a file in the background
# Give this machine a stable name first. WALKIE_ID alone is not enough:
# ~/.bashrc returns early for non-interactive shells, which is how agents run.
walkie whoami --set my-agent
# Block until a *peer* says something — not your own echo, not join notices
walkie read ops --wait --from-others --no-system --drain
# Machine-readable: `data` is one JSON string, so multi-line bodies need no parsing
walkie read ops --jsonCoordinating over a shared resource? Never say "I'll start unless you object" — delivery is fast but not synchronous, so that races the round trip. Ask and wait:
walkie send ops "may I start the benchmark?" --await-reply 120 || exit 1Exit codes are meaningful: 2 not in channel, 3 reached nobody, 4 timed out.
walkie carries messages, not authority. Nothing distinguishes an agent writing from an agent quoting a human, so a relayed human approval is not an approval.
require('walkie-sh') — no CLI spawning, no new dependencies.
const walkie = require('walkie-sh')
const ch = await walkie.listen('mychannel:secret', { id: 'mybot' })
ch.on('message', async (msg) => { // { from, data, ts, id }
await ch.send(`echo: ${msg.data}`)
})
await walkie.send('mychannel:secret', 'one-shot', { id: 'sender' })listen() filters your own messages and auto-starts the daemon. send() auto-joins
and fires once — good for scripts and CI.
Agent A Agent B
┌────────┐ ┌────────┐
│ walkie │◄── P2P ────►│ walkie │
│ daemon │ encrypted │ daemon │
└────────┘ └────────┘
- Channel name + secret are hashed into a 32-byte topic
- Both agents announce/lookup the topic on the Hyperswarm DHT
- DHT connects them directly — no relay, no server
- All communication is encrypted via the Noise protocol
- A background daemon maintains connections so CLI commands are instant
walkie web
# walkie web UI → http://localhost:3000Join a channel, see messages in real-time. Browser notifications when the tab is unfocused. Secret is optional — defaults to channel name, same as the CLI. Channel state is remembered in the browser, so the same browser on the same origin can auto-rejoin after the portal restarts.
Walkie ships with a skill so AI agents can use it out of the box.
npx skills add https://github.com/vikasprogrammer/walkie --skill walkie- Reaping no longer replays history. A subscriber idle past
WALKIE_SUBSCRIBER_TTL_MS(1h) with nothing buffered is dropped, which is intended — but it discarded the read position, so the identity re-registered atlastReadTs: 0and on a persistent channel its next read returned the entire conversation as new. An agent coming back from a quiet hour would re-process and re-reply to everything. The read position now outlives the subscriber in a bounded per-channel map - The invite briefing no longer tells agents to stop after ~5 exchanges. Two live sessions parked, said "listener armed, going quiet", and stopped listening — doing exactly what they were told. A standing channel must not carry a stop condition that fires on message count; the loop now runs until the work is actually done
walkie invite [name]— mints a channel and a random secret and prints a paste-able briefing for the agents that will use it (--jointo join it yourself; it deliberately does not by default, since an inviter who never reads would satisfy another agent's--wait-for-peerand swallow the opening message).walkie invite ops:secretreprints the briefing for a channel that already exists;--token-onlyprints just the tokenwalkie next <channel>— block until another participant says something real, print it, exit. Exactlyread --wait --from-others --no-system --drain, which is the shape every agent loop wants and the one that is easiest to assemble wrong: plainread --waitreturns on your own[system] X joinednoticesend --wait-for-peer [secs]— wait for a recipient before sending, instead of losing the message. There is no offline buffering, so the first message on a fresh channel is normally dropped. On timeout it still sends, still reports "Queued nowhere" and still exits3--await-replynow implies that wait and treats its timeout as one overall deadline. Awaiting a reply on an empty channel could only ever fail- A bare channel name now warns.
walkie chat standupderives its topic from the name alone, so anyone who guesses the word joins the room. The convenience stays; the silence does not walkie watchaccepts--jsonfor the JSONL it already emits, so the flag means the same thing on every command- Removed
walkie pair— twowalkie agentinvocations in two terminals do the same job without a second orchestration path to maintain - Removed the long-dead
create/joincommands from the docs, which still described them as deprecated years after they stopped existing
walkie agent --cli pi— pi joins claude and codex as an agent backend, with conversation memory across turns via--session-id. Contributed by @rossmeyerza in #15detectCliusedwhich, which does not exist on Windows; it now useswherethere
- A daemon that crashed after startup left no trace. The spawn discarded stderr and
nothing handled uncaught errors, so the log showed a clean
Daemon startedand then silence — which is exactly what made #11 unreportable. Uncaught exceptions, rejections and daemon stderr now land in~/.walkie/daemon.log, and a swarm error no longer takes the process down - Windows: the IPC pipe name ignored
WALKIE_DIR. Every instance on a machine shared one hardcoded pipe, so isolated runs collided. It is now derived fromWALKIE_DIR, matching how the POSIX socket already behaved
- Security:
walkie webnow binds loopback by default. It previously listened on every interface, andGET /stateserves channel secrets and message history with no authentication — so anyone who could reach the port could read the secrets for every channel the UI had touched, and a secret is the entire access control for a channel. Use--host 0.0.0.0to opt back in; it warns when you do
walkie agent --cli claudeposted the raw JSON event stream instead of the reply.claude -p --output-format jsonreturns a single-line JSON array of events on current CLIs (the reply is thetype: "result"element), while older ones return a single result object. Walkie parsed line-by-line for a top-level.result, matched neither, and fell back to dumping stdout into the channel. All three shapes are handled now, and a payload that parses as JSON but carries no reply posts nothing rather than leaking the stream. Reported in #13, diagnosed in #14 by @rossmeyerza
- Documentation only. The command list had drifted to 1.5-era commands, the programmatic API was buried in a changelog bullet, and there was no guidance for the agent use case walkie exists for. No code changes
Three silent-failure bugs found by agents using walkie for real cross-machine work. Each one reduced how often a failure appeared while removing the signal that would have prompted a retry.
read --drainnever worked in the case it existed for — it read once and stopped on the first empty reply, but at the instant a waiter wakes the buffer is empty by construction, so it always returned nothing. Now polls until the channel is quiet for--settlems. Documented as a heuristic, never a completeness guaranteesend --await-replyreported "no reply" when the reply had arrived — it polled the buffer, so any other reader (including the backgroundread --waitthe docs recommend) consumed the ack first and the wait timed out while the answer sat in another process's output. Replies are now matched by the daemon at delivery time, before bufferssend --toclaimed success for a name that existed nowhere — now names the missing subscriber and exits3readreports residual depth on stderr, so a read never implies it returned everything- Published via npm trusted publishing (OIDC) — no token, no 2FA code, signed provenance
- Programmatic API —
require('walkie-sh')returnslisten()andsend(). Build bots and integrations in pure Node without spawning the CLI walkie pair <channel>— spawn two AI agents (brain + executor) collaborating on a channel. Auto-detectscodex/claudeCLIs, assigns roles, and relays output with color-coded prefixes.--tasksends an initial prompt to kick things off- Agent loop prevention — consecutive exchanges with the same sender are capped at 10, preventing infinite ping-pong between agents
- Agent @mention filtering — agents ignore messages directed at other agents via
@name, so multi-agent channels stay clean - 4 new API tests —
listen(),send(), self-message filtering, and delivery verification (test count: 49 → 53)
walkie chat <channel>— interactive terminal chat. Same channel name = same channel. Identity defaults to hostname orWALKIE_IDenv varwalkie agent <channel>— AI agent relay. Listens on a channel and responds via Claude Code or Codex CLI. Auto-detects which CLI is available, with--cli,--model,--prompt,--nameoptions. Maintains conversation memory across messages via--resume- P2P identity fix — remote peers now see the actual sender name (e.g.
vikas,my-bot) instead of a daemon hash - P2P join/leave broadcasts —
[system] alice joined/[system] alice leftnow sent to remote peers, not just local subscribers - Auto-restart daemon on update — daemon reports its version on ping; CLI auto-restarts it when a version mismatch is detected after
npm update - Consistent
channel:secretparsing — all commands (chat,agent,connect,send,read,watch) parse the colon syntax the same way - Verbose
--help— shows getting started examples, programmatic usage, identity docs, and architecture summary llms.txt— served at walkie.sh/llms.txt so AI agents can learn walkie in a single fetch- Web UI: browser notifications — desktop notifications when tab is unfocused, title badge showing unread count
- Web UI: optional secret — secret field defaults to channel name, matching CLI behavior. URL params support
?c=channelwithout requiring?c=channel:secret - Removed deprecated commands —
createandjoinremoved in favor ofconnect - Windows support — daemon IPC uses named pipes on Windows instead of Unix sockets
walkie connect— one command replacingcreate/join. Format:walkie connect channel:secret. No colon = secret defaults to channel namewalkie watch— stream messages in real-time. JSONL by default,--prettyfor human-readable,--exec <cmd>to run a command per message with env vars (WALKIE_MSG,WALKIE_FROM,WALKIE_TS,WALKIE_CHANNEL)- Auto-connect —
sendandreadacceptchannel:secretformat, auto-joining before the operation - Join/leave announcements —
[system] alice joined/[system] alice leftdelivered to all subscribers when agents connect or disconnect - Stdin send —
echo "hello" | walkie send channel— reads message from stdin when no argument given, avoids shell escaping issues - Shell escaping fix —
\!automatically unescaped to!in sent messages (works around zsh/bash history expansion) - Web UI —
walkie webstarts a browser-based chat UI with real-time messages, renameable identity, and browser-local persistence across page reloads and same-origin restarts - Deprecation notices —
createandjoinstill work but print a notice pointing toconnect - Persistent message storage — opt-in via
--persistflag onconnect/watch/create/join. Messages saved as JSONL in~/.walkie/messages/. No flag = no files, zero disk footprint - P2P sync — persistent channels exchange missed messages on peer reconnect via
sync_req/sync_resp, with message deduplication via unique IDs - TTL-based cleanup — persistent messages expire after 24h by default (configurable via
WALKIE_TTLenv in seconds), compacted on startup + every 15min
- Simplified CLI — removed
--asflag,WALKIE_IDenv var is the only explicit identity option - Stale daemon recovery — cleans up stale socket/PID files before spawning, better error messages
- Auto-unique subscriber IDs — each terminal session gets a unique ID automatically. Same-machine agents just work with no setup
--waitblocks indefinitely —walkie read --waitblocks until a message arrives. Add--timeout Nfor a deadline
- Same-machine multi-agent routing — per-subscriber message buffers, senders never see their own messages (identity-scoped: see
walkie whoami) walkie statusshows subscriber count,walkie leaveonly tears down P2P when all subscribers leave
MIT
