Skip to content

Telemetry

OpenCloud provides a stable aggregate for reading health and a bounded custom metric API for app-specific product signals. Prometheus, Loki, and Grafana are platform implementation details.

telemetry.summary()

Returns a safe, host-bound aggregate. It contains no raw logs, request paths, internal labels, or datasource credentials.

js
const summary = await opencloud.telemetry.summary();
const rest = summary.activity.surfaces.rest;

renderCount(rest.requests24h);
renderFreshness(summary.activity.telemetry.latestIngestedAt);

Exact response shape

json
{
  "appId": "6f9619ff-8b86-4e6e-a62a-889950f42d3e",
  "asOf": "2026-07-28T12:00:00.000Z",
  "usage": {
    "windowStart": "2026-07-27T00:00:00.000Z",
    "windowEnd": "2026-07-28T00:00:00.000Z",
    "calculationVersion": "v1",
    "completeness": "partial",
    "metrics": {
      "databaseStorageBytes": 4096,
      "fileStorageBytes": 1024,
      "users": 2
    },
    "createdAt": "2026-07-28T00:05:00.000Z"
  },
  "activity": {
    "window": {
      "from": "2026-07-27T12:00:00.000Z",
      "to": "2026-07-28T12:00:00.000Z",
      "seconds": 86400
    },
    "telemetry": {
      "status": "available",
      "latestIngestedAt": "2026-07-28T11:59:58.000Z",
      "ingestionLagSeconds": 0.32,
      "sampledEntries": 241,
      "truncated": false
    },
    "surfaces": {
      "page": {
        "lastActivityAt": "2026-07-28T11:59:55.000Z",
        "requests24h": 80,
        "errors24h": 0,
        "lastStatus": 200
      },
      "rest": {
        "lastActivityAt": "2026-07-28T11:59:50.000Z",
        "requests24h": 120,
        "errors24h": 1,
        "lastStatus": 200
      },
      "storage": {
        "lastActivityAt": null,
        "requests24h": 0,
        "errors24h": 0,
        "lastStatus": null
      },
      "realtime": {
        "lastActivityAt": null,
        "requests24h": 0,
        "errors24h": 0,
        "lastStatus": null
      },
      "function": {
        "lastActivityAt": null,
        "requests24h": 0,
        "errors24h": 0,
        "lastStatus": null
      },
      "cron": {
        "lastActivityAt": "2026-07-28T11:55:00.000Z",
        "requests24h": 24,
        "errors24h": 0,
        "lastStatus": 200
      }
    }
  }
}

All six surface keys are always present. usage can be null.

Freshness rules

  • telemetry.status: "unavailable" means activity could not be read.
  • latestIngestedAt: null means no sampled ingestion evidence exists.
  • truncated: true means the 5,000-entry sample limit was reached.
  • A zero request count is meaningful only when telemetry is available and the sample is not truncated.
  • Missing activity is unknown or quiet, never “healthy.”

Custom counters and gauges

Declare each custom metric in opencloud.yaml before emitting it:

yaml
observability:
  metrics:
    - name: tasks_created
      type: counter
      unit: tasks
      dimensions:
        assignee_type:
          values: [parent, child]
    - name: overdue_tasks
      type: gauge
      unit: tasks
  alertRules:
    - id: overdue-tasks
      name: Overdue tasks detected
      metric: overdue_tasks
      aggregation: latest
      operator: gte
      threshold: 1
      window: 5m
      minimumSamples: 1
      severity: warning
      enabled: true

Emit values through the deployment-pinned SDK:

js
await opencloud.telemetry.increment("tasks_created", 1, {
  dimensions: { assignee_type: "child" },
});
await opencloud.telemetry.gauge("overdue_tasks", 7);

The SDK creates a private idempotency key and retries one transient write, so application code must not invent keys or repeat an ambiguous measurement. Browser values can be manipulated by the visitor, so treat them as product signals rather than security or billing evidence.

An app can ingest at most 1,200 measurements per minute and retain at most 100,000 points. Points older than 14 days are removed during ingestion. A request that exceeds an ingestion or retained-point limit returns HTTP 429.

The first release supports counters, gauges, fixed windows, bounded enum dimensions, and simple threshold rules. Histograms, calculated metrics, arbitrary queries, and external notification channels are not part of this contract. A current rule breach can wake the app's Agent for verified repair.

Agent Feed

Agents read one stable, app-scoped summary instead of depending on Prometheus, Loki, or Grafana response formats:

http
GET /v1/apps/{appId}/agent-feed?since=2026-07-29T10:00:00.000Z
Authorization: Bearer <app-scoped credential>

The response has contractVersion: "1" and contains:

  • Current app and active-deployment state.
  • Telemetry freshness, bounded built-in signals, and each declared custom metric (sum for counters and latest for gauges over 15 minutes).
  • Current custom-rule evaluations that need attention (firing, unknown, or invalid). A rule with enough samples outside its threshold is currently ok and is not included in alerts.
  • Up to 100 recent threshold-breach intervals in recentBreaches, including breaches that ended before the feed was read. endedAt: null means the breach is current, and startedBeforeSince means its reported start was clipped to the requested history boundary.
  • breachesTruncated, which is true when more derived breach intervals matched than fit.
  • Up to 100 recent deployment-operation and cron events.
  • eventsTruncated, which is true when more recent events matched than fit.
  • nextSince, which can be passed to the next poll.

Built-in alerts cover failed app/deployment state, recent operation or cron failure, high HTTP error rate, and unavailable or stale runtime telemetry. Missing activity is not reported as healthy. Metrics originating in a browser remain explicitly marked with source: "browser"; authenticated-browser, mixed, and no-sample provenance are also distinguished.

Alert rules

App-owned alert rules should be declared under observability.alertRules in the same manifest as their metrics. A manifest can contain at most 20 rules. Validation rejects unknown metric references and aggregations that do not match the metric type. Rules become active in the same atomic switch as their deployment, before the new release receives traffic.

Operationally managed rules remain app-scoped, are limited to 20 per app, and can reference only a metric declared by the active deployment:

http
PUT /v1/apps/{appId}/alert-rules/too-many-overdue
Content-Type: application/json

{
  "name": "Too many overdue tasks",
  "metric": "overdue_tasks",
  "aggregation": "latest",
  "operator": "gt",
  "threshold": 10,
  "window": "15m",
  "minimumSamples": 1,
  "severity": "warning",
  "enabled": true
}

Every returned rule has origin: "manifest" | "operational_override". A manifest rule is part of the active immutable release. PUT creates an immediate operational rule; when it uses the same ID as a manifest rule, the operational definition takes precedence until DELETE removes it. Put durable product monitoring into the next manifest revision rather than depending on a permanent override.

Use GET /v1/apps/{appId}/alert-rules to list rules, their current stateless evaluation, and the latest Alert Fire delivery metadata. Use DELETE /v1/apps/{appId}/alert-rules/{ruleId} to remove one. Counters support sum and per-second rate; gauges support latest, min, max, and avg. Operators are gt, gte, lt, lte, and eq. Windows are 5m, 15m, 1h, and 24h.

The Agent Feed evaluates rules from retained metric points when it is read. It derives both the current state (ok, firing, unknown, or invalid) and recent threshold-breach intervals at read time. Reading the feed does not write alert state or mark an alert resolved. Missing samples produce unknown, not ok. Historical intervals describe when the retained measurements crossed a rule threshold; delivery is reported separately from that evaluation.

Each witnessed threshold transition creates one stable, deduplicated Alert Fire. OpenCloud persists its internal delivery record before contacting the app's Agent. Failed and ambiguous deliveries remain retryable after metric samples leave the rule window, and replay uses the same fire identity so the Agent does not create another message, incident, or run. A successful intake links the transport record to the Agent incident; the incident owns the independent unresolved, in-progress, and resolved lifecycle. Rule definitions themselves still have no lifecycle state.

An unknown evaluation means the current window cannot be evaluated, commonly because it contains no samples. It never means ok. A rule can therefore read unknown while an earlier Alert Fire remains pending, retrying, in progress, or resolved.

Keep unknown as the machine-readable evaluation result, but present its specific reason to people: No data in window for zero samples and Not enough data when the minimum sample count has not been reached. This is a current evaluation, not lifecycle state on the rule.

A control-plane worker evaluates enabled rules once per minute. Each current breach produces an alert fire in the app's existing Agent conversation. Rule definitions themselves have no persisted ok or firing lifecycle. The durable fire has the user-facing lifecycle unresolved, in_progress, and resolved.

The fire identity includes the active deployment, rule, and breach occurrence. Repeated evaluations of the same occurrence are exact duplicates and do not wake another Agent run. A later threshold crossing creates a new fire. The fire becomes in_progress when its queued Agent run starts and becomes resolved only after the exact run's new active deployment passes platform verification. A failed or cancelled repair remains unresolved. Because the rule is an owner-authored instruction to the app Agent, its fire does not wait for a separate unattended-repair approval toggle.

Custom measurements and manually managed rules are stored app-scoped in the control database with a maximum 14-day measurement retention. Deployment-owned rules remain in the immutable active manifest. Derived alert-rule state is not stored; alert-fire lifecycle is stored by the Agent service. Prometheus, Loki, and Grafana remain internal diagnostics rather than application contracts.

Self-hosted infrastructure for agent-built applications.