Expand description
Multi-session wire on one machine (v0.5.16).
Problem: multiple Claude Code (or any agent harness) sessions on the
same machine share a single WIRE_HOME, which means they share the
same DID, same relay slot, same inbox JSONL, and same daemon. Peers
have no way to address a specific session, and the operator can’t
tell which session sent what.
Solution: a wire session subcommand that bootstraps isolated
per-session WIRE_HOME trees. Each session gets its own identity,
handle, relay slot, daemon, and inbox/outbox. Sessions pair with each
other through the public relay (wireup.net) like any other peer —
no protocol changes. The bilateral-pair gate from v0.5.14 still
applies in both directions.
Storage layout:
~/.local/state/wire/sessions/
registry.json — cwd → session_name map
<session-name>/ — full WIRE_HOME tree per session
config/wire/...
state/wire/...Naming: derived from basename(cwd) so re-opening the same project
reuses the same session identity. Collisions across two different
paths with the same basename get a 4-char SHA-256 path-hash suffix.
Structs§
- Federation
Only Session View - Sessions with no Local endpoint — shown separately so the operator knows they exist but are federation-only.
- Identity
Split - A session-identity SPLIT: this process’s OPERATIONAL identity (the home it is
actually serving, via
config_dir/WIRE_HOME) disagrees with the LIVE Claude session (resolved fresh from the PID-file). The “displayed name ≠ operational name” bug — a long-lived wire process (usually the MCP server) frozen to a stale/minted identity while the live Claude session moved on (e.g. across a resume). Comparing the SERVED identity (not an env key) is what lets the frozen process itself detect the split: its env key may be unset (minted) or stale, but the home it serves is concrete — the earlier env-vs-pidfile check ran in a fresh CLI whose env was already live, so it never saw the frozen MCP’s stale identity at all. - Local
Endpoint View - Stripped view of a Local endpoint for tooling output. Drops
slot_tokenbecause it is a bearer credential — exposing it throughwire session list-local --jsonwould risk accidental leak via logs, screenshots, or piped output. Routing code uses the fullEndpointfromrelay.jsondirectly; this type is for human/JSON observation only. - Local
Session Listing - Result shape for
wire session list-local.localis grouped by the local-relay URL so output can render each cluster of mutually- reachable sister sessions together.federation_onlylists the rest. - Local
Session View - One row of
wire session list-localoutput: a session that has a Local-scope endpoint plus metadata to render it. - Session
Info - Summary of one on-disk session for
wire session list. - Session
Registry
Constants§
- INBOX_
OWNING_ SUBCOMMANDS - Long-running
wire <subcommand>invocations that own the inbox cursor and therefore race each other under a sharedWIRE_HOME. Keep this list in sync withwarn_on_identity_collision’s pgrep predicate and the call-site list incli::run/mcp::run.
Functions§
- by_
key_ dir_ name - The by-key directory name (16 hex chars / 64 bits) for a session key —
the first 8 bytes of SHA-256(key). Public so test fixtures and external
tooling can locate a session home without replicating the hash:
session_home_for_key(key) == sessions_root()/by-key/<by_key_dir_name(key)>. - default_
sessions_ root - The machine’s DEFAULT sessions root —
sessions_root()with theWIRE_HOMEoverride deliberately ignored. This is where the real operator install lives even when the calling process runs under a temp/testWIRE_HOME. Used bywire nuke’s host guard, whose whole point is to see past the caller’s env to what the machine-global teardown would actually hit. - derive_
name_ from_ cwd - Derive a stable session name for the given cwd. Resolution order:
- detect_
identity_ split - Detect a session-identity split (see
IdentitySplit).Nonein the healthy case (served identity == live Claude session), when either side is unresolvable (bare CLI, uninitialized), or when the operator DELIBERATELY pinnedWIRE_HOMEto share one identity across sessions (not a bug). Run INSIDE a long-lived MCP/daemon this catches the frozen-identity case the old env-vs-pidfile check missed: the served home is stale, the PID-file is live, they differ. Reads the (cached) parent-chain PID-files — cheap on the session-start health check, not a per-message hot path. - detect_
session_ wire_ home - v0.6.7: cwd → session WIRE_HOME lookup. Read-only.
- find_
session_ home_ by_ name - Operator-facing session-name → home_dir resolver. RFC-006 Part A
(1.0 format freeze) collapsed the two historical disk layouts into a
single
by-key/<hash>store, so this resolver now keys two naming conventions onto one physical layout: - is_
unexpected_ session_ source - Sources that indicate the process did NOT inherit an explicit identity
signal from its launcher.
machine-defaultmeans a bare CLI / no session id at all;mintedmeans an MCP server fell through to a fresh per-process key. Either way, a long-running inbox-owning role running under one of these sources is almost certainly NOT the identity its launcher intended (#284.4: the operator-facing symptom is a spawn that should have inheritedWIRE_HOMEbut didn’t, and then silently writes to / blocks on the cwd-default home alongside a sibling process serving the real session-key home). - list_
local_ sessions - Build the listing for
wire session list-localfrom current on-disk state. Read-only; no daemon contact, no relay probe. - list_
sessions - maybe_
adopt_ session_ wire_ home - normalize_
cwd_ key - v0.13.6: case-insensitive cwd-registry key on Windows.
- pid_
to_ session_ map - Walk every initialized session and read its
daemon.pid; return a map frompid → session_name. Used bywire status’s orphan-pid annotation (#173 follow-up) so a supervisor child’s pid — which no longer carries--session <name>in its cmdline post-#174 — is still correctly attributed to the session whose home it serves. - read_
registry - read_
session_ endpoints - Read a session’s
relay.jsonand return itsself.endpoints[]array (v0.5.17 dual-slot). Empty Vec on any read/parse error — this is a best-effort discovery helper, not a verification tool. A pre- v0.5.17 session writes only the legacy flat fields;self_endpointspromotes those to a federation-only Endpoint, so the result is still meaningful for legacy sessions. - registry_
path - Registry tracks
cwd → session_nameso repeatedwire session newfrom the same project reuses the same identity instead of creating a fresh one each time. Lives at<sessions_root>/registry.json. - resolve_
local_ sister - v0.7.4: resolve an operator-typed name to a local sister session.
Input may be the session NAME (e.g.
slancha-api), the card HANDLE (usually equal to the name), or the character NICKNAME (e.g.noble-slate). Returns the session NAME suitable for the--local-sisteradd path. Case-insensitive. None on no match. - resolve_
session_ key - v0.13: resolve a stable per-session key — host-agnostic, with a Claude Code adapter and the path left open for other hosts. Order:
- sanitize_
name - Sanitize an arbitrary string to a session-name-safe form: lowercase
ASCII alphanumeric +
-+_, replace other chars with-, dedupe consecutive dashes, trim leading/trailing dashes, max 32 chars. - session_
daemon_ pid - Read a session home’s daemon pid from
<home>/state/wire/daemon.pid(path-based; does NOT consult WIRE_HOME). None if absent/corrupt. Used to enumerate which daemon pids legitimately belong to a session so orphan detection doesn’t flag a sibling session’s daemon (A2). - session_
dir - Full filesystem path for a named session’s WIRE_HOME root —
<sessions_root>/by-key/<hash>where the by-key hash is derived from the (sanitized) operator-typed name. Inside this dir the standard wire layout applies:config/wire/...andstate/wire/.... - session_
home_ for_ key - v0.13: the WIRE_HOME for a resolved session key —
<sessions_root>/by-key/<hash>wherehashis the first 16 hex of SHA-256(key). Deterministic and cwd-independent, so two sessions never collide and there is no path-string to mis-normalize (the Windows bug cannot occur). 64 bits is collision-safe at this scale. - session_
role_ pid - Read a session home’s
<role>.pid(path-based, no WIRE_HOME read). Same JSON shape asdaemon.pid. None if absent/corrupt. - session_
source - The signal that won session/home resolution for this process. One of:
env:WIRE_HOME,env:WIRE_HOME_FORCE(RFC-008 §C legacy-shape force),override(WIRE_SESSION_ID),claude-code,claude-code-pidfile,codex-cli,copilot-cli,vscode-workspace,minted,machine-default, orunknownif adoption never ran. - sessions_
root - Root directory under which all session WIRE_HOMEs live.
- update_
registry - v0.7.0-alpha.3: flock’d read-modify-write of the session registry.
- warn_
if_ unexpected_ session_ source - #284.4: at startup, a long-running inbox-owning role (
daemon,mcp,monitor,notify) calls this to surface the “the launcher meant to hand us an explicit identity but didn’t” failure mode. Default behavior is a loud, force-rendered stderr warning naming the resolved source so operators see the silent-collision risk; settingWIRE_STRICT_SESSION=1upgrades it to a hard exit (code 2) so a script wrapper can fail-fast instead of waiting for the downstreaminit/bind/daemoncall to block on a shared relay lock. - warn_
on_ identity_ collision - v0.6.10: warn at MCP/CLI startup if another long-running
wireprocess is already running with the same effectiveWIRE_HOME. Closes the “two Claudes in same cwd silently share an identity” failure mode that wasted hours of operator debugging time: today the collision is invisible (both Claudes resolve to the same wire session via v0.6.7 auto-detect, race the inbox cursor, “look identical” from the operator’s view). This surfaces it explicitly with a clear remediation path. - write_
registry