feat(cli): relay init injects messages through the Agent Relay core (probe phase 3)

Merged
KH
5d ago
+3892-192

Phase 3 of moving the desktop core into relay (stacked on #2013, which is stacked on #2012). relay init now gives agents real message injection into running sessions through the core (agent-relay-probe), instead of MCP inbox polling. See docs/probe-move.md ("Phase 3").

What changed

Release artifacts — .github/workflows/probe-release.yml (dispatch only; dry_run defaults to true; also runs dry on PRs that touch it). Builds agent-relay-probe with AGENT_RELAY_PROBE_VERSION=<version> for linux-x64/arm64 (glibc, Ubuntu 22.04), darwin-arm64, darwin-x64 (cross-built) and windows-x64, smoke-tests --version, and publishes probe-v<version>/agent-relay-probe-<version>-<os>-<arch>[.exe] plus .sha256 sidecars (<hex> <asset>). probe-v* tags never collide with the CLI's v* tags, and the release is --latest=false because install.sh resolves the CLI via releases/latest. No release was cut.

Install — the CLI downloads the pinned core (PROBE_VERSION = 15.0.0) into the canonical bin dir (AGENT_RELAY_INSTALL_DIR → AGENT_RELAY_HOME → ~/.agentworkforce/relay/bin, new getCanonicalProbePath) the same way it installs relaycast-engine (generalized into ensureReleaseBinary: checksum first, SHA-256 verified, temp file, --version check, atomic rename). AGENT_RELAY_PROBE_PATH overrides.

One core per machine — init connects to the bridge socket (AGENT_RELAY_CORE_SOCKET or ~/.agentworkforce/desktop/core.sock; Windows pipe name derived exactly as relay::transport, shared test vector), refuses a socket in a directory others can write to, greets with hello and reads status.get. A core that answers (desktop app, service, or an earlier init's) is attached to; only when none answers does init start agent-relay-probe relay serve --headless --cli-managed detached (pid/socket/binary in <relay home>/local/core.json). The core's own flock settles races. relay init --stop stops only the core init started (daemon.shutdown, SIGTERM fallback).

Hooks vs MCP per tool — injected tools get the core's route and no MCP server (one identity per session: the core's registration). Init turns on auto_activate and per tool: Claude Code direct_delivery_default (accept only when unset) + claude_session_hook; Codex codex_stop_hook; OpenCode opencode_plugin; Grok grok_leader; Devin devin_direct_delivery; Pi needs nothing (the core installs its extension). An MCP entry an earlier init added is removed for injected tools. Failed tools, unsupported platforms or a failed download fall back to the MCP server; --delivery mcp asks for it. Settings are per user (every project); --scope now only places the MCP fallback. Settings init turned on are recorded and --uninstall turns off exactly those (and removes the crossSessionInbound key it added rather than leaving the core's hold).

Probe changes — relay serve --cli-managed (requires --headless): keeps the desktop app's login entry and turns nothing on by itself (no agent-led defaults at start, no apply_integration_defaults on refresh). The Claude SessionStart hook now tells a not-yet-registered session that automatic activation will put on the relay that it joins by itself (with the socket's /whoami, /agents, /send) instead of telling it to ask the person to register — that raced auto-activation in the e2e.

End-to-end proof (this machine, isolated)

Isolated HOME, all XDG_*, AGENT_RELAY_HOME, AGENT_RELAY_INSTALL_DIR, core socket and app data; throwaway git project; real Claude Code 2.1.280 and Codex 0.154.0 interactive TUIs in tmux. The user's agent-relay.service, ~/.claude, ~/.codex and active workspace were not touched by the e2e.

  • relay init --yes --tools claude,codex → Started the Agent Relay core (agent-relay-probe 15.0.0, pid …); re-run → The Agent Relay core relay init started earlier is running.
  • Claude session joined by itself (claude-relay-probe-e2e-2-relay-probe). CLI DM sent 00:22:39Z → injected as a turn at 00:22:39.763Z: Another Claude session sent a message: [via Agent Relay — from @alice · ref 3e148a1e6610] …; Claude read it (agent-relay-probe relay read), replied over the session socket, alice's inbox shows PINEAPPLE.
  • Claude → Codex over the socket (sent 00:23:12Z) → injected into the idle Codex TUI at 00:23:18.905Z ([via Agent Relay — from @claude-relay-probe-e2e-2-relay-probe · ref a4092eb03451]); Codex replied → injected into Claude at 00:25:36.269Z. No inbox tool was called anywhere.
  • A core started outside init was attached to (Using the Agent Relay core already running on this machine …), survived relay init --stop; relay init --uninstall turned off auto_activate, direct_delivery, claude_session_hook, codex_stop_hook.

Tests

  • npx tsc --noEmit -p packages/cli: clean.
  • vitest init.test.ts + new probe-core.test.ts: 102 passed (fake core: start/reuse/attach/stop-only-own/uninstall/MCP migration/held Claude/workspace mismatch/download+checksum/fallback/guided; real unix-socket NDJSON client; asset names vs the workflow; bridge version vs protocol.rs).
  • Rust: new tests for --cli-managed defaults and login entry, the Joining hook state, and the pipe-name vector; cargo test --locked (empty HOME/XDG) — see comment for the result.

Remaining (docs/probe-move.md)

Phase 4 relay-desktop consumes the core from relay releases (and must take over from a --cli-managed core instead of crash-looping on the lock); per-project multi-workspace; AGENT_RELAY_PROJECT inference. Gaps: no probe-v15.0.0 release yet (init falls back to MCP until one exists); Codex's sandbox asks to escalate for the socket; messages arrive as pointers (show_relay_messages off); --uninstall leaves an empty hooks.json, Codex hook-trust entries and the core's .relay-desktop-backup copies, and cannot remove the Pi extension; the core's telemetry doesn't follow the CLI's opt-out; no user service is installed (documented).

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Agent Relay sessions

  • claude session a956b33a · opened via gh pr create · last active 2026-10-29
  • claude session 5a4f854b · contributor · ran gh pr commands · last active 2026-10-29