The Five Questions
This page mirrors docs/wiki/The-Five-Questions.md in the auki-sdk repo (branch develop).
The repository is the source of truth.
The Auki protocol is built around five questions any node β a phone, a robot, a cloud server, a browser tab β should be able to answer about any other node:
- Identity β who am I?
- Spatial β where did this happen?
- Temporal β when did this happen?
- Networking β how do I talk to you?
- Tokenomics β how do I compensate you?
Every abstraction in the SDK exists to answer one of these. Skim the table for the lay of the land, then read each section for the conceptual frame and the code that implements it.
| Question | What's implemented | What's pending |
|---|---|---|
| Identity | auki-identity (Wallet + ed25519 + libp2p PeerId), auki-jcs + auki-hash (content-addressing), Sensor / Clock / Frame / Detector Registries with explicit peer_id | β |
| Spatial | Pose Logs (from β to transforms over time), Frame Registry, auki-geometry (convention conversion) | Full convert_pose (composition along a transform path) |
| Temporal | Live in-memory DomainClockEstimate (heartbeat-driven), per-peer local_clock_read TimeTransform Log (monotonicβUTC, on disk), auki-time math, Clock Registry with explicit scope | convert_time (a unified API over the live estimate and the recorded log) |
| Networking | libp2p substrate, 8 typed peer protocols, auki-session Peer / Session (declarative app API) + auki-domain::Domain::join (network presence) | Session::materialize_remote_log (Phase 5 of #216), Python Domain::join binding |
| Tokenomics | Wallet exists as the on-device primitive | All payment / billing rails |
Identity β Who am I?β
A peer needs a durable, cryptographically grounded answer to "who am I" β and the same answer needs to mean the same thing to every other peer. Three IDs cover the practical surface area:
| ID | Lifetime | Where it comes from |
|---|---|---|
peer_id | Durable per device | Wallet::derive_child("peer/v1") β libp2p PeerId |
app_id | Stable per app on this device | Caller-supplied string (e.g. "galbot-ctrl") |
session_id | Fresh per session start | ULID minted by Peer::start_session() |
Beyond those three, every long-lived thing the SDK references β a sensor, a clock, a coordinate frame, a detector, an individual log β also needs identity. The SDK uses content-addressing: every registry entry's (peer_id, id, hash) triple uniquely names it, where the hash is XXH3-128 over RFC 8785 JCS-canonical JSON. The hash is the version; refining an entry produces a new sibling row under the same id.
What addresses itβ
auki-identityβWallet, deterministic child derivation, signed creation certs,load_or_mint_seedauki-jcsβ RFC 8785 JSON canonicalizationauki-hashβ XXH3-128 wrapperauki-registryβSensorRegistryEntry,ClockRegistryEntry,FrameRegistryEntry,DetectorRegistryEntry; each carries an explicitpeer_idfield after the #216 schema migration
How a consumer composes the answerβ
let peer = Peer::new("galbot-01", "galbot-ctrl")
.with_storage_root("/data/auki/galbot-01".into());
let session = peer.start_session()?;
// peer / app / session β the three SDK IDs
session.peer_id(); // "galbot-01"
session.app_id(); // "galbot-ctrl"
session.session_id(); // 26-char ULID
// Content-addressed registry entry (registries are peer-level)
let frame = peer.register_frame("head_left_camera_optical", FrameDef::ros_optical())?;
// frame.peer_id == "galbot-01", frame.id == "head_left_camera_optical", frame.hash == "<xxh3>"
See also: Concept: Peer-Owned Logs β why every data product also carries a peer_id.
Spatial β Where did this happen?β
A spatial computing protocol needs to answer "where was X at time t" without a central authority deciding the answer. The Auki SDK encodes spatial relationships as a graph of timestamped pose logs: each edge is a (from_frame, to_frame) transform sampled over time.
T_X_session(t) = T_body_session(t) β T_X_body(t)
A consumer walks the transform path, looks up or interpolates each edge at time t, and composes the chain. The Frame Registry defines conventions (handedness, axes, units) so the math is unambiguous; auki-geometry does the convention conversion when two peers express the same physical reality in different frame conventions.
What addresses itβ
auki-registryβ Frame Registry withFrameDefpresets (ros_body,ros_optical,opengl,unity)auki-manifestsβPoseLogManifest,PoseSource,PoseWriterMode(rigid / movable)auki-sessionβregister_pose_log,PoseLogSpec,PoseLogHandleauki-geometryβconvert_pose_convention,convert_point_convention,convert_vector_convention,convert_direction_convention
What's pendingβ
- Full
convert_poseβ the operation that composes pose-log paths and answers "where was X at time t" by walking the transform graph. The convention layer (convert_pose_convention) is in place; the path-walking composition is not. Session::resolve_static_transformβ reading a one-sample sealed rigid pose log (today this returnsNotImplementedError; tracked as Phase 5 of #216).
How a consumer composes the answer (today)β
let pose_log = session.register_pose_log(PoseLogSpec {
from_frame: world,
to_frame: base_link,
clock,
source: PoseSource::Manual,
writer_mode: PoseWriterMode::Movable,
expected_rate_hz: 30,
head: HeadSpec::Rolling { retention_ns: 5_000_000_000 },
segment_duration: Duration::from_secs(1),
retention: Duration::from_secs(5),
})?;
// pose_log.resource_id() == "world->base_link"
For now, traversing multi-edge transform paths is a consumer-side concern. The convert_pose card will close that gap.
Temporal β When did this happen?β
Multiple peers run on different clocks: a robot's monotonic system clock, a host's UTC clock, a sensor's hardware-stamped clock. The SDK refuses to canonicalize on any one of them. Every timestamp ships with a named clock identity, and the SDK runs two parallel paths for relating clocks to each other β one live and in-memory for cluster-wide alignment, one persisted on disk for offline replay.
Live: heartbeat-driven domain-clock convergenceβ
The /auki/heartbeat/0.0.1 protocol carries the four NTP timestamps (t0/t1/t2/t3) on every beat. Each peer's ClockSyncHandle (in auki-time) accumulates samples per (local_clock, remote_clock) pair and emits a ClockTransformEstimate(offset_ns, uncertainty_ns). The Manager announces a HeartbeatDomainClock in its heartbeats β "the domain clock is at offset N ns from my backing clock." Each peer composes (local β backing) with (backing β domain) via auki_time::estimate_domain_clock and ends up with a live DomainClockEstimate(local β domain).
ClusterManager::domain_clock_estimate(local_clock_id) returns that estimate for use anywhere in app code. This is what "domain time" actually is, today. It's live state β never persisted as a TimeTransform Log.
Persisted: TimeTransform Logs on diskβ
A TimeTransform Log records sampled offsets between two clocks over time. The only current writer is the per-peer local_clock_read sampler in auki-time (1 Hz) β it pairs local CLOCK_MONOTONIC β CLOCK_REALTIME on a single device, anchoring monotonic timestamps in UTC for offline replay. It does not record the cluster-wide domain clock; that lives in memory only.
What addresses itβ
auki-registryβ Clock Registry;ClockBody::MonotonicClock,UtcClock, etc., with explicitscope(device-local,global)auki-manifestsβTimeTransformLogManifest,TimeTransformSourceauki-timeβSessionClock,TimeTransformmath,NtpExchange/NtpSample/compute_ntp_sample/select_best_ntp_sample,ClockSyncHandle(in-memory NTP accumulator),estimate_domain_clock(composes the live estimate), 1 Hzlocal_clock_readsampler that writes the per-peer monotonicβUTC TimeTransform Logauki-domainβClusterManager::domain_clock_estimate(local_clock)returns the liveDomainClockEstimateauki-networkβ heartbeat protocol carries the four NTP timestamps and the Manager'sHeartbeatDomainClockdescriptorauki-sessionβregister_time_transform_log,TimeTransformLogSpec
What's pendingβ
convert_timeβ a published SDK operation that takes(local_ts_ns, local_clock_ref, target_clock_ref) β target_ts_ns. The primitives exist (liveDomainClockEstimate, on-disk TimeTransform Log entries); the consume-side operation that picks between them and produces a reproducible conversion does not.
How a consumer composes the answer (today)β
let sdk_clock = session.register_clock("session/sdk_clock", ClockBody::MonotonicClock(...))?;
let wall_clock = session.register_clock("wall_clock", ClockBody::UtcClock(...))?;
// Per-peer monotonic β UTC log on disk, written by the local_clock_read sampler.
let _tt_log = session.register_time_transform_log(TimeTransformLogSpec {
from_clock: sdk_clock,
to_clock: wall_clock,
source: TimeTransformSource::LocalClockRead,
..
})?;
// Live cluster-wide alignment β composed from heartbeats, never persisted.
// (`domain` from `auki_domain::Domain::join(&peer, &session, config)`)
let cluster = domain.cluster_manager();
let estimate = cluster.domain_clock_estimate()?;
// estimate.total_offset_ns + estimate.uncertainty_ns
// Or get domain time directly: cluster.domain_time_now()? -> i64
Why no canonical clock? Picking UTC (or any clock) as the default would silently impose a conversion at every boundary. Keeping the conversion explicit β and (when persisted) recorded as a log of its own β keeps the lineage auditable and the SDK honest about what it's done to a timestamp.
Networking β How do I talk to you?β
The Auki SDK runs on libp2p β peer-to-peer, transport-agnostic, no central server. Two peers discover each other through a Discovery HTTP service, exchange identity over /auki/info, agree on cluster membership through /auki/join + /auki/membership, advertise their data products through /auki/resources/0.2.0, and stream live data over /auki/stream/0.2.0.
App code never touches libp2p protocol plumbing directly. Apps construct a Peer, start a Session, declare what they have, and call auki_domain::Domain::join(&peer, &session, config) β the SDK registers stream handlers, advertises the catalog, and serves anyone who asks.
What addresses itβ
auki-networkβ libp2p substrate (TCP/QUIC, Noise, Yamux, Circuit Relay v2), typed/auki/stream/0.2.0streams, Discovery HTTP client with Manager + relay address hintsauki-domainβDomain::join(&peer, &session, config)is the app-facing entry;ClusterManager(cluster bootstrap, Manager election, membership, resource catalog, stream serving) is the engineDomainconstructs and ownsauki-domain-relayβ Domain Relay for browser-compatible reachability through Circuit Relay v2 (WIP)auki-sessionβPeer+Session(identity, registries, log registration; network-free) and thematerialize_remote_logstub
The wire protocolsβ
| Protocol | Purpose |
|---|---|
/auki/join/0.0.1 | Joiner asks Manager to admit it |
/auki/heartbeat/0.0.1 | Pairwise liveness for Manager-death detection |
/auki/membership/0.0.1 | Manager gossips membership |
/auki/info/0.0.1 | Peer-to-peer ParticipantInfo exchange |
/auki/resources/0.2.0 | Catalog row fetch (one row per peer-owned log) |
/auki/registries/0.2.0 | Hash-pinned registry entry fetch |
/auki/stream/0.2.0 | Typed live data streaming |
Plus an HTTP control API β a separate operator-facing surface for daemons that produce SDK sessions (BoosterApp, Sentinel), so any UI like Park can drive them through a uniform contract.
What's pendingβ
Session::materialize_remote_logβ Phase 5 of #216. The plumbing for opening a/auki/stream/0.2.0substream against a remote peer's log exists; the materialization layer that writes a local replica with its own retention policy does not.- Python
Domain::joinβ no Python binding yet (it requires a pre-built libp2p swarm, which Rust callers build themselves); Python daemons driveauki-domain-py'sClusterManagerdirectly.
How a consumer composes the answerβ
let domain = auki_domain::Domain::join(&peer, &session, DomainConfig {
target,
local_identity,
local_multiaddrs,
discovery_url,
swarm,
stream_provider,
daemon_info,
}).await?;
let catalog = domain.catalog(); // what this peer offers
// other peers see this peer's catalog over /auki/resources/0.2.0
Tokenomics β How do I compensate you?β
Peer-to-peer means no platform-level billing. Eventually a peer offering bytes (a robot's camera feed, an edge inference server's compute) needs a way to charge for it, and a peer consuming bytes needs a way to pay.
This question is not implemented. The on-device primitive that future payment rails will bind to is in place:
auki-identityβWallet: ed25519 keypair, deterministic child derivation, signed creation certs
Everything beyond that β payment channels, settlement, billing β is future work. The point of identifying tokenomics as one of the five questions is to make it a first-class architectural concern, not a bolted-on afterthought.
Composing the answers β a Session is the integration pointβ
The Peer / Session / Domain trio is where the five questions converge. A peer declares this device's identity and what it has, each session captures sensor data with the right spatial frame and temporal clock, a domain joins the network on the pair's behalf, and (eventually) payments settle through the Wallet.
Peer
βββ peer_id, app_id β Identity
βββ register_sensor / register_frame /
β register_detector β Identity (registries)
βββ start_session() β Session
βββ session_id + monotonic/UTC clocks β Identity / Temporal
βββ register_clock β Temporal
βββ register_*_log + HeadSpec β Spatial / Temporal lineage
Domain::join(&peer, &session, config)
βββ catalog serving + cluster lifecycle β Networking
βββ (wallet) β Tokenomics (future)
Every line of app code that interacts with the SDK is interacting with one of these. The five questions aren't external commentary on the architecture β they are the architecture.
See alsoβ
- Quickstart β boot a Session and register your first peer-owned log
- Concept: Peer-Owned Logs β the SDK's core data invariant, which threads through Identity / Spatial / Temporal
VISION.mdβ aspirational spec, including the spatial reasoning model- Top-level README β full crate map and shipped status per question