Let two or more AI coding agents — Claude Code, Codex, or OpenCode — talk to each other across separate sessions over a shared, file-based named channel.
No server, no daemon, no API keys. Agents append JSON lines to
/tmp/claude-channels/<channel>.ndjson and each keeps its own durable cursor.
On macOS/BSD the receive path blocks on kqueue filesystem events, so an agent
can wait for a peer message with zero CPU and zero model inference until
something actually arrives — then wake exactly once.
agent A ──send──▶ /tmp/claude-channels/demo.ndjson ◀──wait/listen── agent B
▲ │
└──────────────────────────── replies ─────────────────────────────────┘
A single Python helper (scripts/channel.py, identical across all three) plus a
harness-specific SKILL.md that teaches the agent how to drive it:
| Harness | Skill source |
|---|---|
| Claude Code | plugins/channel/skills/channel (also installable as a plugin) |
| Codex | codex/channel |
| OpenCode | opencode/channel |
Two agents on different harnesses interoperate as long as they share the same
/tmp/claude-channels/<channel>.ndjson path.
/plugin marketplace add fl4p/agent-channel
/plugin install channel@agent-channel
Then just ask: "go on channel demo as alice and watch it".
git clone https://github.com/fl4p/agent-channel ~/agent-channel
ln -s ~/agent-channel/plugins/channel/skills/channel ~/.claude/skills/channelgit clone https://github.com/fl4p/agent-channel ~/agent-channel # if not already
ln -s ~/agent-channel/codex/channel ~/.codex/skills/channelgit clone https://github.com/fl4p/agent-channel ~/agent-channel # if not already
ln -s ~/agent-channel/opencode/channel ~/.config/opencode/skills/channelReuses the OpenCode skill.
git clone https://github.com/fl4p/agent-channel ~/agent-channel # if not already
ln -s ~/agent-channel/opencode/channel ~/.pi/agent/skills/channelOptional, install the fl4p/pi-bash-background
extension for non-blocking background listening:
git clone https://github.com/fl4p/pi-bash-background ~/pi-bash-background
ln -s ~/pi-bash-background/src/index.ts ~/.pi/agent/extensions/bash-background.tsAsk the agent in natural language — it invokes the skill itself:
- "open channel
demoasaliceand tell me when the other agent says something" - "send 'build is green' on channel demo"
- "watch channel demo in the background"
- "leave the channel"
You never run channel.py by hand; the skill drives it for the agent.
stream(preferred for Codexwake_on_output, Claude CodeMonitor, and OpenCode monitor). Launched under a host that wakes on background command output. It keeps running, blocks on real filesystem events, and prints one flushed line for each peer message. Codex support is the localexec_command.wake_on_outputfork for openai/codex#22003.wait(preferred for background-exit hosts, 0-token). Launched as a background command in harnesses that re-invoke the agent on background-command exit (Claude Code). It blocks on real filesystem events until a peer message lands, prints it, exits — waking the agent exactly once with zero idle CPU:kqueueon macOS/BSD,inotifyon Linux (glibc and musl/Alpine). Windows, and any host where neither watcher can be set up, fall back to a short bounded sleep poll — same behavior, just a little idle CPU instead of true event blocking.listen --timeout 30(portable). Foreground bounded listen for harnesses without background output/exit wake-up. Re-run while actively waiting.watch-start(legacy). A detached watcher that only logs and posts desktop notifications; it never wakes the agent on its own.
Two-party assumption.
waitandstreamtreat anyleft the channelmessage as terminal and stop watching. This is correct for the usual two-agent channel; on a channel with three or more participants, the first departure ends the watch even if other peers are still active. Re-arm if you need to keep following a multi-party channel after a peer leaves.
Harness support. Stock Codex still needs foreground
listen; the local Codex fork adds output wake-up viaexec_command.wake_on_output, so it should usestream, not backgroundwait. Claude Code supports background-exitwait. Upstream OpenCode needs monitor/background tooling; the local fork and PR anomalyco/opencode#33806 add that path. Messaging itself works everywhere regardless.
Shared transcript — append-only NDJSON, one JSON object per line:
/tmp/claude-channels/<channel>.ndjson
{"from":"alice","ts":1234567890,"text":"hello"}
Each agent tracks its position in a sibling cursor file
(/tmp/claude-channels/<channel>.<agent>.cursor) so nothing is seen twice and
agents never re-read their own messages.
Pure Python 3 standard library, no third-party deps.
| OS | Messaging (send/listen/wait/leave) |
Wake mechanism | watch-* daemon |
Desktop notifications |
|---|---|---|---|---|
| macOS | ✅ | kqueue events (0 CPU) |
✅ | ✅ (osascript) |
| Linux (glibc) | ✅ | inotify events (0 CPU) |
✅ | — (no-op) |
| Linux (musl/Alpine) | ✅ | inotify events (0 CPU) |
✅ | — (no-op) |
| Windows | ✅ | bounded sleep poll | ✅ | — (no-op) |
wait/listen block on native filesystem events with zero idle CPU on macOS
(kqueue) and Linux (inotify). Anywhere a watcher can't be set up — Windows,
or an exotic host — they degrade to a short bounded sleep poll: identical
behavior, just a little idle CPU.
Notes:
- The channel directory is
/tmp/claude-channelson macOS/Linux and%TEMP%\claude-channelson Windows. Set theCHANNEL_DIRenvironment variable to override it — required only if two agents would otherwise compute different paths (e.g. a macOS and a Windows agent on the same host). - Desktop notifications (
--desktop) are macOS-only; elsewhere they silently no-op and the channel still works. - An earlier MCP-broker implementation of this idea is deprecated in favor of the file-based approach here — no extra process, no polling, instant wake.
Tested on: macOS (kqueue), Linux glibc (python:3-slim) and musl
(python:3-alpine) — both confirmed holding an inotify fd at zero idle CPU and
waking on a peer send — and Windows Python (via Wine: %TEMP% path, ctypes
pid_alive, and the detached watch-* daemon all verified).
MIT — see LICENSE.