Schema · runner/heartbeat
Runner heartbeat
What a coordinator runner (iohr runner, RFC 0120) knows about itself at one beat: who it is, what it declares, how busy it is, how its loop and its reports are doing, and what its turns ended in. A snapshot taken every beat (the service asks for one every 20 seconds), the source of the readings a runner publishes into SensorsService: each number becomes one gauge or counter, runner.<field>, and each state a state sensor, never a document. It carries no turn text, no prompt, no tool target, no path and no credential: the object is closed, and a provider is its name, its sign-in method and whether it is signed in. runner_id, version, capacity, kinds and providers are what RunnerHeartbeat sends the coordinator today (at is the snapshot's own time); slots, build_disk and counters are known in the runner process and sent nowhere yet, and turn_outcomes is not counted yet (gaps listed in the pull request that added this schema). The service's own view (status online, offline, draining or revoked; slots used; leases) is derived from the beats and the leases and is not part of what a runner says. Draft line: it may still be corrected.
https://schemas.inorbit.hr/runner/heartbeat/v0.jsonEach version is a file at its own address. The raw file is plain JSON, served without a sign-in.
- v0draftMay still be corrected; kept for five minutes.schemas.inorbit.hr/runner/heartbeat/v0.json ↗
Fields of v0, the newest version, by path. A name ending in [] is each item of a list. Every field not listed is refused.
runner_idstringrequiredThe runner's id from RegisterRunner: rnr_ and a ULID.
pattern ^rnr_[0-9A-HJKMNP-TV-Z]{10,26}$atstringrequiredWhen the runner took the snapshot, RFC 3339, the runner's clock.
pattern ^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\.[0-9]{1,9})?(Z|[+-]([01][0-9]|2[0-3]):[0-5][0-9])$at most 40 charactersversionstringoptionalThe runner's build, for example 0.1.0-alpha.22 (RunnerHeartbeatRequest.version).
pattern ^[0-9A-Za-z][0-9A-Za-z.+-]*$at least 1 charactersat most 64 characterscapacityintegerrequiredThe turns it runs at once, 1 to 32 (RunnerHeartbeatRequest.capacity). Lowering it never stops a running turn.
1 to 32kindslist of enumrequiredThe turn classes it takes now. none is declared when the build volume is under its floor and build was the only class: the runner offers itself nothing new.
One of
"coordinate""build""none"at most 3 itemsno repeatsproviderslist of anyoptionalThe AI providers it can run, from the binaries' own status (RunnerHeartbeatRequest.providers): a name and whether it is signed in, never a credential. Subscription sign-in only.
at most 2 itemsno repeatsslotsanyoptionalbuild_diskanyoptionalcountersanyoptionalturn_outcomesanyoptional
Valid documents with made-up values, as the repository's tests check them.
{
"runner_id": "rnr_01M5GBYYK0AB3CD4EF5GH6JK7M",
"at": "2026-10-23T09:17:30Z",
"capacity": 2,
"kinds": [
"none"
],
"providers": [
{
"provider": "claude",
"signed_in": false
}
],
"build_disk": {
"free_bytes": 0,
"floor_bytes": 100000000000
}
}The repository's tests also check 17 documents that this schema must refuse.
Replace the file name with yours.
"json.schemas": [
{ "fileMatch": ["my-file.json"], "url": "https://schemas.inorbit.hr/runner/heartbeat/v0.json" }
]check-jsonschema --schemafile https://schemas.inorbit.hr/runner/heartbeat/v0.json my-file.jsonajv validate --spec=draft2020 -s <(curl -s https://schemas.inorbit.hr/runner/heartbeat/v0.json) -d my-file.jsoncurl -s https://schemas.inorbit.hr/runner/heartbeat/v0.jsonThe raw file of v0, as published.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.inorbit.hr/runner/heartbeat/v0.json",
"title": "Runner heartbeat",
"description": "What a coordinator runner (iohr runner, RFC 0120) knows about itself at one beat: who it is, what it declares, how busy it is, how its loop and its reports are doing, and what its turns ended in. A snapshot taken every beat (the service asks for one every 20 seconds), the source of the readings a runner publishes into SensorsService: each number becomes one gauge or counter, runner.<field>, and each state a state sensor, never a document. It carries no turn text, no prompt, no tool target, no path and no credential: the object is closed, and a provider is its name, its sign-in method and whether it is signed in. runner_id, version, capacity, kinds and providers are what RunnerHeartbeat sends the coordinator today (at is the snapshot's own time); slots, build_disk and counters are known in the runner process and sent nowhere yet, and turn_outcomes is not counted yet (gaps listed in the pull request that added this schema). The service's own view (status online, offline, draining or revoked; slots used; leases) is derived from the beats and the leases and is not part of what a runner says. Draft line: it may still be corrected.",
"type": "object",
"additionalProperties": false,
"required": [
"runner_id",
"at",
"capacity",
"kinds"
],
"properties": {
"runner_id": {
"description": "The runner's id from RegisterRunner: rnr_ and a ULID.",
"type": "string",
"pattern": "^rnr_[0-9A-HJKMNP-TV-Z]{10,26}$"
},
"at": {
"description": "When the runner took the snapshot, RFC 3339, the runner's clock.",
"type": "string",
"maxLength": 40,
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]{1,9})?(Z|[+-]([01][0-9]|2[0-3]):[0-5][0-9])$"
},
"version": {
"description": "The runner's build, for example 0.1.0-alpha.22 (RunnerHeartbeatRequest.version).",
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[0-9A-Za-z][0-9A-Za-z.+-]*$"
},
"capacity": {
"description": "The turns it runs at once, 1 to 32 (RunnerHeartbeatRequest.capacity). Lowering it never stops a running turn.",
"type": "integer",
"minimum": 1,
"maximum": 32
},
"kinds": {
"description": "The turn classes it takes now. none is declared when the build volume is under its floor and build was the only class: the runner offers itself nothing new.",
"type": "array",
"uniqueItems": true,
"maxItems": 3,
"items": {
"enum": [
"coordinate",
"build",
"none"
]
}
},
"providers": {
"description": "The AI providers it can run, from the binaries' own status (RunnerHeartbeatRequest.providers): a name and whether it is signed in, never a credential. Subscription sign-in only.",
"type": "array",
"maxItems": 2,
"uniqueItems": true,
"items": {
"$ref": "#/$defs/provider"
}
},
"slots": {
"$ref": "#/$defs/slots"
},
"build_disk": {
"$ref": "#/$defs/build_disk"
},
"counters": {
"$ref": "#/$defs/counters"
},
"turn_outcomes": {
"$ref": "#/$defs/turn_outcomes"
}
},
"$defs": {
"provider": {
"title": "Provider",
"description": "One provider (proto iohr.coordinator.v1.Provider).",
"type": "object",
"additionalProperties": false,
"required": [
"provider",
"signed_in"
],
"properties": {
"provider": {
"description": "The provider.",
"enum": [
"claude",
"codex"
]
},
"method": {
"description": "The sign-in method's name; a subscription sign-in, never a key.",
"enum": [
"subscription",
"setup_token"
]
},
"signed_in": {
"description": "Whether the provider's own status says it is signed in.",
"type": "boolean"
},
"plan_window_until": {
"description": "While a plan window holds the provider, when it ends, RFC 3339.",
"type": "string",
"maxLength": 40,
"pattern": "^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]{1,9})?(Z|[+-]([01][0-9]|2[0-3]):[0-5][0-9])$"
}
}
},
"slots": {
"title": "Slots and queues",
"description": "How busy the runner is, from its loop's own lists. in_flight is the turns running now, at most capacity; fetching the accepted leases whose pack is not fetched yet; outbox the finished turns whose report is waiting to be delivered (the loop keeps at most 8 and takes no new turn while it is full).",
"type": "object",
"additionalProperties": false,
"required": [
"in_flight",
"outbox"
],
"properties": {
"in_flight": {
"type": "integer",
"minimum": 0,
"maximum": 32
},
"fetching": {
"type": "integer",
"minimum": 0,
"maximum": 32
},
"outbox": {
"type": "integer",
"minimum": 0,
"maximum": 8
}
}
},
"build_disk": {
"title": "Build disk",
"description": "The build volume's free space against the floor under which the runner stops declaring build (the sdk's default floor is 100 GB). Bytes only, never a path.",
"type": "object",
"additionalProperties": false,
"required": [
"free_bytes",
"floor_bytes"
],
"properties": {
"free_bytes": {
"description": "Free bytes on the build volume.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"floor_bytes": {
"description": "The floor in bytes.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
}
},
"counters": {
"title": "Loop counters",
"description": "Since the process started, as the loop counts them.",
"type": "object",
"additionalProperties": false,
"required": [
"beats"
],
"properties": {
"beats": {
"description": "Heartbeats the service answered.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"heartbeat_failures": {
"description": "Heartbeats that failed, in all.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"heartbeat_failures_in_a_row": {
"description": "Heartbeat failures since the last answer; the loop backs off while it is above 0.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"leases_accepted": {
"description": "Leases the runner accepted.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"turns_run": {
"description": "Turns it ran to an end, including ones stopped by a shutdown.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"reports_delivered": {
"description": "Turn reports the service recorded.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"reports_rejected": {
"description": "Reports the service refused for good and the runner dropped.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
}
},
"turn_outcomes": {
"title": "Turn outcomes",
"description": "Turns by what ended them, since the process started: the outcome words of ReportTurn. Each count may be absent when it is 0.",
"type": "object",
"additionalProperties": false,
"properties": {
"replied": {
"description": "The provider answered in the shape asked.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"unshaped": {
"description": "It answered, not in the shape asked.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"cap_hit": {
"description": "The turn cap was reached.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"limit_hit": {
"description": "The plan window's limit was reached.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"login_expired": {
"description": "The provider was no longer signed in.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"refused_tool": {
"description": "The provider asked for a tool it was refused.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"failed": {
"description": "The turn failed, or the runner was stopped before it finished.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"refused_provider": {
"description": "The runner refused the provider before starting.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"refused_user": {
"description": "The runner refused the person's turn before starting.",
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
}
}
}
}