Skip to main content

Health Tree

A platform-agnostic Python library for health across a dependency graph: nodes, checks, dependency-aware episodes, and an attention policy.

A house, or any system you can draw as dependencies, can lose a whole machine, a controller, one device, a battery, or a login, or can fail to carry out a command. Usually nothing says which of those happened. You find out when the lights stop following motion, or when the music won't play with guests over. Monitoring tends to either page on every leaf or watch nothing.

Health Tree is the layer that tells a root failure from its symptoms, says what the failure takes down, and decides who hears about it and when.

Home Assistant is the first consumer. Its integration, homeostatic, lives in its own repository and is not part of this package.

Status: first engine. The engine and the attention policy pass every story and scenario fixture. The design of record is docs/rfp.md (version 0.7, draft for review). ADRs 0001 to 0033 are accepted or superseded. Real observation proofs remain outstanding. Version 0.2.0 is on PyPI as health-tree.

Principles

  • A node reports only what its own checks saw. A failed dependency never rewrites a node's status.
  • Cause flows down. A failed node mutes notifications for the dependents that fail with it.
  • Importance flows up. An episode is as important as the most important thing its root takes down.
  • One root, one episode. An episode is updated in place, absorbs roots that turn up late, coalesces siblings that fail together, and recovers once.
  • Attention is policy, not status. Rules decide who hears what, how loudly, and when. Quiet hours, digests, reminders, and escalation are configuration.
  • Delivery is the integration's job. A delivery names the episode, the recipient, the loudness, and the channel names. The integration turns those into a push, speech, a light, or a call.

What it catches

Shape Example Check that catches it
Loud An integration fails to load State
Quiet A sensor stops reporting Freshness: ttl, then stale
Plausible but wrong A person sensor reads "clear" because the detector hung Liveness of the signal path
Latent A login expired and nobody notices until the music is wanted State, evaluated when it breaks
Gradual A disk fills; a battery drains Capacity, with a projected deadline

It also covers operations that didn't do what they were told (the garage door was told to close and is still open) and maintenance debt with no symptom (months of pending updates).

It answers:

  • What is true of this node, from its own checks?
  • What broke first, and what does it take down?
  • Who should be told, how loudly, and when?
  • Why is this function not working? Is this set of functions ready?
  • What isn't being watched at all?

Model

The model is four jobs, not one hierarchy:

Job Question Shape Mutes?
Cause What broke what? One graph of hard dependencies Yes, and it's the only thing that may
Views How do I read the system? Named groupings, as many as needed Never
Problem definitions What can go wrong with this thing? Checks attached by the integration's catalog No
Attention What interrupts whom, how, and when? Policy rules Decides delivery, never status

A node is one capability: something that either works or doesn't, as its dependents see it. Functions ("garage", "motion lighting") are nodes too, which is what makes the explain, impact, readiness, coverage, and rollup queries possible.

Example

Behavior is specified as YAML stories and scenarios that the test suite runs. This one is abridged from story 8:

graph:
  - id: hub
    kind: device
    checks: [{id: link, ttl: 1h, ...}]
  - id: garage_door
    kind: device
    depends_on: [hub]
    checks: [{id: command, labels: {category: operation}, ...}]
  - id: garage
    kind: function
    importance: high
    depends_on: [garage_door]

steps:
  - at: 2026-09-24T23:10:30Z
    ingest:
      - {node: garage_door, check: command, status: fail, reason: command_failed,
         message: Told to close at 23:10 and still open}
    expect:
      events:
        - {opened: {anchor: garage_door, importance: high, reasons: [command_failed]}}
      deliveries:
        - {loudness: urgent, to: michael}

The door is only a device, but the garage function that depends on it is high importance, so the episode is high. The policy's rule for high-importance operations makes it urgent, so it goes out at 23:10 through quiet hours. When the door reports the command completed and the clear hold passes, the episode resolves once.

Design constraints

  • Pure Python 3.14 with no runtime dependencies.
  • No Home Assistant imports.
  • No I/O, threads, event loop, sleeping, or clock reads. Every call that depends on time takes now, a timezone-aware UTC datetime. The integration supplies observations and the time, and carries out deliveries.
  • The core knows nothing about batteries, add-ons, or any device. A new fault is a check registered by an adapter.
  • State survives restarts through snapshot and restore.
  • The library can't report the death of the process it runs in, so any deployment needs an external watchdog.

Registering a graph

The 0.3.0 candidate adds atomic graph registration.

Use engine.register_many(nodes, now) when an adapter discovers multiple nodes at once. The batch adds or replaces those nodes, keeps unchanged nodes and retained check state, validates the final graph, and evaluates once. Empty batches, duplicate node ids, cycles, and reserved redundancy groups are rejected without changing state or time. register(node, now) has the same behavior for one node. Apply initial evidence separately with ingest_many.

Only register monitored capabilities and the dependencies needed to describe them. Healthy monitored nodes belong in the graph so they can report a later failure. An unmonitored requirement belongs too: its missing evidence must remain visible to readiness and coverage. An adapter's wider inventory need not be a health graph. See ADR 0033.

Attention integration

Use Policy.handle for engine events and advance for due work. Deliveries carry opaque recipients/channels and an output cause (open, update, remind, escalate, activate, or digest). The adapter renders and transports them. explain(episode_id) reads the evaluated rule, recipients and pending times without advancing time. Snapshot data stays an opaque persistence contract.

Call activate(now, context) only when the owner starts attention afresh, such as enabling notifications after record-only monitoring. It preserves episode identity and age, restarts escalation, and returns or schedules initial requests subject to batching, quiet hours and shelves. Reminders begin with each recipient's actual request. An adapter can combine activation requests into summaries. Ordinary restart uses restore, which preserves attention clocks and reads schema 1 or 2; new snapshots use schema 2. Scenarios 72 and 73 cover activation and reminder holds. Scenario ids 65 to 71 are reserved for the separate acknowledgment design increment.

Planned modules

Part Module Owns
Engine health_tree Graph, checks, evaluation, inhibition, episodes, importance, quiet windows, snapshots, queries
Attention policy health_tree.policy Rules, recipients, loudness, quiet hours, digests, reminders, escalation, shelving
Conventions health_tree.conventions Standard reasons, categories, and label names, with no behavior

Repository layout

src/health_tree/     package (engine, policy, and conventions as they land)
tests/               unit and property tests, and the fixture runner
tests/fixtures/      stories and scenarios: the executable spec
docs/rfp.md          design of record
docs/adr/            architecture decision records

Documentation

Development

You need uv and git. uv installs Python 3.14 itself.

git clone https://github.com/mjcumming/health-tree.git
cd health-tree
uv sync
uv run prek install
make check        # everything CI runs: hooks, tests with coverage, build

make help lists the other targets. On Windows, the make targets need Git Bash or WSL, and each one is a short uv run command you can run directly.

License

MIT

Release files for health-tree 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for health-tree 0.3.0
File Size Uploaded
health_tree-0.3.0.tar.gz 212.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for health-tree 0.3.0
File Interpreter ABI Platform
health_tree-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 244.9 kB

Release files / health_tree-0.3.0.tar.gz

Download URL health_tree-0.3.0.tar.gz
Size 212.7 kB
Tags Source
SHA-256 checksum
How to use checksums
9f460bc8f4a0ac9c98d7a9b268e226dc8a5fec879bc32096c990f746883fcd76
BLAKE2b-256 checksum
How to use checksums
33692306484caa60e3b9e0b5d10ca2467a67f57ec4d88ac6bba81ed64d33d853
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / health_tree-0.3.0-py3-none-any.whl

Download URL health_tree-0.3.0-py3-none-any.whl
Size 32.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8af0aaf2147182817e6ecd35856145f9905cb9215cba3d60778f357520f67260
BLAKE2b-256 checksum
How to use checksums
36ef46e53ea9433a5cda03c4bc184eccba1c07ec09873d1db918a88ebee89ab5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page