Robot and Topic Discovery
Adamo uses liveliness tokens to track which participants are online. A robot declares a token; the token disappears when the connection drops; everyone else watches the token namespace and reacts.
This is what powers the robot list on operate.adamohq.com. You can also use it directly from your own programs to build dashboards, fleet-management tools, or operators that automatically connect to the first available robot.
For live topic inspection, subscribe to a wildcard and print the keys that arrive. Adamo does not keep a global registry of every possible pub/sub key; a topic is visible when it publishes traffic. Use liveliness for “who is online?” and a wildcard topic tap for “what is flowing right now?”.
Declaring Yourself Alive
Section titled “Declaring Yourself Alive”Hold a liveliness token for as long as you want to be visible. Drop it (or let it fall out of scope) to disappear.
import adamosession = adamo.connect(api_key="ak_...")token = session.alive("my-arm")# now visible at my-arm/alive# ... do work ...token.close()If you use adamo.Robot(...), a liveliness token at {name}/alive is declared automatically when the first track or publisher comes up — no manual call needed.
use adamo::Session;let session = Session::open_default("ak_...")?;let _token = session.alive("my-arm")?; // dropped → undeclared// ... do work ...adamo_session_t *sess = adamo_open_default("ak_...");adamo_liveliness_token_t *tok = adamo_liveliness_declare(sess, "my-arm");/* ... do work ... */adamo_liveliness_token_free(tok);Listing Online Robots
Section titled “Listing Online Robots”A one-shot query that returns every live token matching a pattern. The default **/alive covers every robot in your org.
for key in session.live_tokens(): print(key)# trainer-01/alive# xr-operator/alivefor key in session.live_tokens("**/alive")? { println!("{key}");}size_t count = 0;char **tokens = adamo_liveliness_get(sess, "**/alive", &count);for (size_t i = 0; i < count; i++) { printf("%s\n", tokens[i]);}adamo_liveliness_tokens_free(tokens, count);You can narrow the pattern — arm-*/alive for a specific class of robot, **/video/*/alive to enumerate active video tracks (each track also declares its own liveliness).
Listing Live Topics
Section titled “Listing Live Topics”There is no broker-side “list every topic that could exist” call for live traffic. The practical live workflow is to subscribe to a wildcard, print each new sample.key, and keep the process running while you operate the robot.
Use patterns like:
| Pattern | What you will see |
|---|---|
my-arm/** | Every sample under one robot. |
**/control/** | Gamepad, XR, GELLO, and other control inputs. |
**/video/** | Video packets and video-side control/stat topics. |
** | Every sample visible to your org. Noisy, but useful when debugging. |
import adamo
session = adamo.connect(api_key="ak_...")seen = set()
with session.subscribe("my-arm/**") as sub: for sample in sub: if sample.key not in seen: seen.add(sample.key) print(sample.key)Drive the robot from operate.adamohq.com while this runs. You should see keys such as my-arm/control/joy, my-arm/control/cdr/xr_tracking, my-arm/video/main, and my-arm/stats/latency as those streams publish.
use adamo::Session;use std::collections::HashSet;use std::sync::{Arc, Mutex};
let session = Session::open_default("ak_...")?;let seen = Arc::new(Mutex::new(HashSet::new()));let seen_for_cb = Arc::clone(&seen);
let _sub = session.subscribe_with("my-arm/**", move |sample| { let mut seen = seen_for_cb.lock().unwrap(); if seen.insert(sample.key.clone()) { println!("{}", sample.key); }})?;
std::thread::park();#include <adamo/adamo.h>#include <stdio.h>
void on_sample(const adamo_sample_t *s, void *user) { (void)user; printf("%s\n", s->key);}
adamo_session_t *sess = adamo_open_default("ak_...");adamo_cb_sub_t *sub = adamo_subscribe_cb(sess, "my-arm/**", on_sample, NULL);/* Keep process alive while you inspect traffic. */For C, this prints every matching sample. Pipe the output through sort -u if you want a deduplicated list.
import { watchTopics } from "@adamo/fleet";
const handle = await watchTopics( session, `adamo/${org}/my-arm/**`, { ttlMs: 5000 },);
handle.onUpdate((topics) => { console.table(topics);});
// Later:handle.close();The TypeScript SDK works with full router keys, so include the adamo/{org}/ prefix.
If you need a durable topic list for a recording, use the Data page in operate.adamohq.com or the recorded-data APIs. Recorded sessions store their topic list because those topics have already been observed and persisted.
Watching for Changes
Section titled “Watching for Changes”The interesting case is reacting in real time as robots come and go. Subscribe with history=true to also receive the current set of live tokens up front, so your handler sees them as they appear.
def on_change(key, alive): print(f"{'UP' if alive else 'DOWN'}: {key}")
session.on_liveliness("**/alive", callback=on_change, history=True)let _sub = session.on_liveliness("**/alive", true, |key, alive| { println!("{}: {key}", if alive { "UP" } else { "DOWN" });})?;std::thread::park();void on_change(const char *key, int alive, void *user) { printf("%s: %s\n", alive ? "UP" : "DOWN", key);}
adamo_liveliness_sub_t *sub = adamo_liveliness_subscribe( sess, "**/alive", /* history */ 1, on_change, NULL);/* ... */adamo_liveliness_sub_free(sub);Patterns Worth Knowing
Section titled “Patterns Worth Knowing”| Pattern | What it lists |
|---|---|
**/alive | Every live participant in the org. |
arm-*/alive | All robots whose name starts with arm-. |
**/video/*/alive | Every currently-active video track (across all robots). |
xr-*/alive | All operator-style participants named xr-…. |
The token namespace is just a key expression — model it however suits your fleet.