Rust SDK
Robot-side structured logging plus an async operator client for the Beacon control plane.
The Rust SDK lives in sdk/rust and mirrors the two-package shape of the
Python SDK:
Client— operator-side. Talks to the Beacon API over HTTPS with a bearer portal token.Beacon— robot-side. Writes structured JSONL records to the log path tailed by the Rust agent.
Install
Until the crate is published, use a path dependency:
[dependencies]
beacon = { path = "./sdk/rust" }
serde_json = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }Enable live log streaming with the optional ws feature:
beacon = { path = "./sdk/rust", features = ["ws"] }Robot-side Beacon helper
use beacon::Beacon;
use serde_json::json;
let robot = Beacon::connect()?;
robot.log("starting pick task", "info", "robot_app", json!({
"task_id": "abc123"
}))?;
robot.event("pick_complete", json!({ "duration_ms": 842 }))?;
robot.metric("battery_pct", 73.2, ())?;
robot.ros_node("nav2_lifecycle_manager", "ros2", "running", ())?;
robot.rosbag("/var/lib/rosbags/run1.bag", "started", "ros2", ())?;Beacon::connect() reads BEACON_LOG_PATH or ROBOFLARE_LOG_PATH and
defaults to ~/.roboflare/app.log. Every call appends one JSON record
with ts, level, source, message, and fields.
To publish an application config directory for the agent:
let mut robot = Beacon::from_env_with_config_dir(Some("/opt/robot/config"))?;
let nav_config = robot.config_path("nav.behavior")?;The SDK writes ~/.roboflare/sdk.json, which the agent already reads
when resolving the effective config root.
Operator client
use beacon::Client;
let client = Client::builder()
.token(std::env::var("BEACON_TOKEN")?)
.base_url("https://beacon-api.canlearn.workers.dev")
.build()?;
let robots = client.robots().list().await?;
let release = client
.releases()
.create(serde_json::json!({
"semver": "1.2.3",
"notes": "speed tune"
}))
.await?;Resource methods unwrap Beacon's { "data": ... } and { "items": [...] }
response envelopes and return typed SDK models such as Robot,
Release, Deployment, and EnrollmentKey. Low-level client.get(...)
and client.post(...) remain available when a caller needs raw JSON.
Resources
client.robots().list().await?;
client.robots().inspect(robot_id).await?;
client.robots().health(robot_id).await?;
client.robots().timeline(robot_id).await?;
client.robots().logs(robot_id).await?; // requires the `ws` feature
client.fleets().list().await?;
client.fleets().logs(fleet_id).await?;
client.sites().list().await?;
client.sites().create(input).await?;
client.releases().list().await?;
client.releases().create(input).await?;
client.releases().deploy(release_id, DeployTarget::Fleet(fleet_id)).await?;
client.configs().list().await?;
client.configs().set(input).await?;
client.configs().deployments().list().await?;
client.configs().deployments().create(input).await?;
client.configs().deployments().events(deployment_id).await?;
client.deployments().list().await?;
client.deployments().create(input).await?;
client.deployments().events(deployment_id).await?;
client.enrollment_keys().list().await?;
client.enrollment_keys().create(input).await?;
client.enrollment_keys().revoke(key_id).await?;
client.org_users().list().await?;
client.org_users().create(input).await?;
client.org_users().revoke(user_id).await?;Streaming logs
let mut logs = client.robots().logs(robot_id).await?;
while let Some(line) = logs.next().await? {
println!("{line}");
}