pub struct AgentRegistry {
pub agents: Vec<AgentEntry>,
pub scratchpad: Vec<ScratchpadEntry>,
pub logical_sessions: Vec<LogicalSessionPresence>,
pub logical_session_telemetry_seen: bool,
pub updated_at: DateTime<Utc>,
}Fields§
§agents: Vec<AgentEntry>§scratchpad: Vec<ScratchpadEntry>§logical_sessions: Vec<LogicalSessionPresence>§logical_session_telemetry_seen: bool§updated_at: DateTime<Utc>Implementations§
Source§impl AgentRegistry
impl AgentRegistry
pub fn new() -> Self
pub fn register( &mut self, agent_type: &str, role: Option<&str>, project_root: &str, durable_identity_id: Option<&str>, ) -> Result<String, String>
Sourcepub fn register_child(
&mut self,
agent_type: &str,
role: &str,
project_root: &str,
parent_id: &str,
) -> Result<String, String>
pub fn register_child( &mut self, agent_type: &str, role: &str, project_root: &str, parent_id: &str, ) -> Result<String, String>
Registers (or refreshes) role as its own presence next to
parent_id, which this process holds: a subagent shares its parent’s
MCP connection, and under the parent’s id its reads consumed the
messages directed to the parent (WEB-2 read the lead’s, 25.10.).
Sourcepub fn held_by_this_process(&self, agent_id: &str) -> bool
pub fn held_by_this_process(&self, agent_id: &str) -> bool
Whether agent_id is a live presence this very process registered
(its own or a child): only those may be named as agent_id on a call.
Sourcepub fn declared_role(&self, agent_id: &str) -> Option<String>
pub fn declared_role(&self, agent_id: &str) -> Option<String>
The declared role of agent_id, if it has one.
Sourcepub fn register_transport(
&mut self,
role: &str,
role_source: RoleSource,
project_root: &str,
durable_identity_id: Option<&str>,
transport: TransportClient,
) -> Result<String, String>
pub fn register_transport( &mut self, role: &str, role_source: RoleSource, project_root: &str, durable_identity_id: Option<&str>, transport: TransportClient, ) -> Result<String, String>
Registers (or refreshes) the presence of this process’s own MCP connection, attributed to its host and with the role’s provenance.
Sourcepub fn register_mcp_process(
project_root: &str,
durable_identity_id: Option<&str>,
) -> Result<String, String>
pub fn register_mcp_process( project_root: &str, durable_identity_id: Option<&str>, ) -> Result<String, String>
Atomically registers this MCP process in the shared on-disk registry.
Sourcepub fn register_mcp_process_as(
project_root: &str,
role: &str,
role_source: RoleSource,
durable_identity_id: Option<&str>,
) -> Result<String, String>
pub fn register_mcp_process_as( project_root: &str, role: &str, role_source: RoleSource, durable_identity_id: Option<&str>, ) -> Result<String, String>
Registers this MCP process with the role the session resolved at
initialize (#1766), so the presence retry does not fall back to the
context-engine placeholder. The host comes from this process’s
recorded MCP handshake.
Sourcepub fn record_transport_client_persistent(
transport: &TransportClient,
) -> Result<usize, String>
pub fn record_transport_client_persistent( transport: &TransportClient, ) -> Result<usize, String>
Attributes this process’s live transport presences to the host from
its MCP handshake. Covers sessions whose initialize registers no
project-scoped presence (no resolvable root).
Sourcepub fn transport_presence_is_live(agent_id: &str) -> bool
pub fn transport_presence_is_live(agent_id: &str) -> bool
Whether agent_id is still a listed presence owned by this process.
Read-only: used by the MCP server to notice a presence that another
process retired (for example an older binary’s lease expiry) or pruned.
Sourcepub fn any_live_mcp_transport() -> bool
pub fn any_live_mcp_transport() -> bool
Whether any LeanCTX MCP server runs on this machine: a transport presence that is not finished and whose recorded process is still the one that registered it. Every MCP connection registers one, the stdio server a host starts as well as the daemon’s sessions. Read-only; the deny hook uses it to tell “no daemon” from “no MCP server at all”.
Sourcepub fn heartbeat_persistent(agent_id: &str) -> Result<(), String>
pub fn heartbeat_persistent(agent_id: &str) -> Result<(), String>
Atomically refreshes a registered MCP process heartbeat. The caller is the transport itself, so a presence that only lost its worker lease is revived rather than rejected (an older binary in another process still finishes quiet transports).
Sourcepub fn renew_worker(&mut self, agent_id: &str) -> Result<bool, String>
pub fn renew_worker(&mut self, agent_id: &str) -> Result<bool, String>
The heartbeat of an agent this process registered (ctx_agent register), sent on every tool call of its session: renews its worker
lease, and revives it when the lease had already run out. Ok(true)
when it was revived; take_renewed then says so once.
Sourcepub fn renew_worker_persistent(agent_id: &str) -> Result<bool, String>
pub fn renew_worker_persistent(agent_id: &str) -> Result<bool, String>
Self::renew_worker under the registry lock.
Sourcepub fn finish_persistent(agent_id: &str) -> Result<(), String>
pub fn finish_persistent(agent_id: &str) -> Result<(), String>
Atomically marks a registered MCP process as finished.
pub fn update_heartbeat(&mut self, agent_id: &str) -> Result<(), String>
pub fn set_status( &mut self, agent_id: &str, status: AgentStatus, message: Option<&str>, ) -> Result<(), String>
pub fn cleanup_stale_logical_sessions(&mut self, max_age_seconds: u64)
pub fn record_logical_session_presence( event: &str, source: &str, workspace: &str, session_id: &str, ) -> Result<(), String>
Sourcepub fn require_active_for_presence(
&self,
agent_id: &str,
operation: &str,
) -> Result<(), String>
pub fn require_active_for_presence( &self, agent_id: &str, operation: &str, ) -> Result<(), String>
Gate operations for an explicitly bound presence. Legacy unbound entries retain their prior behavior until a migration enables a mandatory-binding mode.
pub fn list_active(&self, project_root: Option<&str>) -> Vec<&AgentEntry>
pub fn list_all(&self) -> &[AgentEntry]
pub fn post_message_scoped( &mut self, project_root: Option<&str>, from_agent: &str, to_agent: Option<&str>, category: &str, message: &str, privacy: PrivacyLevel, priority: MessagePriority, ttl_hours: Option<u64>, ) -> Result<String, String>
Sourcepub fn post_message_keyed(
&mut self,
project_root: Option<&str>,
from_agent: &str,
to_agent: Option<&str>,
category: &str,
message: &str,
privacy: PrivacyLevel,
priority: MessagePriority,
ttl_hours: Option<u64>,
request_id: Option<&str>,
) -> Result<String, String>
pub fn post_message_keyed( &mut self, project_root: Option<&str>, from_agent: &str, to_agent: Option<&str>, category: &str, message: &str, privacy: PrivacyLevel, priority: MessagePriority, ttl_hours: Option<u64>, request_id: Option<&str>, ) -> Result<String, String>
Self::post_message_scoped with an optional idempotency key
(AP07-G9). A key is scoped to the sender and project: a retry with the
same key and the same request returns the id of the message it first
posted and writes nothing; the same key with another request is
refused. A key lives as long as its message — the scratchpad’s entry
cap and TTL bound both — and an expired message frees its key.
Sourcepub fn read_unread_page(
&mut self,
agent_id: &str,
project_root: &str,
budget: usize,
render: impl Fn(&ScratchpadEntry) -> String,
) -> Result<(Vec<String>, usize), String>
pub fn read_unread_page( &mut self, agent_id: &str, project_root: &str, budget: usize, render: impl Fn(&ScratchpadEntry) -> String, ) -> Result<(Vec<String>, usize), String>
The unread messages of agent_id in project_root, oldest first, as
render shows them, up to budget bytes (at least one); only those
are marked read. Returns them and how many stay unread for the next
read: a host that cuts a long answer used to lose the rest, already
marked read (WEB saw 14 of 55, 25.10.).
Sourcepub fn wait_for_unread(
agent_id: &str,
project_root: &str,
timeout: Duration,
on_poll: impl FnMut(),
) -> bool
pub fn wait_for_unread( agent_id: &str, project_root: &str, timeout: Duration, on_poll: impl FnMut(), ) -> bool
Block until agent_id has a message waiting in project_root
(true) or timeout passed (false). Looks at the registry only when
its file changed (polled every WAIT_POLL); on_poll runs about every
30 s meanwhile (the caller keeps the agent’s lease alive there).
pub fn cleanup_stale(&mut self, max_age_hours: u64)
pub fn load() -> Option<Self>
pub fn load_or_create() -> Self
Sourcepub fn mutate_locked<T>(
f: impl FnOnce(&mut Self) -> T,
) -> Result<(Self, T), String>
pub fn mutate_locked<T>( f: impl FnOnce(&mut Self) -> T, ) -> Result<(Self, T), String>
Atomically load, mutate, and persist the registry under a single file
lock. load_or_create() + mutate + save() is a read-modify-write
race: save() only locks the final write, so two concurrent callers
(two MCP sessions registering, or the dashboard’s own poll-triggered
cleanup_stale + save) can each load a stale snapshot and the last
writer silently drops the other’s changes — e.g. a second session’s
registration vanishing from the dashboard. Holding the lock across
the re-read closes that window: the read inside always sees the
latest on-disk state.
Source§impl AgentRegistry
impl AgentRegistry
Share facts with the other agents of project_root. Every fact is
admitted first; one withheld fact refuses the whole share and nothing
is written.
Adopt one legacy fact (lean-ctx migrate legacy-context) under the
stable id id: a rerun after an interrupted adoption finds the copy
and writes nothing twice.
The facts of project_root that agent_id has not received yet, each
re-authorized for this reader now; a withheld fact is delivered as the
content-free gateway notice.
Trait Implementations§
Source§impl Clone for AgentRegistry
impl Clone for AgentRegistry
Source§impl Debug for AgentRegistry
impl Debug for AgentRegistry
Source§impl Default for AgentRegistry
impl Default for AgentRegistry
Source§impl<'de> Deserialize<'de> for AgentRegistry
impl<'de> Deserialize<'de> for AgentRegistry
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Auto Trait Implementations§
impl Freeze for AgentRegistry
impl RefUnwindSafe for AgentRegistry
impl Send for AgentRegistry
impl Sync for AgentRegistry
impl Unpin for AgentRegistry
impl UnsafeUnpin for AgentRegistry
impl UnwindSafe for AgentRegistry
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
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