Skip to main content

RegistryEntry

Struct RegistryEntry 

Source
pub struct RegistryEntry {
Show 30 fields pub name: String, pub short_id: String, pub legacy_provider: String, pub cwd: String, pub project_root: String, pub session_id: Option<String>, pub claude_session_uuid: Option<String>, pub harness: Option<String>, pub harness_session_id: Option<String>, pub messaging_socket_path: Option<String>, pub codex_session_id: Option<String>, pub gemini_session_id: Option<String>, pub mcp_channel_id: Option<String>, pub host_mode: Option<String>, pub cc_session_id: Option<String>, pub status: AgentStatus, pub last_message_at: Option<String>, pub created_at: String, pub pid: Option<u32>, pub pid_start_time: Option<u64>, pub log_path: Option<String>, pub last_reconciled_at: Option<String>, pub inside_leg: Option<InsideLegReport>, pub exited_at: Option<String>, pub mux: Option<MuxRef>, pub screen_state: Option<ScreenStateReport>, pub crown_level: Option<u32>, pub crown_scope: Option<String>, pub crown_grantor: Option<String>, pub legacy_claude_short_id: Option<String>,
}
Expand description

One registry row (design schema v6). Optional fields default to None and are preserved across update_registry because the whole row round-trips through this typed struct.

Fields§

§name: String§short_id: String

Daemon-set PTY field. Python’s AgentEntry now mirrors it as short_id: str = "" (ab-b946b59c) so a real PTY row in a mixed registry is Python-readable and round-trips losslessly; skip_serializing_if still drops it when empty so a Rust-authored exec/ask row stays slim and a round-tripped Python row omits it (default-to-empty on read, ab-e5a57efa; Codex P1, PR #364). A real daemon PTY agent always has a non-empty short_id, so it still serializes for those rows; conversely a one-shot ask row always has an empty short_id (no worker-socket identity). That exclusivity is what RegistryEntry::is_one_shot_ask keys on – a non-empty short_id on an ask row, or an empty one on a PTY row, is a producer bug. (Python mirrors with a str default, not Option, because a "short_id": null would fail this String field’s deserialize.)

§legacy_provider: String

v10 backfill-only (x-880e): the removed on-disk provider key. Deserialized under its old name so a legacy row’s identity survives the read, but NEVER serialized – RegistryEntry::backfill_harness_aliases moves it into harness at load. This is the Rust mirror of Python’s load_registry popping provider. harness is the sole on-disk identity axis.

§cwd: String§project_root: String

Daemon-set PTY field, mirrored in Python’s AgentEntry as project_root: str = "" (ab-b946b59c; see short_id): default on read, skip-when-empty on write.

§session_id: Option<String>

On disk this is Rust-set only (Python’s session_id is a computed @property, excluded from its serialized rows): skip when absent so Python can read a Rust-written row (Codex P1). When a Rust PTY row DOES record one, Python’s load_registry drops the key before constructing the entry and recomputes the same projection from the *_session_id fields (ab-b946b59c).

§claude_session_uuid: Option<String>

The FULL claude session UUID – the stream-json --resume target, distinct from the 8-hex jobId in short_id. v10 (x-880e): a load-derived in-memory alias only. skip_serializing keeps it off disk (harness_session_id is the sole persisted session id); backfill_harness_aliases populates it from harness_session_id on load, so the ~30 daemon read sites need no churn. A post-load mutation of this field is synced back into harness_session_id at the write choke point (AC6-FR). [stream-json host lane node]

§harness: Option<String>

Canonical harness identity (x-ec59), mirroring Python’s AgentEntry: harness is the harness name (identity only – provider stays load-bearing for dispatch) and harness_session_id is the worker’s own session id in its harness’s store. Both additive-optional, back-filled from the legacy per-provider fields at load via RegistryEntry::backfill_harness_aliases so a Rust reader of a legacy row and a Python reader of a Rust-minted canonical row both resolve. Skip-when-None keeps a Rust-authored row slim; Python’s asdict always emits the key, so a Python row round-trips fine.

§harness_session_id: Option<String>§messaging_socket_path: Option<String>

Daemon-set PTY field, mirrored in Python’s AgentEntry (ab-b946b59c): skip when absent (Codex P1).

§codex_session_id: Option<String>§gemini_session_id: Option<String>§mcp_channel_id: Option<String>§host_mode: Option<String>

Hosting mode: absent/None == "exec" (one-shot, the default for every pre-existing row), Some("interactive") == a long-lived drivable TUI (fno agents host/promote). Skip-when-None so a Rust-authored exec row omits the key; Python’s missing-key coercion then maps the absence back to "exec". (Python itself always emits the key via asdict – as "exec" or "interactive" – and Rust reads the concrete value fine, so both directions agree.) Consumers must read it via RegistryEntry::host_mode_or_default, never the raw Option, so the absent==exec rule lives in one place. [interactive-drive node]

§cc_session_id: Option<String>

Daemon-set PTY field, mirrored in Python’s AgentEntry (ab-b946b59c): skip when absent (Codex P1).

§status: AgentStatus§last_message_at: Option<String>§created_at: String§pid: Option<u32>

Daemon-set PTY field, mirrored in Python’s AgentEntry as pid: Optional[int] (ab-b946b59c): skip when absent so a round-tripped Python row stays slim and Python-readable (Codex P1).

§pid_start_time: Option<u64>

The worker process’s start time, captured alongside pid at spawn, used to detect PID reuse: a liveness/reap/signal decision treats pid as “our worker” only if the live process’s start time still matches this (ab-d19e6458). Per-host, per-boot value (Linux: /proc/<pid>/stat field 22 in clock ticks; macOS: kinfo_proc start timeval in microseconds) — only ever compared for equality against a fresh read of the SAME pid, so the unit/epoch difference across platforms is irrelevant. Daemon-set PTY field, mirrored in Python’s AgentEntry (ab-b946b59c); skip when absent.

§log_path: Option<String>§last_reconciled_at: Option<String>

Timestamp of the most recent reconcile probe (finding #1 High): the reconcile sweep orders entries by ASC last_reconciled_at so a budget-exhausted sweep stays fair across a large registry. Daemon-set, mirrored in Python’s AgentEntry (ab-b946b59c); skip when absent (Codex P1).

§inside_leg: Option<InsideLegReport>

Latest inside-leg report for this row’s claude pane (inside-out E3, contract v2). None for every non-inside-leg row (the default for every pre-existing row, and for any provider/lane that does not run a hook). Skip-when-None so a row without a report stays slim and a stale reader rejects via the v5 schema bump rather than silently dropping it. Mirrored in Python’s AgentEntry as inside_leg: Optional[dict] (X3 / ab-b946b59c).

§exited_at: Option<String>

When the dead-row GC first observed this row’s backing process as gone (ISO 8601 UTC), stamped by the GC sweep on the first tick it sees the row terminal/dead and cleared again if the row re-registers live (x-b1aa). It anchors the config.agents.dead_row_grace window: a row is reaped only once now - exited_at is strictly past the grace. Deliberately NOT set at the status->Exited transition (reconcile re-stamps last_reconciled_at on every probe, so that field can’t anchor a stable clock); the GC’s first-observation stamp is stable until the row is reaped or resurrected. Daemon-set, mirrored in Python’s AgentEntry as exited_at; skip when absent so a pre-GC row round-trips losslessly (additive-optional, no schema bump).

§mux: Option<MuxRef>

The mux hosting ref for a pane-substrate agent (4a-G2): Some means this row’s PTY is a pane in mux.session, and pane-exit facts / live-inject / sideline badges all key on it. None for every daemon worker, bg-thread, and headless row. One live ref per row (mux XOR worker XOR bg) - see MuxRef and validate_single_live_ref. Skip-when-None so a pre-mux row stays slim; a stale reader rejects via the v6 schema bump rather than silently dropping the ref. Mirrored in Python’s AgentEntry as mux: Optional[dict] (X3).

§screen_state: Option<ScreenStateReport>

Latest screen-manifest verdict for this row’s mux pane (v7, the fallback rung under the hook). Daemon-scrape-set, and mutually exclusive with a live inside_leg authority BY THE WRITER (the sweep skips hook-bearing rows; the inside-leg store clears this field on the capability flip) - readers still treat inside_leg as unconditionally senior, defense in depth. Skip-when-None so an unscraped row stays slim and a stale reader rejects via the v7 bump rather than silently dropping a verdict. Mirrored in Python’s AgentEntry as screen_state: Optional[dict] (X3).

§crown_level: Option<u32>

Crown fields (US9, v11): who holds an orchestrator crown and at what altitude. The Python spawn path is the sole writer (grantor-stamped, never self-declared); the daemon only custodies them so a spawn-stamped crown round-trips losslessly across a read-modify-write - the same X3 passthrough treatment as inside_leg/screen_state. Skip-when-None keeps a Rust-authored uncrowned row slim; Python’s asdict always emits the keys, so a crowned Python row round-trips fine. Crown liveness == this row’s liveness (no separate lifecycle).

§crown_scope: Option<String>§crown_grantor: Option<String>§legacy_claude_short_id: Option<String>

v9 backfill-only (x-1b1e): the removed claude_short_id. Deserialized (under its old key) so a legacy row’s jobId survives the read, but NEVER serialized – RegistryEntry::backfill_short_id moves it into short_id at load and clears it, so it never round-trips. This is the Rust mirror of Python’s load_registry popping claude_short_id from the raw row. Not part of identity; no consumer reads it directly.

Implementations§

Source§

impl RegistryEntry

Source

pub fn backfill_harness_aliases(&mut self)

Two-way sync of harness/harness_session_id with the legacy per-provider identity fields (x-ec59), the Rust mirror of Python’s harness_identity.sync_harness_aliases + the registry harness back-fill. Applied at load so a Rust reader of a legacy row and a Python reader of a Rust-minted canonical row both resolve. harness adopts provider when absent (provider is always set; harness is identity-only, never gates the read). Then canonical wins: a set harness_session_id syncs the matching legacy key (a conflicting legacy value is overwritten, never leaked); otherwise the first present legacy value back-fills harness_session_id. The claude legacy key is claude_session_uuid (the registry’s identity), NOT the manifest’s claude_session_id.

Source

pub fn backfill_short_id(&mut self) -> Option<String>

v9 transport-key backfill (x-1b1e), the Rust mirror of Python’s load_registry popping the removed claude_short_id into short_id. Applied at load, before validate_single_live_ref: a legacy row’s jobId (deserialized into legacy_claude_short_id) moves into an empty short_id and the transient is cleared so it never round-trips. A conflicting pair (both set, different values – the drift this removal kills) KEEPS short_id and returns the legacy value so the caller can warn once; it never silently prefers the legacy value.

Source

pub fn transport_short(&self) -> Option<&str>

The provider transport key (v9, x-1b1e), or None when this row has none: the non-empty short_id. For claude it is the jobId (claude attach/logs <jobId>); for a daemon PTY row the worker-socket key. The single accessor consumers use to reach a session’s wire handle, so no verb re-implements the empty-string guard. [x-1b1e transport extraction]

Source

pub fn harness_name(&self) -> &str

The row’s harness name as a required-string view (x-880e). The single accessor every RegistryEntry consumer uses instead of the raw identity field, so the provider->harness migration touches one place. harness is set on load by RegistryEntry::backfill_harness_aliases; during the migration window a not-yet-backfilled fresh row falls back to the legacy provider. Collapses to harness-only once provider is removed.

Source

pub fn host_mode_or_default(&self) -> &str

The hosting mode with the absent==exec rule applied in one place. None on disk (and the legacy rows that predate the field) read as HOST_MODE_EXEC; an explicit value passes through. Reconcile/liveness and the spawn path must use this, never the raw Option, so a missing key can never be mistaken for a non-exec mode. [interactive-drive node]

Source

pub fn is_interactive(&self) -> bool

True when this row is a long-lived interactive host (vs a one-shot exec session). The reconcile branch keys off this: an exec worker that exited is normal; an interactive worker is expected to stay live until /quit.

Source

pub fn is_one_shot_ask(&self) -> bool

True when this row is a one-shot ask agent the daemon does NOT manage as a worker process: empty short_id (no worker-socket identity) AND no recorded pid. Such an agent has no process whose liveness could make it live – its terminal status is exited, and its post-run value is resumability (a recorded provider session id), surfaced separately from status via the session_id projection. Only PTY agents (spawn/host/ promote) carry a non-empty short_id + pid and can be live; this is the invariant documented on the short_id field (“a real daemon PTY agent always has a non-empty short_id”). Reconcile uses this to settle a finished ask to exited by process-liveness alone, never consulting session-file reachability for status. [plan ab-70faa65b, Locked Decision #1]

Trait Implementations§

Source§

impl Clone for RegistryEntry

Source§

fn clone(&self) -> RegistryEntry

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for RegistryEntry

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for RegistryEntry

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl PartialEq for RegistryEntry

Source§

fn eq(&self, other: &RegistryEntry) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for RegistryEntry

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for RegistryEntry

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more