Imported from fl4p/agent-channel (
codex/channel/SKILL.md). Install upstream withnpx skills add fl4p/agent-channel --skill channel. Copyright stays with the author.
Channel
Use a shared append-only NDJSON file to talk with another agent in a separate session. This is compatible with Codex, Claude Code, and OpenCode channel skills: all agents share /tmp/claude-channels/<channel>.ndjson, and each participant has its own cursor.
User Interface
The user should invoke this as a skill, for example $channel vrp gpt, or ask in
natural language to join, watch, send on, poll, or leave a channel. Do not ask
the user to run the Python helper. The helper commands below are internal
implementation details for the agent to execute.
Important Model
Agents are turn-based. A channel watcher only enters context while the agent is idle if the host can wake the agent from background command output.
Codex with
exec_command.wake_on_output: usestream. This is the local fork path for https://github.com/openai/codex/issues/22003. The tool may be shown asexec_commandor namespaced likefunctions.exec_command; thewake_on_outputparameter is decisive. Launchpython3 <HELPER> stream <channel> <agent>withwake_on_output: true,tty: true, and a shortyield_time_ms.streamprints one flushed line per peer message and keeps running, so Codex is re-entered by synthetic background-output user messages. No re-arm is needed. Do not also run foregroundpoll/listen/waitwhilestreamis live because they share the same cursor. Keep the returnedsession_id. If the user stops watching without leaving, stop it withwrite_stdinCtrl-C ("\u0003"). Onleave, the helper writes a cooperative stop marker before announcing departure, so bundled streams exit without replaying the transcript; use Ctrl-C only if the host still reports the stream as live afterward.Stock Codex: use foreground
listen, not backgroundwait. If thewake_on_outputparameter is absent, Codex cannot receive asynchronous channel updates while idle. Drive the channel with foregroundlisten --timeout 30and re-run it while waiting.waitstill works if you actively block on it in the turn, but launching it in the background will not wake Codex.
Do not use watch-start for a normal "watch in the background" request.
watch-start is log/desktop-notification only and does not put messages into
Codex context. Use it only if the user explicitly asks for desktop/log-only
monitoring or accepts that Codex will not be re-entered.
While joined to a live channel without an armed stream, treat listening as active work. Use an explicit bounded timeout such as --timeout 30 so the command returns cleanly in turn-based harnesses. Do not stop after one empty poll if the user is waiting for the peer; run another listen --timeout 30 call unless the user asks you to stop, the peer leaves, or the channel task is clearly complete.
Shell variables do not persist between tool calls in many agent harnesses. Resolve the channel name and your agent name once, tell the user which name you adopted, then pass those literal values to every helper command.
Arguments
Infer these from the user's request:
channel: required. If missing, ask the user for the channel name and stop.agent name: optional. If missing, choose a readable unique name such as<agent>-<cwd-basename>-<short-random>.
The two agents on a channel must use different agent names. If a generated name might collide, ask the user for an explicit one.
Fork/collision safety + newer flags. setup stamps a per-session instance-id and, if a different live session already holds the requested name (e.g. a forked session that inherited it), prints a WARNING and auto-adopts a unique name — use the name setup prints. Two instances that share a session id AND the CLAUDE_CODE_CHILD_SESSION marker still need a unique name or a distinct CLAUDE_CHANNEL_IID. To send a message containing shell metacharacters (backticks, parens, globs, $), use send … --stdin and pipe/heredoc the body (printf '%s' "$msg" | … send ch me --stdin) so the caller's shell can't execute them; a lone -/--stdin must be the only token or it errors. On a busy channel where peers come and go, stream keeps watching through peer leaves by default; pass --stay to wait if one-shot wait should also keep watching after a leave.
Agent-Internal Helper
<HELPER> is the bundled scripts/channel.py shipped alongside this SKILL.md.
Resolve its absolute path once and pass that literal path to every command
below (shell variables do not persist between tool calls in many harnesses, so
do not rely on an exported $HELPER).
Use the bundled helper internally:
python3 <HELPER> <command> <channel> <agent> [args...]
Commands:
# Create the channel file and set this agent's cursor to the current end.
python3 <HELPER> setup <channel> <agent>
# Append a JSON message. The helper handles JSON escaping and newlines.
python3 <HELPER> send <channel> <agent> "hello"
# For arbitrary text, bypass shell interpolation entirely.
printf '%s' "$message" | python3 <HELPER> send <channel> <agent> --stdin
# Read existing transcript without moving the cursor.
python3 <HELPER> history <channel> <agent>
# Wait briefly for new peer messages, then print them and advance the cursor.
python3 <HELPER> poll <channel> <agent> --timeout 30
# Listen for live peer messages. Use this after joining, after sending, or whenever
# the user expects a response. On macOS this is filesystem-event backed.
python3 <HELPER> listen <channel> <agent> --timeout 30
# Block until a peer message arrives, then exit. Use in the background only when
# the harness re-invokes the agent after background command completion.
python3 <HELPER> wait <channel> <agent>
# Stream peer messages forever, one line per message. Use with Codex
# exec_command wake_on_output.
python3 <HELPER> stream <channel> <agent>
# Start a zero-inference watcher in the background. It writes a watch log and,
# on macOS, posts desktop notifications for peer messages without advancing the
# normal poll/listen cursor.
python3 <HELPER> watch-start <channel> <agent>
# Inspect or stop the background watcher.
python3 <HELPER> watch-status <channel> <agent>
python3 <HELPER> watch-log <channel> <agent> --lines 20
python3 <HELPER> watch-stop <channel> <agent>
# Stop this agent's streams, announce departure, and preserve its cursor until setup.
python3 <HELPER> leave <channel> <agent>
The helper prints peer messages as [from] text. It skips messages from the current agent and advances the cursor past all seen lines, including self messages. If the channel file is reset, the cursor recovers from the beginning. listen, wait, stream, and watch-start use filesystem events on macOS and only use the --interval value as a fallback when filesystem events are unavailable.
stream is the Codex output-wake path when wake_on_output exists. It shares
the same cursor as poll/listen/wait, so do not use foreground receives
while it is live. leave asks every bundled stream under the same channel and
agent name to exit before it appends the departure event.
watch-start is a separate log/desktop-notification daemon. It runs outside
inference, skips this agent's own messages, writes
/tmp/claude-channels/<channel>.<agent>.watch.log, and uses a separate
watch.cursor so later foreground poll/listen calls still see unread
messages. It does not wake Codex or enter model context; use it only when the
user explicitly asks for external log/desktop monitoring.
Workflow
When joining a channel:
- Confirm the channel name. Resolve the agent name and report it.
If no explicit name was given, generate a unique one using the helper:
ME=$(python3 <HELPER> name 2>/dev/null | tail -n 1) echo "me=$ME" - Run
setup. - Send
hello. - Run
historyonce and summarize any existing peer messages. - If
exec_command.wake_on_outputis available, startstreamwithwake_on_output: true, record the returnedsession_id, and end the turn. Otherwise run foregroundlisten --timeout 30; do not substitutewatch-startunless explicitly requested as log-only monitoring. - Continue turn by turn:
- When a Codex background-output wake arrives from
stream, show the peer messages fromOutput:and respond as requested. The stream stays armed; do not re-arm it. - When the user gives a message, send it. If
streamis armed, end the turn after sending; otherwise listen again. - When foreground
listenreturns peer messages, show them to the user and respond as requested. - When
listentimes out and the user is waiting for the peer, runlistenagain. - If a peer message is
left the channel, report that the peer left. Keep a livestreamarmed only when channel work continues (for example, other peers remain); if the task is complete, leave the channel so the stream exits. - Before answering "no response" or ending the turn, check the channel one more time.
- When a Codex background-output wake arrives from
If the user explicitly asks for desktop/log-only monitoring and
wake_on_output is unavailable, start watch-start, tell them it only
notifies/logs externally, and end the turn. On a later turn, run watch-log or
foreground listen before answering.
Leaving
Treat these user messages as leave commands: leave, leave the channel, exit, quit, stop watching, /leave, /exit, /quit, disconnect, close the channel, done, bye, goodbye.
On a leave command, run leave, report that you left, and stop polling. The
helper signals live bundled streams before appending the departure message and
does not delete the cursor. Deleting or resetting a cursor while a receiver
is live can replay the entire transcript. If the host still shows a stream after
leave, stop that session with the host's control (write_stdin Ctrl-C in
Codex) before reusing the same agent name.
Protocol Details
The shared transcript lives at:
/tmp/claude-channels/<channel>.ndjson
Each line is:
{"from":"agent-name","ts":1234567890,"text":"message text"}
Each agent's cursor lives at:
/tmp/claude-channels/<channel>.<agent>.cursor
leave preserves this cursor; the next setup resets it to the current channel
end. This prevents a late-running receiver from treating a missing cursor as
position zero.
Keep messages concise and single-purpose. For long code, summaries, or diffs, send a short description and let the user decide whether to relay details.
