Beacon
SDKs

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}");
}

On this page