Skip to main content

Agent

Struct Agent 

Source
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

Source

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().

Source

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() or with_user_key_path()
Source

pub fn identity(&self) -> &Identity

Get the agent’s identity.

§Returns

A reference to the agent’s identity::Identity.

Source

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.

Source

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.

Source

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.

Source

pub fn agent_certificate(&self) -> Option<&AgentCertificate>

Get the agent certificate, if one exists.

The certificate cryptographically binds this agent to a user identity.

Source

pub fn network(&self) -> Option<&Arc<NetworkNode>>

Get the network node, if initialized.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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:

  1. Direct — if the agent reports can_receive_direct: true or has a traversable NAT type, try each known address in order.
  2. Coordinated — if direct fails or the agent reports a symmetric NAT, the outcome is Coordinated if any address was reachable via the network layer’s NAT traversal.
  3. Unreachable — no address succeeded.
  4. 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.

Source

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.

Source

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?;
Source

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());
    }
}
Source

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)
    }
}
Source

pub fn subscribe_direct(&self) -> DirectMessageReceiver

Subscribe to direct messages.

Returns a receiver that can be cloned for multiple consumers. Messages are broadcast to all receivers.

§Example
let mut rx = agent.subscribe_direct();
tokio::spawn(async move {
    while let Some(msg) = rx.recv().await {
        println!("Direct message: {:?}", msg);
    }
});
Source

pub fn direct_messaging(&self) -> &Arc<DirectMessaging>

Get the direct messaging infrastructure.

Provides low-level access to connection tracking and agent mappings.

Source

pub async fn is_agent_connected(&self, agent_id: &AgentId) -> bool

Check if an agent is currently connected for direct messaging.

§Arguments
  • agent_id - The agent to check.
§Returns

true if a QUIC connection exists to this agent’s machine.

Source

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.

Source

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 Blocked senders (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).

Source

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 include user_id and certificate
  • human_consent - Must be true when 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
Source

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.

Source

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.

Source

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.

Source

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).

Source

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)
Source

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
Source

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.

Source

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.

Source

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).

Source

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.

Source

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 (15). Typical: 2.
  • timeout_ms — Query timeout in milliseconds. Typical: 5000.
§Errors

Returns error::NetworkError::NodeError if no network config was provided.

Source

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.

Source

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:

  1. Cache hit — return addresses immediately if the agent has already been discovered.
  2. Shard subscription — subscribe to the agent’s identity shard topic and wait up to 5 seconds for a heartbeat announcement.
  3. Rendezvous — subscribe to the agent’s rendezvous shard topic and wait up to 5 seconds for a ProviderSummary advertisement. 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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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 fresh advertise_identity call is made.
§Errors

Returns an error if the gossip runtime is not initialized, serialization fails, or signing fails.

Source

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.

Source

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 list
  • topic - 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?;
Source

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

Source

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.

Source

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§

Source§

impl Debug for Agent

Source§

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

Formats the value using the given formatter. Read more

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> 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<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> 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

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

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: 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
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: 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.
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