pub struct Agent { /* private fields */ }Expand description
The core agent that participates in the x0x gossip network.
Each agent is a peer — there is no client/server distinction. Agents discover each other through gossip and communicate via epidemic broadcast.
An Agent wraps an identity::Identity that provides:
machine_id: Tied to this computer (for QUIC transport authentication)agent_id: Portable across machines (for agent persistence)
§Example
use x0x::Agent;
let agent = Agent::builder()
.build()
.await?;
println!("Agent ID: {}", agent.agent_id());Implementations§
Source§impl Agent
impl Agent
Sourcepub async fn new() -> Result<Self>
pub async fn new() -> Result<Self>
Create a new agent with default configuration.
This generates a fresh identity with both machine and agent keypairs.
The machine keypair is stored persistently in ~/.x0x/machine.key.
For more control, use Agent::builder().
Sourcepub fn builder() -> AgentBuilder
pub fn builder() -> AgentBuilder
Create an AgentBuilder for fine-grained configuration.
The builder supports:
- Custom machine key path via
with_machine_key() - Imported agent keypair via
with_agent_key() - User identity via
with_user_key()orwith_user_key_path()
Sourcepub fn machine_id(&self) -> MachineId
pub fn machine_id(&self) -> MachineId
Get the machine ID for this agent.
The machine ID is tied to this computer and used for QUIC transport
authentication. It is stored persistently in ~/.x0x/machine.key.
§Returns
The agent’s machine ID.
Sourcepub fn agent_id(&self) -> AgentId
pub fn agent_id(&self) -> AgentId
Get the agent ID for this agent.
The agent ID is portable across machines and represents the agent’s persistent identity. It can be exported and imported to run the same agent on different computers.
§Returns
The agent’s ID.
Sourcepub fn user_id(&self) -> Option<UserId>
pub fn user_id(&self) -> Option<UserId>
Get the user ID for this agent, if a user identity is bound.
Returns None if no user keypair was provided during construction.
User keys are opt-in — they are never auto-generated.
Sourcepub fn agent_certificate(&self) -> Option<&AgentCertificate>
pub fn agent_certificate(&self) -> Option<&AgentCertificate>
Get the agent certificate, if one exists.
The certificate cryptographically binds this agent to a user identity.
Sourcepub fn network(&self) -> Option<&Arc<NetworkNode>>
pub fn network(&self) -> Option<&Arc<NetworkNode>>
Get the network node, if initialized.
Sourcepub fn gossip_cache_adapter(&self) -> Option<&GossipCacheAdapter>
pub fn gossip_cache_adapter(&self) -> Option<&GossipCacheAdapter>
Get the gossip cache adapter for coordinator discovery.
Returns None if this agent was built without a network config.
The adapter wraps the same Arc<BootstrapCache> as the network node.
Sourcepub fn presence_system(&self) -> Option<&Arc<PresenceWrapper>>
pub fn presence_system(&self) -> Option<&Arc<PresenceWrapper>>
Get the presence system wrapper, if configured.
Returns None if this agent was built without a network config.
The presence wrapper provides beacon broadcasting, FOAF discovery,
and online/offline event subscriptions.
Sourcepub fn contacts(&self) -> &Arc<RwLock<ContactStore>>
pub fn contacts(&self) -> &Arc<RwLock<ContactStore>>
Get a reference to the contact store.
The contact store persists trust levels and machine records for known
agents. It is backed by ~/.x0x/contacts.json by default.
Use with_contact_store_path
on the builder to customise the path.
Sourcepub async fn reachability(&self, agent_id: &AgentId) -> Option<ReachabilityInfo>
pub async fn reachability(&self, agent_id: &AgentId) -> Option<ReachabilityInfo>
Get the reachability information for a discovered agent.
Returns None if the agent is not in the discovery cache.
Use Agent::announce_identity or wait for a heartbeat announcement
to populate the cache.
Sourcepub async fn connect_to_agent(
&self,
agent_id: &AgentId,
) -> Result<ConnectOutcome>
pub async fn connect_to_agent( &self, agent_id: &AgentId, ) -> Result<ConnectOutcome>
Attempt to connect to an agent by its identity.
Looks up the agent in the discovery cache, then tries to establish a QUIC connection using the best available strategy:
- Direct — if the agent reports
can_receive_direct: trueor has a traversable NAT type, try each known address in order. - Coordinated — if direct fails or the agent reports a symmetric
NAT, the outcome is
Coordinatedif any address was reachable via the network layer’s NAT traversal. - Unreachable — no address succeeded.
- NotFound — the agent is not in the discovery cache.
§Errors
Returns an error only for internal failures (e.g. network not started).
Connectivity failures are reported as ConnectOutcome::Unreachable.
Sourcepub async fn shutdown(&self)
pub async fn shutdown(&self)
Save the bootstrap cache and release resources.
Call this before dropping the agent to ensure the peer cache is persisted to disk. The background maintenance task saves periodically, but this guarantees a final save.
Sourcepub async fn send_direct(
&self,
agent_id: &AgentId,
payload: Vec<u8>,
) -> NetworkResult<()>
pub async fn send_direct( &self, agent_id: &AgentId, payload: Vec<u8>, ) -> NetworkResult<()>
Send data directly to a connected agent.
This bypasses gossip pub/sub for efficient point-to-point communication.
The agent must be connected first via Self::connect_to_agent.
§Arguments
agent_id- The target agent’s identifier.payload- The data to send.
§Errors
Returns an error if:
- Network is not initialized
- Agent is not connected
- Agent is not found in discovery cache
- Send fails
§Example
// First connect to the agent
let outcome = agent.connect_to_agent(&target_agent_id).await?;
// Then send data directly
agent.send_direct(&target_agent_id, b"hello".to_vec()).await?;Sourcepub async fn recv_direct(&self) -> Option<DirectMessage>
pub async fn recv_direct(&self) -> Option<DirectMessage>
Receive the next direct message from any connected agent.
Blocks until a direct message is received.
§Security Note
This method does not apply trust filtering from ContactStore.
Messages from blocked agents will still be delivered. Use
recv_direct_filtered() if you need
trust-based filtering.
§Returns
The received DirectMessage containing sender, payload, and timestamp.
§Example
loop {
if let Some(msg) = agent.recv_direct().await {
println!("From {:?}: {:?}", msg.sender, msg.payload_str());
}
}Sourcepub async fn recv_direct_filtered(&self) -> Option<DirectMessage>
pub async fn recv_direct_filtered(&self) -> Option<DirectMessage>
Receive the next direct message, filtering by trust level.
Messages from blocked agents are silently dropped. This mirrors the behavior of gossip pub/sub message filtering.
§Returns
The received DirectMessage, or None if the channel closes.
Messages from blocked senders are dropped and the method continues
waiting for the next acceptable message.
§Example
// Block an agent
{
let mut contacts = agent.contacts().write().await;
contacts.set_trust(&bad_agent_id, TrustLevel::Blocked);
}
// Messages from blocked agents are silently dropped
loop {
if let Some(msg) = agent.recv_direct_filtered().await {
// msg.sender is not in the blocked list
// (note: sender is self-asserted, see DirectMessage docs)
}
}Sourcepub fn subscribe_direct(&self) -> DirectMessageReceiver
pub fn subscribe_direct(&self) -> DirectMessageReceiver
Sourcepub fn direct_messaging(&self) -> &Arc<DirectMessaging>
pub fn direct_messaging(&self) -> &Arc<DirectMessaging>
Get the direct messaging infrastructure.
Provides low-level access to connection tracking and agent mappings.
Sourcepub async fn is_agent_connected(&self, agent_id: &AgentId) -> bool
pub async fn is_agent_connected(&self, agent_id: &AgentId) -> bool
Sourcepub async fn connected_agents(&self) -> Vec<AgentId>
pub async fn connected_agents(&self) -> Vec<AgentId>
Get list of currently connected agents.
Returns agents that have been discovered and are currently connected via QUIC transport.
Sourcepub fn set_contacts(&self, store: Arc<RwLock<ContactStore>>)
pub fn set_contacts(&self, store: Arc<RwLock<ContactStore>>)
Attach a contact store for trust-based message filtering.
When set, the gossip pub/sub layer will:
- Drop messages from
Blockedsenders (don’t deliver, don’t rebroadcast) - Annotate messages with the sender’s trust level for consumers
Without a contact store, all messages pass through (open relay mode).
Sourcepub async fn announce_identity(
&self,
include_user_identity: bool,
human_consent: bool,
) -> Result<()>
pub async fn announce_identity( &self, include_user_identity: bool, human_consent: bool, ) -> Result<()>
Announce this agent’s identity on the network discovery topic.
By default, announcements include agent + machine identity only. Human identity disclosure is opt-in and requires explicit consent.
§Arguments
include_user_identity- Whether to includeuser_idand certificatehuman_consent- Must betruewhen disclosing user identity
§Errors
Returns an error if:
- Gossip runtime is not initialized
- Human identity disclosure is requested without explicit consent
- Human identity disclosure is requested but no user identity is configured
- Serialization or publish fails
Sourcepub async fn discovered_agents(&self) -> Result<Vec<DiscoveredAgent>>
pub async fn discovered_agents(&self) -> Result<Vec<DiscoveredAgent>>
Get all discovered agents from identity announcements.
§Errors
Returns an error if the gossip runtime is not initialized.
Sourcepub async fn discovered_agents_unfiltered(&self) -> Result<Vec<DiscoveredAgent>>
pub async fn discovered_agents_unfiltered(&self) -> Result<Vec<DiscoveredAgent>>
Return all discovered agents regardless of TTL.
Unlike Self::discovered_agents, this method skips TTL filtering and
returns all cache entries, including stale ones. Useful for debugging.
§Errors
Returns an error if the gossip runtime is not initialized.
Sourcepub async fn discovered_agent(
&self,
agent_id: AgentId,
) -> Result<Option<DiscoveredAgent>>
pub async fn discovered_agent( &self, agent_id: AgentId, ) -> Result<Option<DiscoveredAgent>>
Get one discovered agent record by agent ID.
§Errors
Returns an error if the gossip runtime is not initialized.
Sourcepub async fn join_network(&self) -> Result<()>
pub async fn join_network(&self) -> Result<()>
Join the x0x gossip network.
Connects to bootstrap peers in parallel with automatic retries. Failed connections are retried after a delay to allow stale connections on remote nodes to expire.
If the agent was not configured with a network, this method succeeds gracefully (nothing to join).
Sourcepub async fn subscribe(&self, topic: &str) -> Result<Subscription>
pub async fn subscribe(&self, topic: &str) -> Result<Subscription>
Subscribe to messages on a given topic.
Returns a gossip::Subscription that yields messages as they arrive
through the gossip network.
§Errors
Returns an error if:
- Gossip runtime is not initialized (configure agent with network first)
Sourcepub async fn publish(&self, topic: &str, payload: Vec<u8>) -> Result<()>
pub async fn publish(&self, topic: &str, payload: Vec<u8>) -> Result<()>
Publish a message to a topic.
The message will propagate through the gossip network via epidemic broadcast — every agent that receives it will relay it to its neighbours.
§Errors
Returns an error if:
- Gossip runtime is not initialized (configure agent with network first)
- Message encoding or broadcast fails
Sourcepub async fn peers(&self) -> Result<Vec<PeerId>>
pub async fn peers(&self) -> Result<Vec<PeerId>>
Get connected peer IDs.
Returns the list of peers currently connected via the gossip network.
§Errors
Returns an error if the network is not initialized.
Sourcepub async fn presence(&self) -> Result<Vec<AgentId>>
pub async fn presence(&self) -> Result<Vec<AgentId>>
Get online agents.
Returns agent IDs discovered from signed identity announcements.
§Errors
Returns an error if the gossip runtime is not initialized.
Sourcepub async fn subscribe_presence(&self) -> NetworkResult<Receiver<PresenceEvent>>
pub async fn subscribe_presence(&self) -> NetworkResult<Receiver<PresenceEvent>>
Subscribe to presence events (agent online/offline notifications).
Returns a tokio::sync::broadcast::Receiver<PresenceEvent> that yields
presence::PresenceEvent values as agents come online or go offline.
The diff-based event emission loop is started lazily on the first call to this
method (or when join_network is called). Subsequent
calls return independent receivers on the same broadcast channel.
§Errors
Returns error::NetworkError::NodeError if this agent was built
without a network configuration (i.e. no with_network_config on the builder).
Sourcepub async fn cached_agent(&self, id: &AgentId) -> Option<DiscoveredAgent>
pub async fn cached_agent(&self, id: &AgentId) -> Option<DiscoveredAgent>
Look up a single agent in the local discovery cache.
Returns None if the agent is not currently cached. No network I/O is
performed — use discover_agent_by_id for
an active lookup that queries the network.
Sourcepub async fn discover_agents_foaf(
&self,
ttl: u8,
timeout_ms: u64,
) -> NetworkResult<Vec<DiscoveredAgent>>
pub async fn discover_agents_foaf( &self, ttl: u8, timeout_ms: u64, ) -> NetworkResult<Vec<DiscoveredAgent>>
Discover agents via Friend-of-a-Friend (FOAF) random walk.
Initiates a FOAF query on the global presence topic with the given ttl
(maximum hop count) and timeout_ms (response collection window).
Returned entries are resolved against the local identity discovery cache so that known agents are returned with full identity data. Unknown peers are included with a minimal entry (addresses only) that will be enriched once their identity heartbeat arrives.
§Arguments
ttl— Maximum hop count for the random walk (1–5). Typical:2.timeout_ms— Query timeout in milliseconds. Typical:5000.
§Errors
Returns error::NetworkError::NodeError if no network config was provided.
Sourcepub async fn discover_agent_by_id(
&self,
target_id: AgentId,
ttl: u8,
timeout_ms: u64,
) -> NetworkResult<Option<DiscoveredAgent>>
pub async fn discover_agent_by_id( &self, target_id: AgentId, ttl: u8, timeout_ms: u64, ) -> NetworkResult<Option<DiscoveredAgent>>
Discover a specific agent by their identity::AgentId via FOAF random walk.
Fast-path: checks the local identity discovery cache first and returns immediately if the agent is already known.
Slow-path: performs a FOAF random walk (see discover_agents_foaf)
and searches the results for a matching AgentId.
Returns None if the agent is not found within the given ttl and timeout_ms.
§Errors
Returns error::NetworkError::NodeCreation if no network config was provided.
Sourcepub async fn find_agent(
&self,
agent_id: AgentId,
) -> Result<Option<Vec<SocketAddr>>>
pub async fn find_agent( &self, agent_id: AgentId, ) -> Result<Option<Vec<SocketAddr>>>
Find an agent by ID, returning its known addresses.
Performs a three-stage lookup:
- Cache hit — return addresses immediately if the agent has already been discovered.
- Shard subscription — subscribe to the agent’s identity shard topic and wait up to 5 seconds for a heartbeat announcement.
- Rendezvous — subscribe to the agent’s rendezvous shard topic and
wait up to 5 seconds for a
ProviderSummaryadvertisement. This works even when the two agents are on different gossip overlay clusters.
Returns None if the agent is not found within the combined deadline.
§Errors
Returns an error if the gossip runtime is not initialized.
Sourcepub async fn find_agents_by_user(
&self,
user_id: UserId,
) -> Result<Vec<DiscoveredAgent>>
pub async fn find_agents_by_user( &self, user_id: UserId, ) -> Result<Vec<DiscoveredAgent>>
Find all discovered agents claiming ownership by the given identity::UserId.
Only returns agents that announced with include_user_identity: true
(i.e., agents whose DiscoveredAgent::user_id is Some).
§Arguments
user_id- The user identity to search for
§Errors
Returns an error if the gossip runtime is not initialized.
Sourcepub fn local_addr(&self) -> Option<SocketAddr>
pub fn local_addr(&self) -> Option<SocketAddr>
Return the local socket address this agent’s network node is bound to, if any.
Returns None if no network has been configured or if the bind address is
not yet known.
Note: If the node was configured with port 0, this returns port 0.
Use bound_addr() to get the OS-assigned port.
Sourcepub async fn bound_addr(&self) -> Option<SocketAddr>
pub async fn bound_addr(&self) -> Option<SocketAddr>
Return the actual bound address from the QUIC endpoint.
Unlike local_addr() which returns the configured value
(possibly port 0), this queries the running endpoint for the real OS-assigned
address. Returns None if no network has been configured.
Sourcepub fn build_announcement(
&self,
include_user: bool,
consent: bool,
) -> Result<IdentityAnnouncement>
pub fn build_announcement( &self, include_user: bool, consent: bool, ) -> Result<IdentityAnnouncement>
Build a signed IdentityAnnouncement for this agent.
Delegates to the internal build_identity_announcement method.
§Errors
Returns an error if key signing fails or human consent is required but not given.
Sourcepub async fn start_identity_heartbeat(&self) -> Result<()>
pub async fn start_identity_heartbeat(&self) -> Result<()>
new announcement.
Called automatically by Agent::join_network.
§Errors
Returns an error if a required network or gossip component is missing.
Sourcepub async fn advertise_identity(&self, validity_ms: u64) -> Result<()>
pub async fn advertise_identity(&self, validity_ms: u64) -> Result<()>
Publish a rendezvous ProviderSummary for this agent.
Enables global findability across gossip overlay partitions. Seekers that have never been on the same partition as this agent can still discover it by subscribing to the rendezvous shard topic and waiting for the next heartbeat advertisement.
The summary is signed with this agent’s machine key and contains the
agent’s reachability addresses in the extensions field (bincode-encoded
Vec<SocketAddr>).
§Re-advertisement contract
Rendezvous summaries expire after validity_ms milliseconds. Callers
are responsible for calling advertise_identity again before expiry so
that seekers can always find a fresh record. A common strategy is to
re-advertise every validity_ms / 2. The x0xd daemon does this
automatically via its background re-advertisement task.
§Arguments
validity_ms— How long (milliseconds) before the summary expires. After this time, seekers will no longer discover this agent via rendezvous unless a freshadvertise_identitycall is made.
§Errors
Returns an error if the gossip runtime is not initialized, serialization fails, or signing fails.
Sourcepub async fn find_agent_rendezvous(
&self,
agent_id: AgentId,
timeout_secs: u64,
) -> Result<Option<Vec<SocketAddr>>>
pub async fn find_agent_rendezvous( &self, agent_id: AgentId, timeout_secs: u64, ) -> Result<Option<Vec<SocketAddr>>>
Search for an agent via rendezvous shard subscription.
Subscribes to the rendezvous shard topic for agent_id and waits up to
timeout_secs for a matching saorsa_gossip_rendezvous::ProviderSummary.
On success the addresses encoded in the summary extensions field are
returned.
This is Stage 3 of Agent::find_agent’s lookup cascade.
§Errors
Returns an error if the gossip runtime is not initialized.
Sourcepub async fn create_task_list(
&self,
name: &str,
topic: &str,
) -> Result<TaskListHandle>
pub async fn create_task_list( &self, name: &str, topic: &str, ) -> Result<TaskListHandle>
Create a new collaborative task list bound to a topic.
Creates a new TaskList and binds it to the specified gossip topic
for automatic synchronization with other agents on the same topic.
§Arguments
name- Human-readable name for the task listtopic- Gossip topic for synchronization
§Returns
A TaskListHandle for interacting with the task list.
§Errors
Returns an error if the gossip runtime is not initialized.
§Example
let list = agent.create_task_list("Sprint Planning", "team-sprint").await?;Sourcepub async fn join_task_list(&self, topic: &str) -> Result<TaskListHandle>
pub async fn join_task_list(&self, topic: &str) -> Result<TaskListHandle>
Join an existing task list by topic.
Connects to a task list that was created by another agent on the specified topic. The local replica will sync with peers automatically.
§Arguments
topic- Gossip topic for the task list
§Returns
A TaskListHandle for interacting with the task list.
§Errors
Returns an error if the gossip runtime is not initialized.
§Example
let list = agent.join_task_list("team-sprint").await?;Source§impl Agent
impl Agent
Sourcepub async fn create_kv_store(
&self,
name: &str,
topic: &str,
) -> Result<KvStoreHandle>
pub async fn create_kv_store( &self, name: &str, topic: &str, ) -> Result<KvStoreHandle>
Create a new key-value store.
The store is automatically synchronized to all peers subscribed
to the same topic via gossip delta propagation.
§Errors
Returns an error if the gossip runtime is not initialized.
Sourcepub async fn join_kv_store(&self, topic: &str) -> Result<KvStoreHandle>
pub async fn join_kv_store(&self, topic: &str) -> Result<KvStoreHandle>
Join an existing key-value store by topic.
Creates an empty store that will be populated via delta sync from peers already sharing the topic. The access policy will be learned from the first full delta received from the owner.
§Errors
Returns an error if the gossip runtime is not initialized.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for Agent
impl !RefUnwindSafe for Agent
impl Send for Agent
impl Sync for Agent
impl Unpin for Agent
impl UnsafeUnpin for Agent
impl !UnwindSafe for Agent
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
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