POST /v1/heartbeat

Integration liveness over the authenticated channel. Uninstalling or silencing an integration is the cheapest bypass there is, and without a beat the runtime cannot distinguish "agent idle" (fine) from "integration went dark" (a coverage loss). The heartbeat keeps those two facts apart.

A heartbeat is transport-level: it is not a GuardEvent and carries no guarded action. This is also where the integration build id's liveness copy lives — fleet coverage reads it from here. (The event carries its own optional integration copy for per-event triage: the heartbeat goes quiet exactly when a bad rollout is what you are naming.)

POST {base_url}/v1/heartbeat
Authorization: Bearer ogr_<key>
Content-Type: application/json

Request

At least one of integration / agent_id must be present.

{
  "integration": "ogr-higress/3.14.0",
  "instance_id": "gw-7c4f1a",
  "agent_id": "invoice-bot",
  "interval_s": 30,
  "counters": {"events_sent": 120, "evaluate_errors": 0, "unresolved_spans": 0},
  "ruleset": "rs_8f21c0"
}
FieldTypeRequiredDescription
integrationstringone-ofThe integration identifying itself — name and build, e.g. my-harness/1.2.0
agent_idstringone-ofThe agent whose liveness rides this beat
instance_idstringoptionalWHICH PROCESS is beating — one value per running replica, stable for that process's life
interval_snumberoptionalDeclared cadence; lets the runtime compute "missed beats"
countersobjectoptionalFree-form counters — events_sent, evaluate_errors, unresolved_spans, …
rulesetstringoptionalThe id of the secret ruleset this integration currently holds, when it does local redaction

⚠️ Send instance_id if you run more than one replica. A runtime records the build and the counters per (integration, instance_id). Without it, every replica of one integration collapses into a single row: the version shown is whichever beat last, and a lab instance on a newer build can name itself while the replicas actually serving traffic are invisible. The counters have the same problem — they are properties of a PROCESS, not of a name.

evaluate_errors is how the runtime learns an integration entered degraded mode: events observed while the runtime was unreachable are lost observations (the protocol has no replay channel), and the counters are what make the gap visible instead of silent. unresolved_spans counts modification spans the integration could not apply — "no spans resolved" is otherwise indistinguishable from "no redaction policy".

Response — 200

{
  "ok": true,
  "limits": { "max_request_bytes": 67108864,
              "media": { "image": 8388608, "audio": 8388608, "document": 16777216,
                         "file": 8388608, "video": 0 },
              "media_parts_max": 16 },
  "rules": { "id": "rs_8f21c0" }
}

The two echoes let a long-running integration learn about a change within one interval, without polling:

  • limits is the same object GET /v1/limits serves — what this tenant may send. An integration that elides oversized media reads its budget from here.
  • rules.id is the id of the tenant's current secret ruleset. When it differs from the one the integration holds, refetch GET /v1/rules.

Both are advisory: an integration that ignores them behaves exactly as before.

A heartbeat registers a live-but-idle agent: fleet coverage reflects integrations that have not yet emitted a single event. Deploy the integration, start the beat, and the runtime knows the surface is covered before the first guarded action arrives.

Example

curl -s $OGR_RUNTIME/v1/heartbeat \
  -H "Authorization: Bearer $OGR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"integration": "my-harness/1.0.0", "agent_id": "invoice-bot", "interval_s": 30}'

Operational semantics

  • A runtime alerts when an integration misses beats beyond a tolerance, and treats the gap as a coverage loss — never as "no risk".
  • counters reconciled against delivered events is what makes selective event suppression detectable: an integration reporting N sent while N−k arrived is a finding, not noise.