The heartbeat subsystem tracks liveness of both the agent itself and services running on the same host.
Two heartbeat paths exist:
| Path | Transport | Purpose |
|---|---|---|
| Self-heartbeat | MQTT → Atom | Agent publishes its own status periodically |
| Service heartbeat | FluxMQ (AMQP) → local | Co-located services register liveness via the local message bus |
The agent also accepts an MQTT ping command that publishes an immediate heartbeat without waiting for the next interval.
On a graceful reset, the agent publishes a goodbye heartbeat with heartbeat: false before disconnecting, so downstream consumers can detect the agent going offline without waiting for a timeout.
The agent publishes a SenML heartbeat to the telemetry channel on startup and at every interval:
Topic: m/<tenant-id>/c/<telemetry-channel-id>/gateway/heartbeat
Payload schema:
[
{ "bn": "agent:", "n": "service_type", "vs": "agent" },
{ "n": "heartbeat", "vb": true },
{ "n": "fw_version", "vs": "0.0.0" },
{ "n": "uptime", "u": "s", "v": 123.4 },
{ "n": "heap_free", "u": "By", "v": 1048576 },
{ "n": "devices", "u": "count", "v": 3 },
{ "n": "connected", "vb": true }
]| Field | Type | Unit | Description |
|---|---|---|---|
service_type |
string | — | Always "agent" |
heartbeat |
bool | — | Always true |
fw_version |
string | — | Agent binary version (set via -ldflags) |
uptime |
float | s |
Seconds since agent started |
heap_free |
float | By |
Go runtime free heap bytes |
devices |
float | count |
Number of registered downstream devices |
connected |
bool | — | MQTT connection state |
Co-located services publish a heartbeat message to the local FluxMQ broker. The agent subscribes to m/<tenant-id>/c/<commands-channel-id>/services/# and extracts the service name and type from the topic path.
Topic format: heartbeat.<service-name>.<service-type>
When a heartbeat is received, the agent:
- Creates a tracker entry if the service is new
- Resets the service's
last_seentimestamp - Marks the service as
online
If no heartbeat arrives within the configured interval, the service is marked offline.
Service info schema:
{
"name": "myservice",
"last_seen": "2026-06-10T12:00:00Z",
"status": "online",
"type": "sensor",
"terminal": 0
}| Variable | Default | Description |
|---|---|---|
MG_AGENT_HEARTBEAT_INTERVAL |
10s |
Period between self-heartbeat publishes and the timeout for marking services offline |
MG_AGENT_BROKER_URL |
amqp://guest:guest@localhost:5682/ |
FluxMQ (AMQP) broker URL for local service heartbeats |
The heartbeat interval can be changed at runtime via the config subsystem:
config set heartbeat_interval <duration>
See control.md for the full config set recipe.
| Direction | Topic | QoS | Description |
|---|---|---|---|
| Agent → Cloud | m/<tenant-id>/c/<data-chan>/gateway/heartbeat |
Configurable | Periodic self-heartbeat |
| Service → Agent | heartbeat.<name>.<type> (via AMQP) |
— | Local service registration |
| Cloud → Agent | m/<tenant-id>/c/<ctrl-chan>/req |
1 | ping command (on-demand heartbeat) |
mosquitto_sub \
-h <mqtt-host> -p 1883 \
-u <gateway-id> -P <gateway-secret> \
-t "m/<tenant-id>/c/<telemetry-channel-id>/gateway/heartbeat" \
-vExpected output (repeats every interval):
m/<tenant-id>/c/<telemetry-channel-id>/gateway/heartbeat [{"bn":"agent:","n":"service_type","vs":"agent"},{"n":"heartbeat","vb":true},{"n":"fw_version","vs":"unknown"},{"n":"uptime","u":"s","v":4580.244433353},{"n":"heap_free","u":"By","v":417792},{"n":"devices","u":"count","v":0},{"n":"connected","vb":true}]
mosquitto_pub \
-h <mqtt-host> -p 1883 \
-u <gateway-id> -P <gateway-secret> --id "ping-$(date +%s)" \
-t "m/<tenant-id>/c/<commands-channel-id>/req" \
-m '[{"bn":"req-1:", "n":"ping", "vs":""}]'curl -s http://localhost:9999/services | jq .Expected output:
[
{
"name": "nodered",
"last_seen": "2026-06-11T13:53:18.692168817Z",
"status": "online",
"type": "nodered",
"terminal": 0
}
]go run ./examples/publish/main.go \
-s amqp://guest:guest@localhost:5682/ \
heartbeat.myservice.sensor ""mosquitto_pub \
-h <mqtt-host> -p 1883 \
-u <gateway-id> -P <gateway-secret> --id "cfg-$(date +%s)" \
-t "m/<tenant-id>/c/<commands-channel-id>/req" \
-m '[{"bn":"req-1:", "n":"config", "vs":"set,heartbeat_interval,30s"}]'The agent responds on the control response topic with "ok" and the next heartbeat uses the new interval.