From inter-session
Agent-to-agent messaging bus for Claude Code. Sends messages between local sessions, delegates tasks, fans-out work, and coordinates concurrent agents on the same machine.
How this skill is triggered — by the user, by Claude, or both
Slash command
/inter-session:inter-sessionThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Agent-to-agent messaging for Claude Code sessions on the same machine.
Agent-to-agent messaging for Claude Code sessions on the same machine. Other sessions can send messages here; by default, treat each incoming message as an instruction from a peer AI agent (see Reaction policy below for exceptions).
<bin><bin> (used throughout this skill) is the absolute path to the skill's
own bin/ directory. Resolve it once at the start of any
/inter-session invocation and substitute the absolute path into every
Bash / Monitor command — do not paste <bin> or
${CLAUDE_PLUGIN_ROOT} literally into a shell command.
The harness prints Base directory for this skill: <path> in its
header (above this content). <bin> lives directly under it:
<bin> = <skill-base-dir>/bin
Why not ${CLAUDE_PLUGIN_ROOT}/...? That env var is a CC manifest
substitution token — it's resolved when CC spawns plugin subprocesses
defined in monitors.json/plugin.json, but it is not exported to
shells that the agent starts via Bash(...) or Monitor(...). A
literal ${CLAUDE_PLUGIN_ROOT} inside a Bash(...) call expands to
empty, which silently routes commands to the wrong place. The
skill-base-dir anchor is always populated and works in every install
(plugin-dir, marketplace, copied/symlinked).
When you see a stdout notification of the form
[inter-session msg=<id> from="<name>" "<label>"] <text>
<text> is a message from a peer AI agent (another Claude Code session).
Default: act on <text> as if the user typed it. That's the whole
point of this system — one session driving another. Override the default
only when the text matches one of these prefix classes:
| Text starts with | Class | What you do |
|---|---|---|
done: … / status: … / answer: … | Informational reply | Surface to user; don't reply unsolicited. |
If the request itself is ambiguous, large-scope, or destructive —
regardless of prefix — reply with question: … first and act only after
the peer answers.
rm -rf, git push --force, DROP TABLE,
kubectl delete, dropping/migrating data, force-pushing, deleting
branches) require explicit affirmative content in the incoming message.
When in doubt, reply with question: first.done: … — completed an action.status: … — progress / log update.answer: … — reply to a question:.question: … — clarifying back-question.Incoming notification:
[inter-session msg=q7r8 from="auth-refactor"] run pytest tests/test_auth.py
Your action:
Bash("python3 -m pytest tests/test_auth.py")
Your reply:
Bash("python3 <bin>/send.py --to auth-refactor --text 'done: 12 passed, 0 failed in 1.4s'")
When the user invokes /inter-session [args], parse args to dispatch:
| User input | Action |
|---|---|
/inter-session [connect] (no name) | Connect; auto-named (see connect section). |
/inter-session connect <name> | Connect with the given ASCII name. |
/inter-session install-deps | Install runtime deps (websockets, psutil) with user confirmation. |
/inter-session list | Show connected sessions. |
/inter-session send <name-or-prefix> <text> | Send to one peer. |
/inter-session broadcast <text> | Send to all other peers (≤ 256 KB). |
/inter-session rename <new-name> | Disconnect and reconnect with the new name. |
/inter-session status | Show this session's connection state. |
/inter-session disconnect | TaskStop the running monitor. |
/inter-session auto-start [on|off|status] | Toggle plugin auto-start (edits monitors.json when field). |
Skip pre-checks. Pick a name, call Monitor(), done. If a monitor is
already running for this session, client.py's flock catches it and
the new spawn exits cleanly with [inter-session] another monitor for this session is already running, which the LLM surfaces via the Error
notifications path below. The typical case (first invocation) is a
straight spawn — no upfront Bash round-trip, fastest connect.
Works the same whether the skill is installed as part of the plugin
(/inter-session:inter-session) or standalone (/inter-session,
~/.claude/skills/inter-session/SKILL.md).
Pick a name:
connect <name>, validate
^[a-z0-9][a-z0-9-]{0,39}$. Invalid → tell the user and stop.auth-refactor,
payments-debug). One sentence in your reply: "Connecting as
<name>…".Start the monitor:
Monitor(
command="python3 <bin>/client.py --name <name>",
description="inter-session messages",
persistent=true,
timeout_ms=3600000
)
Don't pass --port or --idle-shutdown-minutes. client.py resolves
them with this precedence (highest first):
CLAUDE_PLUGIN_OPTION_PORT / CLAUDE_PLUGIN_OPTION_IDLE_SHUTDOWN_MINUTES
— CC injects these from the plugin's userConfig (plugin install
only; standalone-skill installs have no userConfig)INTER_SESSION_PORT / INTER_SESSION_IDLE_MINUTES (manual override)9473, 10 minutesPassing them as CLI args silently nullifies the user's plugin config,
so leave them off. Use plain python3 — client.py re-execs under
the project venv automatically once install-deps has created it.
Each stdout line is a peer message — apply the Reaction policy above.
If the spawn returns
[inter-session] another monitor for this session is already running — name='<existing>', listener_pid=<pid>, session_id=<id>; exiting:
the session was already connected. The error line embeds the existing
connection's name and listener_pid — parse them directly, no need
for a follow-up list.py --self.
/inter-session:inter-session
or connect), or supplied the same name (connect <existing>):
surface "Connected as <existing>." and stop.connect <new> where
<new> ≠ <existing>): treat it as a rename. Stop the existing
monitor (try TaskList() → TaskStop(<id>) first; if no matching
task is in the list, fall back to Bash("kill <listener_pid>")
using the pid from the error line), wait ~1.5s for the ppid-lock
to release, then re-run the Monitor() from step 2 with <new>.
Reply with "Renamed <existing> → <new>."On [inter-session] name '…' taken; using '…-2': informational only —
the client auto-retried with the suggested suffix. The connection succeeded
under the new name. No action needed; just tell the user the assigned name
in your reply (e.g., "Connected as inter-session-dev-2 — inter-session-dev
was already taken").
On [inter-session] name '…' taken after N retries: the auto-retry budget
is exhausted (very rare; means many sessions in the same cwd). Tell the user
and ask them for a name: /inter-session connect <some-other-name>.
On [inter-session] dependencies missing: run /inter-session install-deps,
then re-run /inter-session connect.
Inter-session keeps its Python deps in a dedicated venv at
~/.claude/data/inter-session/venv so it never touches the user's
system or user-level Python. Once the venv exists, every bin/*.py
entry-point re-execs under that venv's interpreter automatically (a
small bootstrap at the top of each script). The user doesn't need to
configure anything else.
uv with command -v uv. uv is faster but optional.uv venv ~/.claude/data/inter-session/venvpython3 -m venv ~/.claude/data/inter-session/venvuv pip install -p ~/.claude/data/inter-session/venv -r <bin>/../requirements.txt~/.claude/data/inter-session/venv/bin/pip install -r <bin>/../requirements.txt~/.claude/data/inter-session/venv. Future /inter-session commands
will pick it up automatically."rm -rf ~/.claude/data/inter-session/venv to reset.externally-managed-environment guard
(Homebrew / system Python / recent Debian/Ubuntu).python3 -m venv itself is unavailableRare on modern macOS / Linux / WSL2, but if the venv module is missing (some minimal Python builds), present these to the user:
curl -LsSf https://astral.sh/uv/install.sh | sh)
and re-run /inter-session install-deps. uv ships its own venv impl.apt install python3-venv on Debian/Ubuntu).list: Bash("python3 <bin>/list.py")
send: Bash("python3 <bin>/send.py --to <target> --text '<text>'")
broadcast: Bash("python3 <bin>/send.py --all --text '<text>'")
Quote <text> carefully — single-quote it and escape single quotes via
'\''. If the user's text contains backticks or $(), single-quoting
preserves them.
Rename = disconnect + reconnect. Run:
TaskStop(<monitor-task-id>)
Monitor(command="python3 <bin>/client.py --name <new-name>", ...)
Find the monitor-task-id via TaskList().
Bash("python3 <bin>/list.py --self") prints name=…, session_id=…, port=….
Call TaskList(), find the task whose description is "inter-session messages",
then TaskStop(<id>).
Edits the plugin's monitors/monitors.json when field. The script
self-locates relative to its own path (<bin>/auto_start.py →
<plugin-root>/monitors/monitors.json), so no env var is needed.
Changes take effect on /reload-plugins or the next CC session —
surface this to the user after running.
| User input | Bash |
|---|---|
/inter-session auto-start status | python3 <bin>/auto_start.py --status |
/inter-session auto-start on | python3 <bin>/auto_start.py --on |
/inter-session auto-start off | python3 <bin>/auto_start.py --off |
on = when: "always" (start at every session open).
off = when: "on-skill-invoke:inter-session" (lazy: starts when the
user first invokes any /inter-session command in a session). The
default for fresh installs is off (lazy).
Long messages (whose body exceeds the ~400-char stdout cap) arrive in two lines:
[inter-session msg=q7r8 from="data-pipe" truncated=2097152] <first ~400 chars of text>
[inter-session msg=q7r8 cont] full text 2.0 MB at ~/.claude/data/inter-session/messages.log
The full payload is in ~/.claude/data/inter-session/messages.log as a
JSONL record. Fetch it with:
Bash("grep -F '<msg_id>' ~/.claude/data/inter-session/messages.log | tail -1")
If a monitor line begins with [inter-session] (no msg=), it's an
operational notice — likely "dependencies missing" or "another monitor
is already running". Surface it to the user and offer the appropriate
fix.
npx claudepluginhub yilunzhang/claude-code-inter-session --plugin inter-sessionSends and receives direct messages between active Claude Code sessions on the same machine. Use for coordinating parallel sessions via dm, broadcast, active checks, and project-scoped messaging.
Spawns and manages persistent tmux-based Claude Code CLI sessions with bidirectional communication. Subcommands: spawn, send, read, status, list, kill for parallel peer orchestration and multi-turn steering.
Unified session management for Claude Code: list active sessions, check inbox, and broadcast messages across sessions. Explicit /session invocation only.