Metering model
Every meter, where it is emitted, how events flow to a bill, and the correctness guarantees.
Metering is the part of the platform that must never be wrong. A dropped event is unbilled work; a duplicated event is a customer overcharged. The pipeline is built around both failure modes.
The pipeline
Meters
Time
| Meter | Unit | Emitted by | Note |
|---|---|---|---|
tier0 — none | — | — | Tier 0 has no session, so no time meter. The point of the tier |
tier1.session_s | seconds | wbnode | Wall clock in running |
tier2.session_s | seconds | wbnode | |
tier3.session_s | seconds | wbnode | |
tier4.session_s | seconds | wbnode | The dominant tier 4 line |
Time accrues only in running. provisioning is free — a cold start is the platform's failure to
have a warm instance, and billing for it would pay the platform for being slow.
Compute
| Meter | Unit | Emitted by | Note |
|---|---|---|---|
cpu_ms | milliseconds | wbnode | Actual CPU consumed, from cgroup accounting |
mem_gib_ms | GiB-milliseconds | wbnode | Allocated, integrated over time |
disk_gib_ms | GiB-milliseconds | wbnode | Scratch, integrated |
gpu_s | seconds | wbnode | Attached GPU, whole-device |
vgpu_s | seconds | wbnode | Mediated vGPU slice, tier 4 |
CPU is actual and memory is allocated, deliberately. CPU can be shared, so charging for use is fair. Memory cannot be meaningfully overcommitted, so the allocation is what is consumed from the fleet whether or not the guest touches it.
Network
| Meter | Unit | Emitted by | Note |
|---|---|---|---|
egress_gb | GiB | egress-proxy | Measured at the proxy; the guest cannot under-report |
ingress_gb | GiB | edge-router, proxy | Lower rate |
intra_region_gb | GiB | wbnode | Free below a threshold |
private_link_gb | GiB | Private link gateway | Separate rate |
Storage
| Meter | Unit | Emitted by | Note |
|---|---|---|---|
volume_gib_month | GiB-month | storage | Provisioned size, attached or not |
snapshot_gib_month | GiB-month | storage | Deduplicated, compressed size |
recording_gib_month | GiB-month | storage | Browser and desktop recordings |
paused_gib_month | GiB-month | wbnode | Memory and disk held by a paused session |
snapshot_transfer_gb | GiB | storage | Cross-region copies |
Storage meters are emitted hourly and prorated, so a volume that exists for six hours bills six hours of a GiB-month, not a whole month.
Activity
These two meters are tied to the managed tool/MCP catalog described in Tools and MCP — a capability this platform has explicitly decided not to provide (ADR-012 in the decision log). Kept here as the record of the earlier, broader framing; no code emits either meter today.
| Meter | Unit | Emitted by | Note |
|---|---|---|---|
tool_call | count | control | Per invocation, success or failure |
tool_call_ms | milliseconds | control | Handler wall time |
browser_page_s | seconds | wbnode | Summed across open pages |
desktop_s | seconds | wbnode | Desktop session active |
api_requests | count | api-gateway | Free below a generous threshold |
browser_page_s is the meter teams overlook. Ten open tabs for sixty seconds is six hundred
page-seconds, not sixty.
Reserved capacity
| Meter | Unit | Note |
|---|---|---|
pool_reservation_s | seconds | Reserved warm-pool depth, billed whether claimed or not |
Only appears for organizations that bought a latency guarantee. The capacity is genuinely held for them, so it is genuinely billed.
Event shape
{
"session_id": "sess_2kQ9xwR4",
"meter": "tier2.session_s",
"window_start": "2026-10-06T09:21:00Z",
"window_s": 60,
"quantity": 60,
"organization_id": "org_2kQ9xw",
"project_id": "proj_7fTn21",
"end_user_id": "eu_customer_4821",
"region": "bom1",
"tier": 2,
"source": "node",
"node_id": "node-bom1b-0412"
}
Dedupe key: (session_id, meter, window_start). A retried event with the same key is discarded at
ingest, which is what makes at-least-once delivery safe.
Correctness
No lost usage
Nodes write every sample to a local write-ahead log before acknowledging it internally, stream from
the WAL, and truncate only on acknowledgement. A node that cannot reach metering keeps running
sessions and keeps buffering for up to 24 hours. Past that it raises a critical alert and refuses
new placements, because beyond that point the platform is doing unbilled work.
No double billing
The dedupe key makes redelivery a no-op. Redeliveries are expected and counted — events_deduped is
a normal non-zero metric, and a spike in it means node retries, which is a transport problem rather
than a billing one.
Nightly reconciliation
A job replays a sample of completed sessions from archived node WALs and compares the result against billed usage. Any mismatch is a billing incident, handled at the same severity as an outage. This is the check that catches a class of bug no amount of unit testing does: a pipeline that is internally consistent and wrong.
Reproducible rating
Rated usage stores price_list_id and price_list_version. Re-running the rating for a past period
produces the same numbers, which is what makes an invoice explainable a year later.
Finer granularity multiplies event volume without changing any bill — nothing is priced below a second, and a session that lives 90 seconds bills 90 seconds regardless of bucket size. Coarser granularity would make spend caps and live usage lag unacceptably. One minute is the point where both constraints are satisfied.
Freshness
| Stage | Typical | p99 |
|---|---|---|
| Node emission to ingest | 10 s | 45 s |
| Ingest to minute bucket | 15 s | 60 s |
| Bucket to rated usage | 20 s | 50 s |
| Rated to usage API | 5 s | 15 s |
| End to end | ~50 s | < 3 m |
usage_to_date on a live session object bypasses the pipeline and comes from the node's last flush,
so it is fresher — under 15 seconds — but it is an estimate. The billed figure is whatever the
pipeline settles on.
Attribution
Every event carries organization_id, project_id and, when set, end_user_id. All three are
captured at emission.
end_user_id cannot be backfilled. An event emitted without it is permanently unattributed.
This is not a limitation that could be engineered away — the association exists only in the caller's
head at the moment of the request. Set it on every create from day one. See end-user
billing.
What is not metered
- Provisioning and teardown time. Platform cost, not customer cost.
- Failed starts. A session that never reaches
runningbills nothing. - Control-plane API requests, below a generous threshold.
- Pool capacity, unless the customer bought a reservation.
- Template builds and storage, below a per-project threshold.
- Webhook deliveries, logs, traces and the audit log, within retention.
The principle: customers pay for compute they used and storage they hold. Everything the platform does to make that fast and safe is the platform's cost of doing business.