agentio is a Rust library for composing authenticated peerbus endpoints by
topic name. One Agent owns one peerbus::Node, one Ed25519 identity, a local
directory, and its control-plane workers. A multi-machine composition consists
of several Agents that exchange signed directory records.
- A Machine is one endpoint identity and its
peerbus::Node. - An Agent is the owning handle for that Machine's IO and directory state.
- A topic is a normalized absolute path such as
/perception/pose. qualify_participant_topicandsubscribe_inprovide local topic-prefix convenience. There is no participant registry or participant discovery.
Each hosted exchange is recorded with its topic, exchange family, request and
response type hashes, owner endpoint, revision, lease expiry, machine name, and
owner signature. Received announcements and snapshots are verified before
insertion. Older revisions, expired records, invalid signatures, type
mismatches, and competing live owners are rejected. Hosted records are renewed;
dropping the returned Registered publisher/server handle sends a signed
withdrawal bound to the exact signed record it removes. Startup and periodic
reconciliation recover missed announcements; reconcile_now also triggers it
on demand.
Snapshot pages carry a generation number, so a mutation during pagination
restarts the snapshot instead of mixing two directory states.
Discovery and authorization are separate:
bootstrap([...])configures outbound directory seeds only.allow_peer(id)andallow_peers([...])configure inbound transport access.allow_any_peer()explicitly opts out of the deny-by-default allowlist.DirectoryMode::Replicatedreconciles every configured seed.DirectoryMode::FrontDoor(id)uses one directory authority. The front-door ID is not automatically authorized for inbound transport.
For production, exchange endpoint IDs through a trusted channel and use
explicit allowlists. allow_any_peer is appropriate only for a deliberately
permissive trust boundary. An ALPN identifies a protocol; it is not a secret or
an authorization mechanism.
A typed client first checks its verified local directory and can query a seed's
reserved __agentio_resolve req/res service. It validates the returned topic,
exchange family, and type hashes before dialing the owner directly. by_id
bypasses directory resolution but not peerbus transport authorization.
Each configured directory target has one bounded worker and one persistent
control client. Unreachable seeds therefore do not serialize calls to healthy
seeds. control_timeout bounds the caller-visible wait; the target worker may
still be completing the underlying peerbus call. lease_duration controls
record expiry and the automatic renewal cadence. The defaults are six seconds
for control calls and 30 seconds for leases.
Peerbus can use shared memory for co-located endpoints and iroh QUIC for remote
endpoints. skip_shm() forces QUIC; no_relay() disables relay fallback.
wait_for_direct_addresses provides bounded address readiness for remote
setups. Rust transport type hashes include the Rust type path, so separate
binaries must import shared message definitions from the same crate. The heavy
and camera examples use the small examples/shared_types support crate for
this reason.
IdentitySource::Random creates a non-persistent identity. Named, default, DID,
and explicit-file sources persist 32-byte keys with owner-only Unix permissions
and fail on malformed or insecure existing files. DID-index copy failures are
returned to the caller. A human-readable name is never converted into private
key material. See
docs/migrations/identity-derived-name.md
when migrating from the removed name-derived API.
use agentio::{Agent, IdentitySource};
use datapod::datapod;
use std::time::Duration;
#[datapod]
struct Pose {
x: f32,
y: f32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let agent = Agent::builder()
.identity(IdentitySource::Random)
.name("local")
.build()?;
let mut publisher = agent.publish::<Pose>("/pose")?;
let mut subscriber = agent.subscribe::<Pose>("/pose")?;
publisher.send(&Pose { x: 1.0, y: 2.0 })?;
let sample = subscriber
.recv_timeout(Duration::from_secs(1))?
.ok_or("pose timed out")?;
assert_eq!(sample.header().x, 1.0);
Ok(())
}Agent clones share one inner node. Dropping a non-final clone changes nothing;
dropping the final clone signals and joins both control workers before the node
is released. Publisher and server wrappers withdraw their records on drop while
the Agent remains live.
directory_health() snapshots announcement successes/failures, resolver
errors, rejected records, last successful reconciliation, stale seed count,
conflicts, and pending announcements. Configuration, signature, expiry,
ownership, exchange, type, batch, and stale-revision failures are typed Error
variants.
The Makefile is the supported command surface:
make run
make run EXAMPLE=05_all_exchanges
make run EXAMPLE=07_client_all ARGS='<SERVER_DID_KEY>'
make integration
make agent-test
make directory-test
make remote-test
make examples-smoke
make verifyExamples 10/11 publish RGB frames from a Linux V4L2 camera and subscribe from a second Agent. They use GStreamer for camera capture and optional display, while the frame payload travels through agentio over iroh by default.
# Terminal 1: copy the printed did:key value
make camera-publisher CAMERA_DEVICE=/dev/video0
# Terminal 2: receive frames and open a display window
make camera-subscriber SERVER='<PUBLISHER_DID_KEY>' ARGS='--display'Pass ARGS='--frames 30' for a bounded headless receiver, or ARGS='--use-shm'
to either target when shared-memory transport is desired. Camera access
requires read/write permission on the selected /dev/video* device.
ARGS='--test-pattern' on the publisher replaces the camera with a GStreamer
test pattern for transport testing.
Examples 06/07 are the two-process exchange demos. Examples 08/09 are bounded on every remote-controlled allocation and are throughput demos that measure actual received payload bytes and validate their content; they are not repeatable statistical benchmarks.
agentio is library-only. CI builds, tests, lints, and documents the library;
it does not package a nonexistent application binary. Source releases remain a
manual maintainer operation.
The audit target explicitly reports but does not block on
RUSTSEC-2023-0071, a medium-severity timing advisory in rsa with no fixed
release. Agentio's directory protocol uses Ed25519, not RSA. Applications that
use RSA through the re-exported authbox surface must assess that advisory for
their own threat model. The current dependency graph also produces a
non-vulnerability maintenance warning for transitive paste 1.0.15 through
iroh's networking stack.