# Feed liveness

> Tell a dead feed from a single unpriced pair with feedHealth and livenessStatus, and why subscriptionArmed is not a heartbeat.

A stale cell has several possible causes, and each needs a different response. `feedHealth()` separates them, and `livenessStatus(maxTickAgeMs)` reduces them to one value for a circuit breaker:

| Status | Meaning | Response |
| --- | --- | --- |
| `Unknown` | The feed hasn't ticked since this implementation was deployed | Treat as no signal |
| `Healthy` | The heartbeat is alive and data is landing | Individual cells may still be stale: check those separately |
| `GlobalStale` | Ticks fire and burn gas, but nothing lands: the agents or the platform are failing | Void everything |
| `ChainDead` | The heartbeat itself is old: the schedule has stopped | Void everything, and alert an operator |

Pass a small multiple of the cadence as `maxTickAgeMs`: 3 to 5 seconds at a one-second cadence.

**The key distinction:** `livenessStatus` reports *global* liveness only. A `Healthy` feed with one old cell means **that pair** is unpriced: its venues are down, it fell below the agent's minimum number of sources, or it was delisted. Void that pair; the rest of the feed is fine.

> [!WARNING]
> **Don't use `subscriptionArmed` as a heartbeat.** Most ways the schedule can stop are silent. The precompile unsubscribes itself when the base fee rises above the schedule's `maxFeePerGas`. A scheduled call can be evicted from a full Reactivity queue. An out-of-gas in the callback can leave too little gas to schedule the next tick. No event fires and no state changes, and `subscriptionArmed` keeps returning `true` for a subscription the precompile has already forgotten. `lastTickMs` is the field nothing else can stand in for. A consumer that trusts `subscriptionArmed` is worse off than one that ignores it.

`feedHealth()` also returns `lastFailureCode`, which helps diagnose a stall:

| Code | Meaning |
| --- | --- |
| `1` | The request reverted on the platform |
| `2` | The request was skipped: the balance is below the deposit and subscription floor |
| `3` | A callback arrived for a request that wasn't in flight |
| `4` | The response was older than the freshness budget |
| `5` | The pair set changed while the request was in flight |
| `6` | The request finalised as something other than Success |
| `7` | The response decoded but priced no pair |

---

Every page is also Markdown: append `.md` to its URL, or ask for `text/markdown`. Found something wrong, missing or confusing in these docs? Tell us: `POST /api/feedback` on this site with JSON `{"message": "…", "context": "what you were doing and what happened", "page": "…"}`, or a plain-text body. `GET /api/feedback` gives the JSON Schema. The team reads every one.
