feat(session-relay): cross-session/cross-project + cross-tool (Codex↔Claude) agent message bus#3
Merged
Conversation
…ugin Add session-relay, a second (Claude-only) marketplace plugin that lets an agent in one Claude Code session message — and get a reply from — an agent in another session or project, keyed by session id. - lib/store.mjs: shared on-disk registry + per-recipient JSONL mailboxes + cwd→id markers (mkdir-lock + atomic writes, zero deps) - mcp/bus.mjs: zero-dep MCP stdio server (whoami/register/roster/send/inbox) - hooks/session-start.mjs: auto-register + inbox drain via additionalContext - skills/productivity/session-relay: skill (scores 15/16) + relay.mjs doorbell CLI - test/selftest.mjs: claude-free MCP-handshake + store + hook test (11 checks) - ci.mjs: self-contained session-relay validation block - marketplace.json + AGENTS.md: register the 2nd plugin Transport is headless `claude -p --resume`, scoped to the recipient's project dir. Claude-only by design (uses the claude CLI + a plugin MCP server, which Codex does not consume); self-versioned (0.1.0). docks' release.mjs is unchanged — release separately via `claude plugin tag ./plugins/session-relay`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ool-aware doorbell - store home defaults to ~/.agent-relay (AGENT_RELAY_HOME, SESSION_RELAY_HOME alias) - registry entries carry a tool field (claude|codex; default claude) - relay.mjs wake dispatches per target.tool; --dry prints the command - selftest covers tool field, home precedence, and doorbell dispatch (15 checks) Claude path behavior-identical; prepares the bus for a Codex peer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- shared SessionStart hook (session-start.mjs takes a tool arg) — Codex's
SessionStart contract is identical to Claude's, so one script serves both
- hooks/codex-hooks.json invokes it with codex; tags register({tool:'codex'})
- bus.mjs ignores an unsubstituted ${...} project-dir env (Codex static config)
and falls back to process.cwd()
- .codex-plugin/plugin.json + .codex-plugin/bus.mcp.json (skills + hooks + MCP);
session-relay added to the Codex marketplace catalog
- SKILL.md documents the cross-tool model, two doorbells, Codex install
- ci.mjs gains 5 Codex-parity checks (codex manifest/version/marketplace/
hooks.json/bus.mcp.json JSON-valid)
Verified live (codex 0.142.2 + claude): bidirectional round-trip — a real Codex
session and a real Claude session register on one store and exchange
message+reply both ways. selftest 15 checks; ci.mjs green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tool manifest descriptions - plan-review completion verdict: review_status=passed, Goal met, no regressions - resolve the one follow-up inline: Claude-side .claude-plugin/plugin.json + .claude-plugin/marketplace.json descriptions/keywords/tags now read cross-tool (Claude Code + Codex), matching the Codex manifests Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…istration) Closes the gap where the bus could only reach pre-registered sessions and roster had no liveness signal. New lib/discover.mjs scans the RAW on-disk session stores so an agent can auto-resolve "my other running session": - Claude: <root>/<enc-cwd>/<id>.jsonl — id=filename, cwd read from file CONTENT (the dir name is a lossy '-' encoding), mtime=liveness. - Codex: rollout-*.jsonl session_meta line -> payload.id + cwd. Surfaced as an MCP "discover" tool + "relay.mjs discover"; works even for sessions that never joined the bus. Reach an unregistered one with "relay.mjs wake --id <uuid> --dir <cwd> --tool <t> -- '<msg>'" (the message rides the resume prompt inline). Decisions: full-scan (not registry-only) + auto-pick most-recent/cwd-relevant, confirm only when ambiguous. Hardened after a multi-lens adversarial-review workflow (14/21 findings confirmed, each independently verified): - UUID-validate ids — drop planted/flag-shaped ids and reject a non-UUID --id so they can't reach the spawned doorbell argv as injected options. - stat-gate the content read by the liveness window before opening files (cost proportional to live sessions, not total history). - isFile guard on the Claude scan; "--"-separator inline-message parsing; Number.isFinite --within guard; head-read pop guard; refreshed MCP initialize instructions string. Documented limit: same-cwd self-pick (needs a host->bus session id MCP doesn't expose) — the skill says to verify a candidate isn't self. Verified: selftest 28 checks; node scripts/ci.mjs green; live — a brand-new plugin-less claude session was discovered from disk and answered with its own context via the inline doorbell. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make ci.mjs and release.mjs host multiple plugins from one repo instead of hardcoding docks. scripts/lib/plugins.mjs is the single source of truth: a PLUGINS array of descriptors (paths + capabilities). Adding a plugin = adding one descriptor; no edits to ci.mjs/release.mjs. - ci.mjs: registry-driven. Repo-wide checks run once (workflow YAML, both marketplace catalogs, tree/guard, idempotency, shellcheck, scaffold), then a capability-driven per-plugin gate (gatePlugin) runs for each present plugin -- a check fires only when its capability is declared (skills/agents/codex/ selftest/extraJson/transformGuard). Flags: -q, --list, --plugin name. Replaces the old docks-hardcoded sections + bolted-on session-relay block. - release.mjs: registry-driven, single-plugin. --plugin name (default docks) bumps only that plugin's manifests + its marketplace entry (matched by name); tag = name--vVER; commit = chore(release): name vVER. --dry-run previews. Versions are per-plugin and independent. - no-author-scripts.mjs: takes skills-dir [agents-dir] so gatePlugin scopes it per-plugin (agents scanned only when given) -- closes the gap where session-relay skills were never author-script-checked. - session-relay plugin.json: normalize to canonical 2-space JSON so release bumps produce version-only diffs (no semantic change). - scripts/AGENTS.md: document the multi-plugin model, flags, per-plugin versioning. session-relay now gets MORE coverage than the old bolted-on path (category layout, no-author-scripts, trigger-collision). CI green: 2 plugins + repo-wide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… + mail hardening, tests A prior-art survey (14 verified projects: mcp_agent_mail, claude-peers-mcp, AMQ, cc-to-cc, Anthropic Agent Teams, openai/codex resume issues, et al.) confirmed all six load-bearing behaviors and surfaced concrete gaps. Fixes: - discover.mjs: honor the tools' own relocation env vars. Roots were hardcoded to ~/.claude/projects and ~/.codex/sessions, ignoring CLAUDE_CONFIG_DIR and CODEX_HOME — a relocated user got a silent "no sessions found". Now CLAUDE_CONFIG_DIR -> <dir>/projects and CODEX_HOME -> <dir>/sessions, with RELAY_* still overriding outright. (real correctness bug) - relay.mjs wake: refuse to resume into a target dir that no longer exists. A stale/moved registration would otherwise resume from an unexpected cwd, and Codex widens its sandbox writable roots to the caller cwd. Guard before spawn. - session-start.mjs: structurally fence injected mail. Bodies come from other (untrusted) writers; wrap them in a labelled <session-relay-mail> UNTRUSTED block instead of relying on prose guidance alone. SKILL.md updated to match + state the single-user trust boundary (no store auth); content_hash rebumped. - selftest.mjs: +10 checks (28 -> 38) for the coverage gaps prior art proved matter: concurrent multi-writer stress (no lost/torn JSONL, intact registry), stale-lock reclaim + fresh-lock fail-fast deadline, discovery format-fragility canary (malformed/cwd-less/empty), path-traversal sanitize, doorbell argv safety, CLAUDE_CONFIG_DIR/CODEX_HOME honoring, and the untrusted-mail fence. ci.mjs green: 2 plugins + repo-wide. selftest: 38 checks pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on, stale-lock Merge-review of PR #3 surfaced two confirmed injection-hardening gaps plus two stale-lock robustness bugs; fix all four and correct a doc mislabel. - session-start.mjs: defuse the </session-relay-mail> sentinel in mail body AND sender name before fencing, so a peer message can't close the block early and smuggle text out as trusted prose (reproduced breakout). - relay.mjs: put the untrusted message after a `--` end-of-options marker in both child argvs, and UUID-validate target.id on the resolved-name path, so a dash-leading body/id can't become a flag on the spawned claude/codex. - store.mjs: bound stale-lock reclaim by the deadline (no busy-spin hang) and reclaim atomically via rename so two racers can't both enter the mutex; fix the header comment that overstated mailbox atomicity. - AGENTS.md: session-relay is cross-tool (Claude + Codex), not Claude-only. - selftest.mjs: +2 checks (fence closing-tag payload; doorbell `--` fencing); 38 -> 40, all green. node scripts/ci.mjs green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UyDDEDsq6yGeJKzNAwW3ZQ
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
session-relay, a second plugin in the docks marketplace: a cross-session, cross-project, cross-tool agent message bus. One agent can hand a message to — and get a reply from — an agent in another session, another project, or another tool (Claude Code ⇄ Codex), over a single shared on-disk store.This PR folds two phases:
bus) exposingwhoami/register/roster/send/inboxover a shared store, aSessionStarthook that auto-registers each session and drains its inbox into context, and arelay.mjsdoorbell that wakes an idle recipient via headlessclaude -p --resume.toolfield, the doorbell dispatches per-tool (claude -p --resumeorcodex exec resume), theSessionStarthook is shared (Codex's contract is byte-identical to Claude's), and a.codex-plugin/manifest + Codex marketplace entry ship it to Codex.Why
Native Claude Code has no general live session-to-session channel across projects (Agent Teams can't span sessions; Channels are single-session, research-preview). The session id is the routing key; the transport is built from three confirmed primitives — an MCP server over a shared store, a
SessionStarthook for auto-register + inbox-drain, and a headless-resume doorbell to wake idle recipients. Codex exposes the same three primitives (codex exec resume, a matchingSessionStarthook,codex mcp), so the bus spans both tools.How it works
Delivery is pull + event, never a live push: a recipient sees mail when it calls
inboxor at its nextSessionStart;sendreaches an idle session only after the doorbell wakes it.Verification
node scripts/ci.mjs— green (adds a self-containedsession-relaysection with 5 Codex-parity checks; the rest of CI stays scoped toplugins/docks).node plugins/session-relay/test/selftest.mjs— 15 checks (store round-trip, MCP handshake, hook inject/drain, tool-field, doorbell dispatch selection,AGENT_RELAY_HOMEprecedence).MANGO); Codex→Claude had the Codex agentrelay.mjs senda message that was delivered into the Claude session via its hook (echoedPAPAYA-FROM-CODEX).Notes for reviewers
tooltag and a second doorbell branch.release.mjsis intentionally not extended — it stays scoped toplugins/docks;session-relayself-versions (its manifest triple is gated inci.mjs).docs/plans/active/session-relay-cross-tool-bus.md.Plan & completion review
Tracked end-to-end in
docs/plans/active/session-relay-cross-tool-bus.md(11/11 stepsdone). An independentplan-reviewcompletion pass diffedplanned_at_commit..HEADagainst the goal and setreview_status: passed— Goal met, no regressions to the Claude-only path, CI green. Its one follow-up (Claude-side manifests still said "Claude Code only") is resolved in this PR.🤖 Generated with Claude Code