Skip to main content

Telemetry Schema

This is the authoritative wire contract between the Lowkey installer (install.sh, with the telemetry client inlined) and any telemetry backend that ingests its events. It is versioned, stable, and backwards-compatible within a major version.
Telemetry is opt-out by default via LOWKEY_TELEMETRY=0 or DO_NOT_TRACK=1. Test installs (--test mode) are flagged with is_test: true so backends can exclude them from product metrics. See Telemetry Privacy for a plain-English version.

Transport

AspectValue
Endpoint (default)https://telemetry.loki.run (override: LOWKEY_TELEMETRY_URL)
ProtocolHTTPS POST, Content-Type: application/json
Timeout--connect-timeout 2s --max-time 2s
DeliveryFire-and-forget background curl, detached from parent shell
RetryNone — all sends are best-effort
AuthNone required. Backend may add IP rate-limiting or a CF front door.
Max body256 KB
Max events per ingest batch500

Envelopes

The installer sends two envelope types on three paths:
PathMethodEnvelope schemaWhen
/v1/installPOSTlowkey.install.v1At install start (outcome: started), on completion (completed), on failure (failed)
/v1/ingestPOSTlowkey.telemetry.v1Batched at install end — contains all queued fine-grained events
/v1/configGET(response only)Installer fetches remote kill-switch + sampling config

lowkey.install.v1 — Install Beacon

Sent to POST /v1/install. One envelope per outcome transition (started, completed, failed). This is the primary record for install funnel analytics.

Example

{
  "schema": "lowkey.install.v1",
  "sent_at": "2026-04-27T12:30:14Z",
  "install_id": "09ff13c6-0d4e-4e18-9d93-011e0afd9a6e",
  "machine_id": "sha256:83d4cc5a36917da57120c27553c78ea83607b9b32ca23af148fc90cdca132a1f",
  "agent": {
    "version": "0.5.103",
    "channel": "stable",
    "os": "linux",
    "arch": "arm64",
    "os_version": "6.1"
  },
  "install_method": "cfn",
  "outcome": "completed",
  "duration_ms": 412380,
  "is_test": false,
  "failure_step": null,
  "failure_class": null
}

Fields

FieldTypeRequiredDescription
schemastring (const)yesAlways lowkey.install.v1.
sent_atstring (ISO-8601 UTC)yesWall-clock time on client, e.g. 2026-04-27T12:30:14Z.
install_idstring (UUIDv4)yesUnique per install run. Regenerated on every `curlbash` invocation.
machine_idstringyessha256:<hex> of machine fingerprint, or fallback:<uuid> if hashing unavailable. Never the raw fingerprint.
agent.versionstringyesInstaller version (e.g. 0.5.103). unknown if unset.
agent.channelstringyesRelease channel. Currently always stable. Reserved: beta, nightly.
agent.osstring enumyeslinux, darwin, unknown. Lower-cased uname -s.
agent.archstring enumyesarm64, x86_64, unknown.
agent.os_versionstringyesKernel major.minor, e.g. 6.1 or 23.6.
install_methodstring enumyescfn, terraform, tf, console, unknown.
outcomestring enumyesstarted | completed | failed.
duration_msintegeryesms since install start. 0 on started.
is_testbooleanyestrue when installer ran with --test / TEST_MODE=true. Backends MUST exclude these from funnels.
failure_stepstring | nullconditionalnull unless outcome: failed. Short step identifier, e.g. aws_cli_check, cfn_deploy, bootstrap_timeout. Max 64 chars.
failure_classstring | nullconditionalnull unless outcome: failed. Machine-friendly class, e.g. exit_1, exit_130. Max 64 chars.

Outcome lifecycle

install starts
   ↓  POST /v1/install  { outcome: "started",   duration_ms: 0 }
   ↓  ...user picks pack/method, downloads CFN, etc...
   ↓  fine-grained events recorded to local queue

  ├─ success path ─→  POST /v1/install  { outcome: "completed", duration_ms: N }
  │                   POST /v1/ingest   { events: [...all queued...] }

  └─ failure path ─→  POST /v1/install  { outcome: "failed",
                                          duration_ms: N,
                                          failure_step: "cfn_deploy",
                                          failure_class: "exit_1" }
                      POST /v1/ingest   { events: [...all queued...] }

lowkey.telemetry.v1 — Event Batch

Sent to POST /v1/ingest. Exactly once per install run, at the end. Contains all fine-grained events that were queued locally during the run.

Example

{
  "schema": "lowkey.telemetry.v1",
  "sent_at": "2026-04-27T12:30:15Z",
  "agent": {
    "version": "0.5.103",
    "channel": "stable",
    "os": "linux",
    "arch": "arm64",
    "os_version": "6.1"
  },
  "machine_id": "sha256:83d4cc5a...",
  "install_id": "09ff13c6-0d4e-4e18-9d93-011e0afd9a6e",
  "session_id": "5e172374-29a2-442e-8f47-f15b0b8148ef",
  "is_test": false,
  "events": [
    { "t": "2026-04-27T12:23:35Z", "name": "install.started",         "props": { "method": "cfn" } },
    { "t": "2026-04-27T12:23:42Z", "name": "install.pack_selected",   "props": { "pack": "openclaw", "profile": "builder" } },
    { "t": "2026-04-27T12:23:58Z", "name": "install.method_selected", "props": { "method": "cfn", "region": "us-east-1" } },
    { "t": "2026-04-27T12:24:10Z", "name": "install.deploy_started",  "props": { "method": "cfn", "region": "us-east-1", "pack": "openclaw" } },
    { "t": "2026-04-27T12:29:50Z", "name": "install.deploy_completed","props": { "duration_ms": 340000, "method": "cfn" } },
    { "t": "2026-04-27T12:30:11Z", "name": "install.bootstrap_completed","props":{ "instance_id": "i-0abc..." } },
    { "t": "2026-04-27T12:30:14Z", "name": "install.completed",       "props": { "duration_ms": 412380, "pack": "openclaw", "method": "cfn", "region": "us-east-1" } }
  ]
}

Envelope fields

FieldTypeRequiredDescription
schemastring (const)yesAlways lowkey.telemetry.v1.
sent_atstring (ISO-8601 UTC)yesTime the flush happened.
agent.*objectyesSame shape as lowkey.install.v1.agent.
machine_idstringyesSame value as in install beacon for this run.
install_idstring (UUIDv4)yesSame value as in install beacon for this run.
session_idstring (UUIDv4)yesRegenerated every time install.sh is sourced — always equal to install_id today, reserved for future sub-sessions.
is_testbooleanyesInherited from install run.
eventsarray of Eventyes1–500 entries.

Event shape

FieldTypeRequiredDescription
tstring (ISO-8601 UTC)yesClient-side timestamp of the event.
namestring enumyesEvent name — see allowed events. Max 64 chars.
propsobjectyesFlat object, primitive values only. Max 20 keys. Keys ≤40 chars, values ≤500 chars. Nested objects get JSON-stringified by the backend.

Allowed event names

Backends MUST reject events whose name is not on this allowlist. This prevents runaway cardinality and keeps cost/dashboards predictable.

Install funnel (emitted by install.sh)

NamePropsMeaning
install.started{method}User launched installer.
install.pack_selected{pack, profile}User picked an agent pack.
install.method_selected{method, region}User picked deploy method (cfn/terraform/console) + region.
install.deploy_started{method, region, pack}CFN/TF apply kicked off.
install.deploy_completed{duration_ms, method}Stack reached CREATE_COMPLETE or equivalent.
install.bootstrap_completed{instance_id}EC2 instance finished userdata bootstrap.
install.completed{duration_ms, pack, method, region}Full install success.
install.failed{duration_ms, exit_code, step, pack, method}Installer exited non-zero.

Runtime (reserved — not yet emitted by installer)

The following names are pre-registered so the agent runtime and extensions can emit them without a schema bump: first_run, session.started, session.ended, heartbeat.daily, command.used, feature.used, model.invoked, error.reported, crash.reported, update.available, update.applied, update.skipped, auth.started, auth.completed, auth.failed, onboarding.completed. Event names are closed (allowlist). Event props are open — backends should tolerate unknown keys and drop values that exceed length caps.

/v1/config — Remote Kill-Switch

GET https://telemetry.loki.run/v1/config returns a JSON config that the installer may consult to throttle or disable itself. Missing/unreachable config is treated as “defaults”.

Response

{
  "enabled": true,
  "sample_rate": 1.0,
  "flush_interval_sec": 600,
  "dropped_events": [],
  "latest_version": "",
  "ttl_sec": 300
}
FieldTypeDefaultDescription
enabledbooleantrueMaster kill-switch. If false, installer MUST skip all sends.
sample_ratenumber1.00.0–1.0. Clients hash machine_id and only send if hash mod 10000 < sample_rate * 10000.
flush_interval_secinteger600Reserved for long-running agents. Installer flushes at exit only.
dropped_eventsarray<string>[]Event names to drop client-side before queuing.
latest_versionstring""Optional — latest known installer version. Clients may log a “you are out of date” hint.
ttl_secinteger300How long clients may cache this config.
Response headers: Cache-Control: public, max-age=60.

Data-retention & privacy contract

Backends implementing this schema MUST:
RuleRationale
Drop IP addresses before persisting.We only care about country (from CloudFront-Viewer-Country).
Never re-hash or de-anonymize machine_id.Already sha256:<hex>.
TTL raw events ≤ 90 days.High-volume, low-business-value.
TTL install beacons ≤ 365 days.Funnel metrics are still useful long-term.
Exclude is_test: true from all product dashboards.Dev/CI noise.
Reject Content-Length > 256 KB.Abuse / accidental logs.
Reject batches with > 500 events.Backpressure.
Return 204 No Content on success.Minimize wire overhead.
Never return body/stack traces on error.Don’t leak backend details to curl pipes.
Provide /v1/health returning 200.Ops monitoring.

Reference backend (DynamoDB)

A minimal compliant backend needs: Table lowkey-install-events (beacon persistence)
  • Partition key: id (string, UUIDv4 assigned by backend)
  • Sort key: timestamp (string, ISO-8601)
  • Attributes: all fields from lowkey.install.v1 + country, source: "v1_install"
  • TTL attribute: ttl (epoch seconds, +365d)
Table lowkey-telemetry-events (per-event persistence)
  • Partition key: id (string, UUIDv4)
  • Sort key: timestamp (string, ISO-8601)
  • GSI by-name on (name, timestamp) — for per-event-name dashboards
  • GSI by-machine on (machine_id, timestamp) — for per-machine queries
  • TTL attribute: ttl (epoch seconds, +90d)

Backward-compatibility rules

  • schema version is in the envelope; any breaking change bumps the suffix (v1v2).
  • Within v1, backends MAY add new optional fields; clients MUST ignore unknown fields.
  • Adding a new event name requires updating the allowlist in both the installer and backend.
  • Removing a field within v1 is forbidden.
  • Renaming a field is forbidden (emit both for one major version).

Versioning

Schema versionIntroducedStatus
lowkey.install.v1lowkey@0.5.103Current
lowkey.telemetry.v1lowkey@0.5.103Current