Skip to main content

Registry

Struct Registry 

Source
pub struct Registry {
    root: PathBuf,
    write_lock: Mutex<()>,
}

Fields§

§root: PathBuf§write_lock: Mutex<()>

Implementations§

Source§

impl Registry

Source

pub fn open(root: PathBuf) -> Result<Self, RegistryError>

Open or create the registry rooted at root. Ensures the active dir and retired/ subdir both exist.

Source

pub fn root(&self) -> &Path

Source

pub fn agents_root(&self) -> Option<&Path>

Resolved <shared_home>/agents/ — one level above root. Used by callers that need to express working-directory paths as relative subpaths under the shared agents tree. Returns None if the registry root has no parent (only happens in pathological filesystem-root setups; production always nests under ~/.agentmux/agents/registry).

Source

fn active_path(&self, instance_id: &str) -> PathBuf

Source

fn retired_path(&self, instance_id: &str) -> PathBuf

Source

pub fn upsert(&self, rec: &NamedAgentRecord) -> Result<(), RegistryError>

Insert or update a record. If the file already exists, unknown top-level + data fields are preserved (forward-compat with future schemas that add columns this binary doesn’t know).

Forward-compat invariant (spec §6): never write a higher-schema row into a lower schema, never overwrite a corrupt/unparseable file. Both cases skip the mirror with a warning — the on-disk file stays intact for the binary that authored it (or for ops triage). Skipping is Ok(()): SQLite remains authoritative.

Source

pub fn upsert_keeping_session( &self, rec: &NamedAgentRecord, ) -> Result<(), RegistryError>

Self::upsert, except a session_id the record already holds wins over rec’s. For a mirror that didn’t capture a session — a launch — whose local row may carry an older session id than the one another channel has since written here (#3586). A record without a session id still takes rec’s.

Source

fn upsert_unlocked( &self, rec: &NamedAgentRecord, session: SessionWrite, ) -> Result<(), RegistryError>

Self::upsert without taking write_lock, for callers already holding it. write_lock is a plain Mutex — re-entering it from a locked section would deadlock, not nest.

Source

pub fn set_instance_name_for_agent( &self, agent_id: &str, scope: RecordScope, instance_name: &str, ) -> Result<usize, RegistryError>

Set instance_name on every ACTIVE record for agent_id. Returns how many files changed.

Every record, not just the canonical one, because listrecentsessions dedupes rows by (definition_id, instance_name) — renaming one of an agent’s records and not the others splits it into two picker rows, one under each name. (An agent can hold more than one record for the same reason Self::hard_delete_for_agent exists.)

Active tree only: upsert writes to the active path, so touching a retired record here would resurrect a deliberately-forgotten agent. A retired record keeps its old name until unretire_for_agent brings it back.

Source

pub fn retire(&self, instance_id: &str) -> Result<(), RegistryError>

Move record into retired/ (soft delete — keeps the working dir intact, drops it from the launch-modal dropdown). Idempotent.

Source

pub fn unretire(&self, instance_id: &str) -> Result<(), RegistryError>

Move record back from retired/ to active. Idempotent.

Source

pub fn hard_delete(&self, instance_id: &str) -> Result<usize, RegistryError>

Hard-delete by FILE KEY only (drops both active and retired files), returning how many files were removed.

The narrow counterpart of Self::hard_delete_for_agent. Deleting a TEMPLATE needs this one: a template’s id can legitimately appear as the definition_id of a legacy, never-re-keyed launch record belonging to a real agent launched from it, and matching on definition_id would take that agent’s record along with the template (ReAgent P1 round 3 on PR #3262). A template’s own record, if it has one, is file-keyed, so this is the complete sweep for one.

Source

pub fn hard_delete_for_agent( &self, agent_id: &str, scope: RecordScope, ) -> Result<usize, RegistryError>

Drop every record — active AND retired — that belongs to the agent agent_id, matching on either the record’s file key (its instance id) or its definition_id. Returns how many files were removed.

Why this exists alongside Self::hard_delete. That one keys only on the file name. Since the agent-concept consolidation an agent’s instance id and definition id are the same value, so the two agree — but only for records this tree has been re-keyed to. m0026_registry_agent_id_rekey does that re-keying by reading db_agent_instances, and returns a no-op the moment that legacy table is gone (v32 dropped it). An install that consolidated before m0026 could run therefore still holds one launch-keyed file per agent, in addition to the correctly-keyed one.

Deleting such an agent removed only the correctly-keyed file, leaving the launch-keyed one active with no definition behind it — which listrecentsessions still renders as a row (no provider, so no icon, and "(missing definition)"). The delete looked like it half-worked: the icon vanished, the card stayed.

Both keys are checked rather than just definition_id because callers legitimately hold either one: a consolidated agent id, or the launch id of a registry-only row this channel’s SQLite has never seen. Matching both is a strict superset of Self::hard_delete, and a cross-key collision would require the same uuid to name two different agents.

Source

pub fn retire_for_agent( &self, agent_id: &str, scope: RecordScope, ) -> Result<usize, RegistryError>

Retire every active record for agent_id, matched the same way Self::hard_delete_for_agent matches. A launch-keyed record left active by the file-keyed Self::retire is exactly the “‘Forget agent’ retires the NEW key while the stale file stays active, so the forgotten agent reappears” failure m0026_registry_agent_id_rekey’s own doc comment predicted.

Source

pub fn unretire_for_agent( &self, agent_id: &str, scope: RecordScope, ) -> Result<usize, RegistryError>

Source

fn move_for_agent( &self, agent_id: &str, scope: RecordScope, from_dir: PathBuf, to_dir: PathBuf, ) -> Result<usize, RegistryError>

Move an agent’s records between the active and retired trees, keeping each file’s own name (the instance id) so the record stays addressable by Self::get. Caller holds write_lock.

Source

pub fn exists(&self, instance_id: &str) -> bool

Whether an active record exists for instance_id. Doesn’t validate — useful for migration idempotency checks. Use Self::exists_anywhere when retired records should also count (e.g. so migration doesn’t resurrect a hidden agent).

Source

pub fn get( &self, instance_id: &str, ) -> Result<Option<NamedAgentRecord>, RegistryError>

Read a single active record by id, without scanning the whole tree. Ok(None) when there’s no active file for instance_id (never checks retired/). Callers that need to patch a single field (e.g. propagating a fresh session_id) must read-modify- upsert via this rather than constructing a partial record — upsert’s merge overwrites every field present in the struct, so a partially-populated record would clobber the rest with defaults.

Source

pub fn exists_anywhere(&self, instance_id: &str) -> bool

Whether a record exists in either active or retired. Used by migration to skip already-tombstoned records (avoid resurrecting a user’s deliberate “Forget agent” via a per-version SQLite row that still has display_hidden = 0).

Source

pub fn list_active(&self) -> Result<Vec<NamedAgentRecord>, RegistryError>

Read every valid active record. Invalid files are skipped + logged. PR A doesn’t wire this into the RPC path; included so PR B can swap reads over without further restructuring.

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
§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync> ⓘ

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

§

impl<T> Instrument for T

§

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

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

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> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

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

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
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.
§

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

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

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
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

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