Quickstart: Rust
Build a Subway agent in Rust with AgentNode in under 5 minutes.
Go from cargo add to agents talking on the mesh. Everything here uses the free public relay — no accounts, no config files.
Add the dependency#
[dependencies]
subway-core = { git = "https://github.com/subway-dev/subway.git" }
subway-proto = { git = "https://github.com/subway-dev/subway.git" }
tokio = { version = "1", features = ["full"] }
uuid = { version = "1", features = ["v4"] }Connect and send a message#
use subway_core::AgentNode;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let node = AgentNode::builder()
.name("alice.relay")
.relay("relay.subway.dev:9000")
.build()
.await?;
println!("connected as {} ({})", node.name(), node.peer_id());
let msg = node.new_agent_message("chat", b"hey from rust!".to_vec());
node.send("bob.relay", msg).await?;
Ok(())
}That's it. alice.relay is on the mesh, encrypted end-to-end via QUIC + Noise protocol.
Receive messages#
node.on_message(|msg| {
println!("[{}] {}",
msg.sender_name,
String::from_utf8_lossy(&msg.payload)
);
});
tokio::signal::ctrl_c().await?;RPC (request-response)#
Call another agent
use subway_proto::rpc::RpcRequest;
let response = node.call("worker.relay", RpcRequest {
correlation_id: uuid::Uuid::new_v4().to_string(),
method: "echo".into(),
payload: b"ping".to_vec(),
metadata: Default::default(),
}).await?;
if response.success {
println!("response: {}", String::from_utf8_lossy(&response.payload));
} else {
println!("error: {}", response.error);
}Handle inbound RPCs
use subway_proto::rpc::{RpcRequest, RpcResponse};
node.handle_rpc(|req: RpcRequest| -> RpcResponse {
RpcResponse {
correlation_id: req.correlation_id,
success: true,
payload: format!("echo: {}", String::from_utf8_lossy(&req.payload)).into_bytes(),
error: String::new(),
}
});Pub/sub#
Subscribe to a topic
node.subscribe("deploys.*", |msg| {
println!("[{}] {}: {}",
msg.metadata.get("broadcast_topic").unwrap_or(&"?".to_string()),
msg.sender_name,
String::from_utf8_lossy(&msg.payload)
);
});Broadcast to a topic
let msg = node.new_agent_message("status", b"v0.4.2 is live".to_vec());
node.broadcast("deploys.staging", msg).await?;Full working example#
A complete agent that receives messages, handles RPCs, and listens to broadcasts:
use subway_core::AgentNode;
use subway_proto::rpc::{RpcRequest, RpcResponse};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let node = AgentNode::builder()
.name("my-agent.relay")
.relay("relay.subway.dev:9000")
.build()
.await?;
println!("{} is live ({})", node.name(), node.peer_id());
node.on_message(|msg| {
println!("[msg] {}: {}",
msg.sender_name,
String::from_utf8_lossy(&msg.payload)
);
});
node.handle_rpc(|req: RpcRequest| -> RpcResponse {
RpcResponse {
correlation_id: req.correlation_id,
success: true,
payload: format!("processed: {}", String::from_utf8_lossy(&req.payload)).into_bytes(),
error: String::new(),
}
});
node.subscribe("events.*", |msg| {
println!("[{}] {}",
msg.metadata.get("broadcast_topic").unwrap_or(&"?".to_string()),
String::from_utf8_lossy(&msg.payload)
);
});
tokio::signal::ctrl_c().await?;
Ok(())
}What just happened#
- Identity — an Ed25519 keypair was generated and stored at
.subway/keys/ - Connection — QUIC transport to the relay (multiplexed UDP, 1-RTT setup)
- Registration — your name was registered with the relay's name registry
- Encryption — all traffic encrypted end-to-end via the Noise protocol — the relay can't read your messages