Offline telemetry and edge observability
Offline telemetry is observability for systems that spend most of their life disconnected: an opt-in telemetry sink that streams structured records in-process, push or pull, with no backend to run and nothing that phones home.
What is edge observability?
Observability tooling assumes a pipe: agents on hosts stream metrics, logs, and traces to a backend in near real time. At the disconnected edge that pipe does not exist. A drone mid-mission, a phone in a blackout, or a sensor in a dead zone generates the most interesting events precisely when nothing can be streamed.
Edge observability means capturing structured records on the device itself and surfacing them in-process, so a device can be fully observed with no backend and no connectivity. What you do with the records, render them on a live screen, log them, or ship them when a link returns, is your decision, not the protocol's. The design questions are what gets captured, who decides, and where it goes.
There is also a privacy boundary: a protocol that silently exfiltrates telemetry is a liability. Emission has to be an application decision, not a protocol default.
Diagram: in-process telemetry, where a device running the protocol produces structured records for transport switches, routing decisions, and deliveries, and an installed sink receives them live, push or pull, into the app's own diagnostics dashboard.
In-process observability: unit-07 runs the protocol and produces a structured record for every metrics tick, routing decision, transport switch, and delivery. The sink your app installed receives them live, push or pull, with identifiers scrubbed. The dashboard is yours; the protocol just emits.
How Offline Protocol implements it
Telemetry is a single, structured event stream with one rule: nothing is emitted until your application installs a sink.
No sink, no emission. The protocol has no default backend and no phone-home path. Your app decides what is collected and where it goes.
Your listener fires in-process the instant a record is produced, or you drain a bounded pull queue of 1024 records on a timer. Both see the same stream.
Long-lived identifiers are hashed with SHA-256 before they reach your sink, so you can correlate a peer across events without ever seeing its real id.
Set the metrics cadence, gate MLS detail from off to diagnostic, and switch on per-factor routing breakdowns only while you are debugging DORS.
Push or pull, in-process
Install a sink and the protocol calls it in-process. By default delivery is push: your listener runs the moment a record is produced, straight from the core, so a live diagnostics screen updates as the mesh behaves. The sink runs on hot paths, so it must not block; hand anything expensive to a queue and handle it elsewhere.
For consumers that prefer to poll, an optional bounded pull queue holds up to 1024 records that you drain on a timer. It is first in, first out and drops the oldest record on overflow, so a slow reader degrades gracefully instead of stalling the protocol. A push listener and the pull queue see the same records, so you can mix the two.
// Opt in: nothing is emitted until a sink is installed
const unsubscribe = await protocol.installTelemetrySink(
{ metricsCadenceMs: 5000, mlsVerbosity: 'lifecycle', routingDiagnostic: false, scrubIds: true },
(rec) => {
if (rec.category === 'metricsFrame') render(rec.frame.retryQueue, rec.frame.transports);
if (rec.category === 'routingDecision') log(rec.decision.reasonCode);
},
); Because the sink is local, telemetry needs no connectivity to work: a device deep in a dead zone is fully observable to code running on it. If you want the records off the device, that is your app's job. You can render them, write them to a file, or ship them over the same mesh and internet the rest of the protocol uses when a path appears.
The record categories
Periodic snapshots on a cadence you set: per-transport metrics, retry-queue depth by priority, deduplication stats, pending acknowledgments, neighbor count, current transport, and relay state. This is the surface most dashboards read.
Every change of a transport status, recorded as previous to current, so you can see BLE, WiFi Direct, internet, Reticulum, or Nostr coming up and going down.
When DORS selects, switches, or escalates a transport: the phase, the from and to, the winning score, and a reason code. Switch on the diagnostic tier for per-factor score breakdowns.
Battery, charging, and relay-role changes, each with the current values and a bitmask of what changed, so you can watch a device move in and out of relay duty.
Secure-session events, gated by a verbosity knob: session init and ready, decrypt failures, and session-missing events by default, with per-operation diagnostics available when you need them.
The message lifecycle and other protocol events as structured records, so sent, delivered with latency and hop count, and failed with a reason all share one timeline.
A seventh, forward-compatible extension record carries variants added after your binding was built, so a newer SDK never breaks an older client. You tune the whole stream at install time: the metrics cadence, MLS verbosity from off through lifecycle to diagnostic, per-factor routing score breakdowns for when you are tuning DORS, and identifier scrubbing, which is on by default. They all share one timeline, so cross-category questions ("did the transport switch before or after that delivery failed, and on which hop?") are answerable by direct inspection.
A sink is ordinary application code, which is the point: forward the records to whatever backend your stack already uses, a time-series database, a log pipeline, or a flat file reviewed after the mission. There is no vendor backend to adopt and no agent to deploy, because the instrumentation ships inside the protocol your app already runs, on every platform the SDK supports.
Where it applies
- Autonomous fleets
- Post-mission analysis from records captured while off-link.
- Live events
- Event operators get a picture of mesh behavior under load.
- Public sector
- Humanitarian programs keep auditable operational records.
- What it observes
- The DORS transport and Service Discovery layers, end to end.
Offline telemetry FAQ
Does telemetry compromise the privacy model?
No. The protocol emits nothing on its own. Telemetry exists only when your application installs a sink, the records go only where your code sends them, and long-lived identifiers are hashed before they reach your sink by default.
What is on the stream?
Periodic metrics frames, transport-state transitions, DORS routing decisions, device-capability changes, MLS lifecycle, and protocol events, plus a forward-compatible extension record, all as structured records on one stream.
Is it push or pull?
Both. By default your listener is called in-process the moment a record is produced. You can also drain a bounded pull queue on a timer, and a push listener and pollTelemetry see the same records.
Can we pipe it into our existing observability stack?
Yes. A sink is ordinary application code; forward the records to whatever backend your stack already uses, a time-series database, a log pipeline, or a flat file. There is no vendor backend to adopt and no agent to deploy.
Why is telemetry the right fit for disconnected devices?
Because it needs no backend and no pipe. Observability tooling assumes a device can stream to a server in near real time, and at the disconnected edge that assumption fails. Telemetry delivers structured records in-process, so a device observes itself fully with zero connectivity, and you decide if and how those records ever leave.
What is in a metrics frame?
A metrics frame is emitted on a cadence you set, 5 seconds by default. It carries per-transport metrics, retry-queue depth broken down by priority, deduplication stats, the count of messages awaiting acknowledgment, the neighbor count, the current transport, and whether the device is currently relaying.
How are identifiers protected?
With scrubbing on by default, long-lived identifiers such as peer, user, and group ids are hashed with SHA-256 before they reach your sink, while single-use ids like message and file ids and the message content are left alone. The same raw id always maps to the same hash within a run, so you can correlate a peer without ever seeing its real identifier.
Is there overhead when no sink is installed?
Emission is gated at the source: with no sink installed the protocol emits nothing, so an application that never opts in ships no telemetry and sends no data anywhere.

