Class: Agent::Lock::Identity
- Inherits:
-
Object
- Object
- Agent::Lock::Identity
- Defined in:
- lib/agent/lock/identity.rb
Overview
Who is asking, and whether they are still around.
A lock is worthless unless the holder gives the same answer every time it is asked. The obvious answer, this process's own pid, is the wrong one: an agent harness runs each command in a shell of its own, so a lock acquired by one invocation could never be released by the next. The identity has to belong to the session, not to the process that happens to be speaking for it right now.
In order of preference:
AGENT_ID what a human or a harness set on purpose
CLAUDE_SESSION_ID the session, which survives --resume
fingerprint the first ancestor process that is not a shell
The fingerprint is the fallback that needs explaining. Walking up from
this process, the shells are throwaway and the thing above them is not:
the claude or codex process driving the session, or the terminal a
human is typing in. Its pid and start time, hashed, are stable for as
long as that session lives and different for anybody else's.
Constant Summary collapse
- SHELLS =
%w[sh bash zsh ksh csh tcsh fish dash].freeze
- MAX_HOPS =
10
Class Method Summary collapse
-
.current ⇒ Identity
Deliberately not memoized.
-
.session_pid ⇒ Integer
The ancestry walk, on the other hand, cannot change while this process lives, and it costs two
pscalls per hop.
Instance Method Summary collapse
-
#evidence ⇒ Hash{Symbol => Object}
Evidence, not identity: enough to ask later whether the holder is still running.
-
#id ⇒ String
The holder name written into a lock.
-
#initialize(env: ENV) ⇒ Identity
constructor
A new instance of Identity.
-
#parent_id ⇒ String?
The session that spawned this one: AGENT_PARENT_ID when a harness says so, and otherwise inferred.
-
#parent_source ⇒ Symbol?
Where #parent_id came from.
-
#source ⇒ Symbol
Where #id came from, so a session can check what it is being taken for before a lock is written under the wrong name.
Constructor Details
#initialize(env: ENV) ⇒ Identity
Returns a new instance of Identity.
50 51 52 |
# File 'lib/agent/lock/identity.rb', line 50 def initialize(env: ENV) @env = env end |
Class Method Details
.current ⇒ Identity
Deliberately not memoized. A forked CLI computes this once and dies, but the same code runs inside a long-lived process during the specs and inside any harness that loads the library, where a cached answer would outlive the environment it was computed from.
40 |
# File 'lib/agent/lock/identity.rb', line 40 def current = new |
.session_pid ⇒ Integer
The ancestry walk, on the other hand, cannot change while this
process lives, and it costs two ps calls per hop.
46 |
# File 'lib/agent/lock/identity.rb', line 46 def session_pid = @session_pid ||= yield |
Instance Method Details
#evidence ⇒ Hash{Symbol => Object}
Evidence, not identity: enough to ask later whether the holder is still running. The pid alone is not enough, since pids are reused, and the host matters because a lock store can be on a shared or synced volume.
103 104 105 |
# File 'lib/agent/lock/identity.rb', line 103 def evidence { pid: session_pid, started: ProcessInfo.started_at(session_pid), host: Socket.gethostname } end |
#id ⇒ String
Returns the holder name written into a lock.
55 56 57 |
# File 'lib/agent/lock/identity.rb', line 55 def id @id ||= explicit || session || fingerprint end |
#parent_id ⇒ String?
The session that spawned this one: AGENT_PARENT_ID when a harness says so, and otherwise inferred.
The inference exists because Claude Code runs a sub-agent inside its parent's own process and sets nothing to tell them apart, so without it every sub-agent resolved to its parent's fingerprint and none of them could ever block another. A sub-agent's one distinguishing mark is the AGENT_ID it was told to use. When that differs from what this session would answer to without it, the session is, by elimination, the parent.
71 72 73 74 75 |
# File 'lib/agent/lock/identity.rb', line 71 def parent_id return @parent_id if defined?(@parent_id) @parent_id = declared_parent || inferred_parent end |
#parent_source ⇒ Symbol?
Where #parent_id came from. An inferred parent is a guess a human may want to overrule with AGENT_PARENT_ID, so it is reported as one.
92 93 94 95 96 |
# File 'lib/agent/lock/identity.rb', line 92 def parent_source return :explicit if declared_parent :inferred if parent_id end |
#source ⇒ Symbol
Where #id came from, so a session can check what it is being taken for before a lock is written under the wrong name.
81 82 83 84 85 86 |
# File 'lib/agent/lock/identity.rb', line 81 def source return :explicit if explicit return :session if session :fingerprint end |