Four layers. One tight contract.
The platform splits into a Client SDK, a Coordinator control plane, a Cloud Inference fallback layer, and a Developer API. Each layer is independently deployable and independently testable.
System overview
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β DEVELOPER'S WEB APP β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β @meshinfer/sdk Β· @meshinfer/react β β
β β βββββββββββββββ βββββββββββββββ ββββββββββββββββ β β
β β β Probe β β Local Runtimeβ β Router Clientβ β β
β β β (GPU/RAM) β β WebGPU/WASM β β WS + REST β β β
β β βββββββββββββββ βββββββββββββββ ββββββββ¬ββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββββββ
β TLS 1.3 + mTLS
ββββββββββββββββββββββββββββββββββΌβββββββββββββββββ
β COORDINATOR (runtime) β
β βββββββββββββ ββββββββββ ββββββββββββββββ β
β β Gateway ββ β Router ββ β Task Queue β β
β β (edge gw) β β Engine β β (queue) β β
β βββββββββββββ ββββββ¬ββββ ββββββββ¬ββββββββ β
β ββββββββββββββΌββββββββββββββΌβββββββββββ β
β βΌ βΌ βΌ βΌ β
β ββββββββββββ βββββββββββββ ββββββββββ ββββββββββ
β β Registry β βReputation β βVerify β βBillingββ
β β (store) β βScorer β βSampler β βMeter ββ
β ββββββββββββ βββββββββββββ ββββββββββ ββββββββββ
βββββββββββββ¬ββββββββββββββββββββββββββ¬ββββββββββββ
β β
βββββββββββββββΌβββββββββββ ββββββββββββΌβββββββββββββ
β PEER NODES (mesh) β β CLOUD INFERENCE LAYERβ
β browsers Β· desktops β β OpenAI Β· Anthropic Β· β
β Β· mobile (opt-in) β β Groq Β· Together Β· BYOMβ
βββββββββββββββββββββββββββ βββββββββββββββββββββββββA. Client SDK
Packages shipped
| @meshinfer/sdk | Core TypeScript SDK Β· 38kB gz Β· browser + Node |
| @meshinfer/react | Hooks (useMeshChat, useMeshEmbed, useNodeStatus) + provider |
| @meshinfer/desktop | Electron app wrapping a Rust worker (tokio + candle/llama.cpp bindings) |
| @meshinfer/ios | Swift package Β· CoreML + Metal path Β· App Store distributable |
| @meshinfer/android | Kotlin Β· LiteRT + NNAPI path Β· AAR |
Capability probe
Runs once per session (β€ 40ms) and re-runs on route failure. Emits a signed capability vector:
{
"runtime": "browser" | "desktop" | "ios" | "android",
"cpu": { "cores": 8, "arch": "arm64", "isa_ext": ["neon","i8mm"] },
"gpu": { "vendor": "Apple", "model": "M3 Pro", "vram_mb": 18432, "webgpu": true },
"npu": { "present": true, "vendor": "Apple Neural Engine" },
"ram": { "total_mb": 18432, "free_mb": 9112 },
"battery":{ "level": 0.82, "charging": true },
"net": { "rtt_ms": 34, "downlink_mbps": 118, "effective": "4g" },
"sig": "ed25519:..."
}Local runtime selection
| WebGPU available | MLC/WebLLM β ~40 tok/s on M-class, 25 tok/s on mid-range dGPU |
| WebGPU absent | ONNX Runtime Web + WASM SIMD β 6β12 tok/s, Q4 only |
| Desktop | llama.cpp via Rust FFI β full GGUF catalog up to 7B Q5 |
| iOS 17+ | CoreML via ANE for β€3B, Metal Performance Shaders for larger |
| Android | LiteRT + NNAPI on supported SoCs; llama.cpp fallback |
Opt-in controls (user facing)
- First-run consent dialog (required under GDPR; configurable copy)
- Granular toggles: run locally, contribute to mesh, allow cloud fallback
- Battery floor slider (default: pause mesh work under 30%)
- Bandwidth cap (default: pause under metered connection)
- Live status indicator + receipts log
B. Coordinator Backend (Closed Core β DOSFI Crown Jewels)
Components
| Gateway | Edge gateway Β· auth, rate limit, routing-decision cache. p99 β€ 15ms. |
| Router Engine | Selection service over {local, mesh, cloud} with features: cost_pref, latency_pref, device caps, peer health, model availability, current cloud pricing tier. |
| Node Registry | Clustered registry, TTL-keyed. Holds last heartbeat, capability vec, reputation, current load. |
| Heartbeats | 15s interval Β· WebSocket if possible, SSE fallback. 3 missed β marked degraded, 6 missed β evicted. |
| Task Queue | Task queue, partitioned by priority (interactive / background / verification). At-least-once delivery with idempotency keys. |
| Verification Sampler | A fraction of peer tasks duplicated; deterministic-decode hash compare; mismatches widen the sample and start investigation. |
| Reputation Scorer | Continuous score 0β100. Factors: uptime, avg latency vs SLO, verification pass rate, cancellations. Public to router, opaque to peers. |
| Billing Meter | Metering pipeline. Events have `hypothetical_cloud_cost` and `actual_cost` for savings-share. |
| Model Distributor | CDN-backed, signed bundles (Ed25519). Delta updates. Per-device quantization selection. |
Routing decision logic (speculative dispatch)
The Router Engine evaluates each incoming request against the requester's cost, latency, and privacy preferences, the device's capability vector, and the current state of the mesh pool. It selects among three tiers β local execution, peer mesh dispatch, and cloud fallback β choosing the tier that best satisfies the requester's constraints.
Privacy is a structural gate, not a policy check. For local-only requests, the privacy gate resolves synchronously before any network I/O occurs. No speculative cloud stream is started, no fetch is issued, and no data leaves the device. This is enforced by the structure of the routing path, not by a runtime check that could be bypassed.
Speculative dispatch reduces cold-start latency. When local execution is eligible but the model is still cold, the Router may start a cloud stream concurrently with local warm-up. If the local model becomes ready in time, the cloud stream is cancelled and local takes over. If not, the already-started cloud stream is kept. This optimization is never invoked for local-only or cost-first requests.
Candidate selection incorporates reputation and load. Among eligible mesh peers, the Router weights candidates by reputation, model availability, and current load. A small exploration factor ensures the router continues learning and prevents any single node from monopolizing traffic.
C. Cloud Inference Layer
| Providers | OpenAI Β· Anthropic Β· Groq Β· Together Β· Azure OpenAI Β· AWS Bedrock |
| Adapter pattern | Each provider implements `InferProvider` trait (chat, embed, classify, healthcheck, price). |
| Health signals | Active probes every 30s + passive p95 latency per region. Unhealthy β 5-min cool-down. |
| Cost optimizer | When multiple cloud candidates satisfy model+quality SLO, picks lowest `$ / token` after latency penalty. |
| Fallback semantics | Only invoked when local+mesh fail eligibility. If cloud is forbidden by policy, request returns 503 `mesh_no_capacity`. |
D. Developer Integration API
POST /v1/ai/chat
Host: api.meshinfer.ai
Authorization: Bearer msk_live_...
Content-Type: application/json
{
"model": "local-small",
"messages": [
{ "role": "user", "content": "Summarize this article..." }
],
"latency_preference": "low",
"cost_preference": "cheap",
"privacy": "mesh-ok",
"stream": true
}wss://stream.meshinfer.ai/v1/ai/stream
β { "type":"route", "route":"local", "est_ms": 110 }
β { "type":"token", "delta":"The " }
β { "type":"token", "delta":"article argues " }
...
β { "type":"done", "usage":{...}, "savings_usd":0.00031 }G. Energy & Thermal Policy
Mobile devices are first-class mesh participants but must never be exploited as silent compute donors. The following rules are enforced by the Coordinator before any task is dispatched to a mobile node β they are defaults, not opt-ins.
Hard dispatch gates (mobile only)
| Network | Must be Wi-Fi (net.effective β "cellular" / "metered"). Coordinator checks the signed capability vector β no Wi-Fi β node excluded from mesh pool entirely. |
| Power source | Must be charging (battery.charging === true). Battery-only nodes are never assigned mesh work regardless of battery level. |
| Battery floor | Even while charging, dispatch pauses if battery.level < 0.20 (20%). Configurable by user down to 0.10. |
| Thermal state | Device must report thermal β€ "fair". "Serious" or "critical" thermal state β immediate task cancellation and 5-min cool-down before re-evaluation. |
| Foreground only | iOS/Android nodes only accept tasks while the app holds an active foreground or background-task budget window. Silent background execution is never requested. |
Capability vector additions
The probe (Β§ 2.A) is extended with two new fields on mobile runtimes:
{
"battery": {
"level": 0.82,
"charging": true,
"floor": 0.20 // user-configured minimum
},
"thermal": "nominal", // nominal | fair | serious | critical
"app_state": "foreground" // foreground | background-task | background
}User transparency (receipts log)
| Per-session summary | Tasks served, estimated battery % consumed, tokens generated, mesh credits earned. |
| Displayed in SDK UI | Live indicator in the opt-in status widget shows current state: "idle", "contributing", "paused β unplugged", "paused β thermal". |
| Exportable | Full log available via /v1/nodes/me; 30-day rolling retention. |
Mesh participation profiles
Developers set a participation profile at SDK init time. The Coordinator enforces the stricter of the developer profile and the user's personal settings β the user can always tighten, never loosen, what the developer allows.
| eco (default) | Wi-Fi + charging required Β· battery β₯ 20% Β· thermal β€ fair Β· max 30 tasks/min Β· foreground only |
| standard | Wi-Fi + charging required Β· battery β₯ 50% Β· thermal β€ nominal Β· max 60 tasks/min Β· foreground only |
| power | Wi-Fi + charging required Β· battery β₯ 80% Β· thermal = nominal only Β· max 120 tasks/min Β· foreground only |
| off | Node never contributes to mesh. Local inference still runs. Use for latency-critical or regulated apps. |
new MeshInfer.AI({
apiKey: 'msk_live_...',
// Choose the participation profile for this app.
// Users can tighten further in their own settings, but never loosen.
meshParticipationProfile: 'eco', // 'eco' | 'standard' | 'power' | 'off'
// Or override individual thresholds for fine-grained control:
meshPolicy: {
requireCharging: true,
batteryMinLevel: 0.80, // only contribute at 80%+
thermalMaxState: 'nominal',
maxTasksPerMin: 60,
},
});Real-time condition enforcement
The participation profile sets static thresholds. Real-time device conditions act as a continuous hard filter on top β checked every heartbeat (15s). A condition breach triggers an immediate ephemeral off state regardless of the active profile.
The participation profile sets static thresholds. Real-time device conditions β battery level, thermal state, network type β act as a continuous hard filter on top, re-evaluated on every heartbeat. A condition breach triggers an immediate ephemeral off state regardless of the active profile: the node is removed from the dispatch pool, in-flight tasks complete normally, and no new tasks are accepted. Recovery requires the breached condition to remain satisfied across consecutive heartbeats, preventing rapid on/off cycling at threshold boundaries.
| In-flight tasks | Never killed mid-stream. Current task completes; node stops accepting new work. Abrupt kills would penalize reputation score. |
| Hysteresis | Re-entry requires the breached condition to be satisfied for 2 consecutive heartbeats (30s) to prevent rapid on/off cycling at threshold boundaries. |
| Coordinator authority | SDK reports breach on next heartbeat; Coordinator removes the node server-side immediately. If SDK goes silent, node is evicted after 3 missed heartbeats (~45s). |
| App-user experience | Transparent β the request is silently re-routed to mesh or cloud. No error, no interruption, no visible state change. |
Sharded inference & in-flight migration
Single-node dispatch (1Bβ7B models) fails over atomically β the coordinator re-routes the entire request with no partial state to recover. Sharded inference across multiple nodes (70B+) requires an explicit migration strategy. Three approaches, phased by version:
| V1βV2 Β· Stateless re-dispatch | No checkpointing. Node failure β entire request fails over to cloud atomically. Zero complexity, reliable, wastes partial work. Acceptable for completions β€ 512 tokens. 70B sharding is out of scope in V1βV2. |
| V3 Β· KV-cache checkpointing | Coordinator snapshots the KV cache at regular intervals into ephemeral storage. On node failure, replacement node resumes from last checkpoint. Hard fallback SLA: if migration cannot complete within 2Γ the remaining generation budget, abort to cloud. |
| V3 Β· Speculative redundancy (opt-in) | Shard dispatched to 2 nodes simultaneously. Slower node cancelled when faster completes. Zero recovery latency; doubles shard compute cost. Enabled via meshPolicy.shardRedundancy: true for latency-critical long-context workloads. |
KV-cache transfer budget
Before attempting cache migration the Coordinator evaluates a viability gate. Migration is only initiated when it is strictly faster than restarting from cloud. The bandwidth estimate used in the gate is derived dynamically β not from a static config value.
Before attempting migration, the Coordinator evaluates a viability gate: migration is only initiated when it is strictly faster than restarting from cloud. The gate compares estimated transfer time against the remaining generation budget β if migration would consume too much of that budget, the request aborts to cloud immediately and no migration is attempted.
Dynamic bandwidth estimation
estimated_bandwidth_mbps(origin, replacement) is not a static value β it is resolved at dispatch time from the Coordinator's peer-throughput tracker, with an optional probe path for high-stakes decisions.
| Source | How bandwidth_mbps is resolved |
| Historical p50 (default) | Coordinator maintains a rolling p50 of observed payload-transfer throughput for every node-pair that has communicated in the last 6h. Derived from actual task completion times and payload sizes β no extra signaling. |
| Cold-start floor | First dispatch between a node-pair with no shared history uses a conservative 10 Mbps floor. Aggressively biases toward cloud on unknown pairs. |
| Peer probe (opt-in) | Coordinator instructs the replacement node to open a direct channel to the origin and transfer a 64 KB probe payload, reporting RTT and throughput. Adds ~200β400 ms overhead but gives a real-time measurement. Only triggered when cache_size_mb > 80. |
Bandwidth between a node pair is estimated dynamically at dispatch time. The Coordinator maintains a rolling history of observed transfer throughput for node pairs that have communicated recently. For pairs with sufficient shared history, the measured median is used. For pairs with no shared history, a conservative cold-start floor biases the decision toward cloud. For large caches, an optional peer probe can provide a real-time measurement that overrides the historical estimate.
Peer-to-peer throughput tracker
The tracker is a lightweight accounting layer in the Coordinator that piggybacks on normal task telemetry β no extra network round-trips for the common case.
| Event source | Every mesh task completion event carries payload_bytes and wall_ms. The Coordinator derives throughput = payload_bytes / wall_ms and records it against the node-pair key. |
| Storage | Redis sorted set per pair-key. Each entry is (timestamp, throughput_mbps). Entries older than 6h are pruned on write. |
| Aggregation | p50 computed over the last 20 samples (or all samples if fewer). p50 is preferred over mean to suppress outliers from brief congestion spikes. |
| Directionality | Transfer is originβreplacement, but the tracker stores bidirectional samples since either node may be origin in future tasks. Both directions share the same pair-key. |
| Cold-start ramp | After 1 sample: still uses floor. After 2β4 samples: weighted blend (floor Γ 0.5 + measured Γ 0.5). After 5+ samples: fully measured p50. |
Peer probe mechanism
The probe is an optional high-precision path. It adds latency upfront but eliminates surprises on large cache transfers.
When triggered, the Coordinator instructs the replacement node to open a direct authenticated channel to the origin node and transfer a small probe payload, measuring round-trip time and throughput. The probe uses the same encrypted channel as task envelopes and carries random bytes β no prompt data or model weights. If the probe does not complete within its time budget, the decision falls back gracefully to the historical estimate or cold-start floor. Probe failure never aborts a migration on its own.
| When triggered | cache_size_mb > 80 OR meshPolicy.probeBeforeMigration: true. Never triggered for small caches β probe overhead would exceed savings. |
| Node eligibility | Both nodes must be in Standard or Trusted band (reputation β₯ 65). Probing Probation nodes wastes the 500ms budget. |
| Overhead | 200β400ms typical. Budgeted into the migration viability gate: probe time is subtracted from the remaining generation time before the 40% threshold is evaluated. |
| Privacy | Probe payload is random bytes β no prompt data, no model weights. The channel is the same mTLS-encrypted path used for task envelopes. |
Transfer size mitigations
| Prefix-only transfer | Only the prompt-prefix KV is migrated (derived from input, not generated tokens). Generated tokens are cheap to re-run from checkpoint on the replacement node. Cuts transfer size 60β80% for typical chat workloads. |
| Quantized cache transfer | KV cache re-quantized to INT8 for wire transfer (from FP16/BF16), dequantized on receiver. ~50% size reduction with negligible quality impact on continuations. 7B @ 512 tok: 384 MB β ~96 MB. |
| 70B on mesh | KV-cache migration over Wi-Fi is never viable at 70B scale. The only resilience option is speculative redundancy or treating 70B as cloud-only. The spec is explicit on this β it is not a future fix, it is a physics constraint. |
I. Reputation Scoring System
Every peer node carries a continuous reputation score (0β100) computed by the Coordinator from observed behaviour. The score is the primary signal the Router uses to gate dispatch β not just for load-balancing, but to actively protect long-context and latency-critical tasks from nodes with unstable track records.
Score composition
The score is a weighted blend of these signals, normalized to a 0β100 range. New nodes start at a neutral score and earn trust through task history. The score is stored per-node and updated in real time as events arrive β task completions, verification results, and heartbeat observations all adjust the score immediately. Old events lose weight as they age out of their measurement windows, so honest nodes recover automatically without operator intervention.
Dispatch gates by score band
context_tokens > 2048 or model_class = "70b" are hard-blocked from Probation and below. A single flaky node mid-generation on a long-context shard forces a full cloud re-dispatch β the cost of that failure exceeds the savings from ever routing there in the first place.Score update cadence
| Real-time delta | Applied immediately on task completion, verification result, or heartbeat miss. Written to Redis, broadcast to Router via pub/sub. |
| Decay | Old events lose weight as they age out of the measurement window. Score improves naturally as bad events expire β no manual forgiveness needed. |
| Cold-start | New nodes start at 50. They must complete 10 tasks successfully to unlock Standard; 50 tasks to unlock Trusted. Threshold configurable per tenant. |
| Transparency | Score is visible to the developer via /v1/nodes/me. Breakdown by signal is included. Raw score is never exposed to peer nodes β only to the Coordinator. |
Interaction with routing weights
The reputation score enters the Router's multi-armed bandit as a multiplicative modifier on the mesh candidate score (see Β§ 2.B routing logic). A node at 90 reputation gets full weight; a node at 50 gets 0.5Γ weight; a node below 40 is excluded from the candidate set entirely before scoring begins.
The reputation score enters the Router's candidate selection as a multiplicative modifier on the mesh candidate score. Trusted nodes receive full weight; nodes in the probation band are heavily discounted; nodes below the probation threshold are excluded from the candidate set entirely before scoring begins.
H. Mesh Diagnostic Dashboard
A live view of the three key metrics developers should monitor when tuning mesh policies. Values update from the Coordinator's streaming telemetry endpoint (/v1/nodes/metrics/stream).
KV-cache migrations attempted by the Coordinator.
Requests that escaped mesh and hit a cloud provider.
Mean wall-clock time to transfer a KV snapshot between peer nodes.
meshPolicy.migrationBudget to 0.55 or increase batteryMinLevel to reduce eligible-but-slow nodes.Peer Discovery & Mesh Routing
V1βV2 design: coordinator-routed for simplicity. All mesh peer dispatch flows through the Coordinator. There is no direct WebRTC or peer-to-peer connection between client and peer node. This decision trades network latency for operational simplicity and auditability.
Why coordinator-routed, not direct P2P?
| Auditability | Every task & result transits the coordinator β complete audit trail, billing, verification sampling, and reputation scoring are built-in. |
| NAT traversal | Browser/mobile behind NAT cannot easily accept inbound connections. Relaying through coordinator avoids STUN/TURN complexity. |
| Verification | Sampling a fraction of peer tasks to detect cheating requires the coordinator to see every result. P2P direct mode defeats this. |
| Reputation feedback | Router learns latency, correctness, and uptime only if results funnel through the coordinator. Opaque P2P loses signal. |
| Privacy policy enforcement | Policy rules (e.g. "no cloud, mesh-only") can only be enforced if the coordinator is the routing chokepoint. |
| Cost trade-off | Adds ~180ms vs. direct P2P (~50ms local on same LAN). Acceptable for non-interactive workloads; interactive-priority requests go local or cloud. |
Coordinator-routed mesh flow
Browser Coordinator Peer Node
β β β
βββinfer(task)βββΊβ β
β ββdispatch(enc)βββΊβββinferβββ
β β βββββββββββ
β ββββresult(enc)ββββ
ββββtokensβββββββββ β
β ββmeter_event β
β β β
β β (verification: β
β β a sample of β
β β compute hash, β
β β compare, β
β β penalize β
β β mismatches) β- Dispatch: Coordinator selects peer from registry based on reputation, latency, model availability, and privacy policy. Task encrypted with peer's session key (AES-256-GCM derived from TLS exporter).
- Result: Peer returns encrypted result. Coordinator decrypts, verifies hash (on a sample of tasks), updates reputation, meters usage, streams tokens to client.
- Latency cost: +180ms round-trip to coordinator vs. direct P2P (~50ms on LAN). Offset by better peer selection (reputation router has full signal vs. blind local discovery).
Peer registry & discovery
| Storage | Clustered registry, TTL-keyed (30min sliding). One key per active peer node. |
| Heartbeat | Every 15s via WebSocket (preferred) or SSE fallback. Includes: node_id, runtime, capability_vec, current_load (active_tasks), reputation_score, last_success_at. |
| Eviction | 3 missed heartbeats (45s) β degraded state (reduced traffic weight). 6 missed (90s) β evicted entirely. Re-entry on next successful heartbeat. |
| Query | Router calls `registry.query(model, req_class, latency_slo)` β sorted set by reputation score. Returns top N candidates. |
| Selection | Router picks 1 candidate via weighted random (reputation score * uptime_pct). No explicit load balancing β reputation scorer already penalizes overload. |
SDK-to-Coordinator session protocol
After the initial capability probe, the SDK establishes a persistent WebSocket to the Gateway for token streaming and real-time routing feedback. Failover to REST (request-by-request) occurs if WebSocket unavailable (legacy browsers, firewall blocks).
After the initial capability probe, the SDK establishes a persistent WebSocket to the Gateway for token streaming and real-time routing feedback, with automatic failover to REST (request-by-request) if WebSocket is unavailable. The connection multiplexes concurrent requests and uses keepalive pings to detect dropped connections. On route degradation (peer timeout, cloud error), the capability probe re-runs and a fresh routing decision is fetched. Session identity is derived from the API key and a stable device identifier cached locally. Per-peer session keys are derived from the TLS channel β no persistent key material is stored on the client.
- Keepalive: SDK sends ping; Coordinator pong. 3 missed pongs β reconnect.
- Probe re-run: On route degradation (peer timeout, cloud 5xx), capability probe re-runs and new router decision fetched.
- Session state: API key + device_id (derived from probe) = session identifier. Device_id stays stable across page reloads (IndexedDB cache).
- Encryption: Per-peer session keys derived from TLS exporter (RFC 5705). No persistent key storage on client.
V3 roadmap: optional direct peer dispatch
Post-GA, we will explore direct WebRTC dispatch as an opt-in mode for latency-critical workloads (interactive routing latency budget < 50ms).
| Scope | Browser-to-browser only. Desktop/iOS/Android nodes remain coordinator-routed for now (platform-specific NAT complexity). |
| Mode | User opts in via `latency_preference: "ultra-low"` at SDK init. Requires stable internet and foreground presence. |
| Flow | Coordinator still performs peer selection + reputation lookup. Client opens WebRTC data channel to peer via coordinator-provided ICE candidates. |
| Safety | Task still signed by Coordinator before dispatch. Peer cannot forge results (signature verification on return via coordinator). |
| Fallback | Any connection failure β automatic failover to coordinator-routed or cloud. Zero impact on reliability. |
| Verification | Continues as-is: a sample of tasks still decrypted and verified by coordinator post-hoc. |
Data flow (happy path β local)
App SDK Gateway Router LocalWorker
β β β β β
βββchat()βββββΊβ β β β
β ββprobeββββββββββΊβ β β
β β βββclassifyββββΊβ β
β ββββdecision:localβββββββββββββββ β
β ββexecute(task, model_bundle)ββββββββββββββββββ βΊβ
β β β β βββinferβββ
β β β β βββββββββββ
β ββββββββββββββββββββββββββββββββββββββββββββββtokensβββββββββ
ββstreamββββββββmeter_eventββββΊβ β β
β β β (no cloud call, savings_usd logged) β