ADR-171: Stable session identity — the roster enumerates addressable coordinating units¶
Context¶
A live multi-session test (issue #378) surfaced the "multiple personalities"
failure: one human and two claude sessions rendered as four-plus identities
(hana, Hana-beta, the ghost Hana-alpha, a tools/-keyed persona from
the same session). Three root causes were reproduced from one session's own
history:
- Identity keyed off process cwd at launch. attend derived its working
dir from
current_dir(), so a stray shellcdleaking into anattend runlaunch (a Monitor inheriting a build directory) put the session on the bus as a different persona than its project. - Registration happened once, at startup. The periodic instance-registry
maintenance used
touch, which is a no-op for a missing entry — a session whose registration was GC'd or never written rendered as the bare nickname forever. - Historical wire data kept superseded personas alive in the chat legend for as long as their signals sat in the buffer.
The debate (operator + two peer sessions, recorded on #378) also settled what identity is on this bus, which the fix must encode.
Decision¶
Identity is the coordinating unit, not the process. A claude's canonical
identity is the tuple (sessionId ∩ origin_path) — the session UID paired
with the session record's cwd, never the process cwd. Concretely:
- A new
attend-sessioncrate owns the one derivation of "who am I on the bus": pid-ancestry walk over~/.claude/sessions/*.jsonfor the session UID, the session record'scwdas the origin path, with explicit flagged fallbacks (pid-<pid>+ process cwd,resolved: false) for processes no Claude session owns. attend (run, send, status, peers, inbox, config), sensor-peers, the instance registry, the heartbeat id, and focus-group member ids all resolve through it. Downstream durable state — the planned per-session consumption checkpoint (drain research) — keys on the same tuple by calling the same derivation. attend whoamiis the CLI accessor for the tuple (--machineemitskey=valueof only the stable fields), so hooks and scripts obtain identity without touching attend-owned state. The rendered display name is deliberately absent from machine output: ordinals are presentation, never keys.- The roster enumerates addressable coordinating units — top-level
sessions, by session UID per origin dir. A second top-level instance in
the same origin dir takes the next Greek letter (the existing ADR-129
allocator, which was already idempotent per
(cwd, sessionId); this ADR fixes its inputs). Subagents are the supervisor's efferent limbs, not afferent participants: they get no roster identity and no letter. Their activity may render as decoration on the parent's chip (deferred, presentation-only). - The periodic registry maintenance upserts (
register, idempotent) instead of touching, so a session with a missing entry self-heals within one interval instead of rendering bare forever.
Consequences¶
Positive¶
- A session's bus persona is immune to shell cwd drift — the launch environment can no longer mint accidental identities.
- The "bare nickname" degradation self-heals; ghosts stop accumulating at their source.
- One identity derivation shared by four consumers replaces three partial ones; the drain checkpoint research can key on it without re-implementing resolution.
- A minimal attend build (without sensor-peers) now resolves real session
identity instead of degrading to
pid-<pid>member ids.
Negative¶
- Session-record resolution costs a sessions-dir walk plus a
ps-based ancestry climb per subcommand invocation — negligible at CLI cadence, but no longer a baregetcwd. - Historical signals written under superseded personas still render as distinct chips until they age out of buffers — display residue this ADR accepts rather than rewriting history.
Neutral¶
- Humans are unaffected: their username identity (ADR-170) was stable by construction.
Focus::default_focus()keeps its process-cwd default for the generic sensor API; attend overrides the working dir at the one place identity is authoritative.
Alternatives Considered¶
- Per-process registry identity (PID lineage) so subagents get letters — rejected in the #378 debate: subagents have no independent voice in the channel, letters would flicker with worker lifecycles, and consumption is session-level regardless. Identity follows addressability.
- Keying anything on the rendered ordinal — rejected: slot reuse would alias different sessions over time; monotonic slots grow unboundedly. Ordinal is presentation; the tuple is the key.
- Fixing cwd drift by documenting "don't
cdbefore launching attend" — rejected: the failure was produced by an agent following normal build workflows; discipline that one straycddefeats is not an invariant. - Synthesizing session records for non-session processes — rejected: pollutes Claude Code-owned state (same reasoning as ADR-170's rejection of synthetic session files).