Sign in

← All schemas

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.

Canonical $id, draft
https://schemas.inorbit.hr/runner/heartbeat/v0.json
01Versions

Each version is a file at its own address. The raw file is plain JSON, served without a sign-in.

02Fields

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_idstringrequired

    The runner's id from RegisterRunner: rnr_ and a ULID.

    pattern ^rnr_[0-9A-HJKMNP-TV-Z]{10,26}$

  • atstringrequired

    When 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 characters

  • versionstringoptional

    The 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 characters

  • capacityintegerrequired

    The turns it runs at once, 1 to 32 (RunnerHeartbeatRequest.capacity). Lowering it never stops a running turn.

    1 to 32

  • kindslist of enumrequired

    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.

    One of"coordinate""build""none"

    at most 3 itemsno repeats

  • providerslist of anyoptional

    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.

    at most 2 itemsno repeats

  • slotsanyoptional
  • build_diskanyoptional
  • countersanyoptional
  • turn_outcomesanyoptional
03Examples

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.

04Check a file against it

Replace the file name with yours.

VS Code, JetBrains: settings
"json.schemas": [
  { "fileMatch": ["my-file.json"], "url": "https://schemas.inorbit.hr/runner/heartbeat/v0.json" }
]
check-jsonschema
check-jsonschema --schemafile https://schemas.inorbit.hr/runner/heartbeat/v0.json my-file.json
ajv-cli
ajv validate --spec=draft2020 -s <(curl -s https://schemas.inorbit.hr/runner/heartbeat/v0.json) -d my-file.json
curl
curl -s https://schemas.inorbit.hr/runner/heartbeat/v0.json
05The schema itself

The raw file of v0, as published.

schemas.inorbit.hr/runner/heartbeat/v0.json
{
  "$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
        }
      }
    }
  }
}