Skip to content

Architecture

Hassette has a lot of moving parts, but at its core it’s simple: everything revolves around apps, events, and resources.

  • Apps are what you write. They respond to events and manipulate resources.
  • Events describe what happened—state changes, service calls, lifecycle transitions, or scheduled triggers.
  • Resources are everything else: API clients, the event bus, the scheduler, etc.

Hassette Architecture

At runtime, the Hassette class is the entry point. It receives a HassetteConfig instance that defines where to find Home Assistant, your apps, and related configuration.

Each app you write receives four lightweight handles — these are the objects you call in your automation code:

  • Api – call Home Assistant services, read entity states, and subscribe to WebSocket messages.
  • Bus – subscribe to state change events and service call events.
  • Scheduler – schedule one-off and recurring jobs.
  • States – read the current state of any Home Assistant entity, instantly, from local memory.
Internal services

Hassette starts several infrastructure services to support your apps. These are not user-facing and do not appear in your app code, but they may appear in debug logs:

  • WebsocketService – maintains the WebSocket connection and dispatches events.
  • ApiResource – typed interface to Home Assistant's REST and WebSocket APIs.
  • BusService – routes events from the socket to subscribed apps.
  • SchedulerService – runs scheduled jobs.
  • AppHandler – discovers, loads, and initializes your apps. Configured via Application Configuration.
  • StateProxy – tracks state changes and provides a consistent view of Home Assistant states.
  • DatabaseService – persistent telemetry storage, configurable via db_* fields in global settings.
  • WebApiService – serves the REST API, healthcheck, and web UI.
  • RuntimeQueryService – provides live runtime data (events, logs, metrics) to the web UI.
  • TelemetryQueryService – serves historical telemetry (invocations, executions, errors) from the database.
  • EventStreamService – event delivery pipeline.
  • ServiceWatcher – monitors and restarts failed services.
  • FileWatcherService – detects code changes for hot reload.
  • SessionManager – tracks session lifecycle.
  • CommandExecutor – dispatches app management commands.

Diagrams

These diagrams illustrate the architecture and relationships between the main components. Diagrams 1–2 show what Hassette is made of internally; diagram 3 shows the four handles your app code calls directly.

1) High-level flow

flowchart TD
    subgraph ha["Home Assistant"]
        HA["Events + API"]
    end

    subgraph hassette["Hassette"]
        H["Framework"]
    end

    subgraph apps["Your Apps"]
        APPS["Automations"]
    end

    HA <--> H
    H <--> APPS

    style ha fill:#f0f0f0,stroke:#999
    style hassette fill:#fff0e8,stroke:#cc8844
    style apps fill:#e8f0ff,stroke:#6688cc
Hold "Ctrl" to enable pan & zoom

2) Core services inside Hassette

flowchart TD
    H[Hassette]

    subgraph infra["Infrastructure"]
        direction LR
        WS[WebsocketService]
        DB[DatabaseService]
    end

    subgraph core["Core"]
        direction LR
        BUS[BusService]
        SCHED[SchedulerService]
        API[ApiResource]
        STATE[StateProxy]
    end

    subgraph web["Web"]
        direction LR
        WEB[WebApiService]
        RTQ[RuntimeQueryService]
        TQ[TelemetryQueryService]
    end

    subgraph apps["Apps"]
        APPH[AppHandler]
    end

    H --- infra & core & web & apps

    style infra fill:#f0f0f0,stroke:#999
    style core fill:#fff0e8,stroke:#cc8844
    style web fill:#f0f8e8,stroke:#88aa66
    style apps fill:#e8f0ff,stroke:#6688cc
Hold "Ctrl" to enable pan & zoom

3) What each app gets (lightweight handles)

flowchart TD
    subgraph app["App Instance"]
        APP["Your App"]
    end

    subgraph handles["Lightweight Handles"]
        direction LR
        API[Api]
        BUS[Bus]
        SCHED[Scheduler]
        STATES[States]
        CACHE[Cache]
    end

    APP --> API & BUS & SCHED & STATES & CACHE

    style app fill:#e8f0ff,stroke:#6688cc
    style handles fill:#fff0e8,stroke:#cc8844
Hold "Ctrl" to enable pan & zoom

Learn more about writing apps in the apps section.

Service Dependency Graph

Every internal Hassette service declares which other services it needs to be ready before it can initialize. This declaration drives both startup ordering and shutdown ordering — automatically, without any explicit sequencing code in the services themselves.

How depends_on works

Each service class carries a depends_on ClassVar that lists the resource types it depends on:

from typing import ClassVar

from hassette.core.database_service import DatabaseService
from hassette.resources.base import Resource
from hassette.resources.service import Service


class CommandExecutor(Service):
    depends_on: ClassVar[list[type[Resource]]] = [DatabaseService]

At startup, Hassette validates the full dependency graph and computes a topological initialization order. When a service initializes, it automatically waits for every service in its depends_on list to become ready before any of its own lifecycle hooks (on_initialize, etc.) run. You do not need to call wait_for_ready() yourself — the framework handles it.

depends_on is scoped to Hassette's direct children — the top-level services registered with the Hassette instance. It is not used for child resources inside a service.

Coordinator gate vs. service dependency

Hassette.ready_event is a separate mechanism from depends_on. It signals that the coordinator is ready to begin starting services, but does not guarantee that every service has finished starting. Services like BusService, SchedulerService, and FileWatcherService wait on it before processing user-visible work. Do not confuse this coordinator gate with depends_on, which expresses readiness ordering between individual services.

Initialization and shutdown order

Both startup and shutdown use wave-based ordering. The dependency graph is partitioned into levels: level 0 has services with no dependencies, level 1 has services that depend only on level 0, and so on. Each wave starts (or shuts down) concurrently via asyncio.gather, but waves execute sequentially — so all dependencies are guaranteed ready before their dependents begin.

Shutdown proceeds in reverse wave order. Services that depend on others shut down first; services depended upon (like DatabaseService) shut down last. For example, AppHandler shuts down before StateProxy, and StateProxy shuts down before WebsocketService.

Cycle detection

Hassette validates the dependency graph at construction time. If a cycle exists — for example, service A declares depends_on = [B] and B declares depends_on = [A] — startup raises a ValueError with the full cycle path before any service starts:

ValueError: Cycle detected: CommandExecutor → DatabaseService → CommandExecutor

Fix cycles by restructuring the dependency so one service no longer needs the other to be ready first.

Framework dependency graph

The key built-in services have the following declared dependencies, organized by startup wave:

graph BT
    subgraph wave0["Wave 0 — No Dependencies"]
        DB[DatabaseService]
        WS[WebsocketService]
        BUS[BusService]
        SCHED[SchedulerService]
    end

    subgraph wave1["Wave 1"]
        CMD[CommandExecutor]
        API[ApiResource]
    end

    subgraph wave2["Wave 2"]
        SP[StateProxy]
        TQS[TelemetryQueryService]
    end

    subgraph wave3["Wave 3"]
        AH[AppHandler]
    end

    subgraph wave4["Wave 4"]
        RQS[RuntimeQueryService]
    end

    subgraph wave5["Wave 5 — Last to Start"]
        WEB[WebApiService]
    end

    CMD --> DB
    TQS --> DB
    API --> WS
    SP --> WS & API & BUS & SCHED
    AH --> WS & API & BUS & SCHED & SP
    RQS --> BUS & SP & AH
    WEB --> RQS & TQS

    style wave0 fill:#e8f0ff,stroke:#6688cc
    style wave1 fill:#dde8f8,stroke:#6688cc
    style wave2 fill:#d0e0f0,stroke:#6688cc
    style wave3 fill:#c4d8e8,stroke:#6688cc
    style wave4 fill:#b8d0e0,stroke:#6688cc
    style wave5 fill:#acc8d8,stroke:#6688cc
Hold "Ctrl" to enable pan & zoom

An arrow from A to B means "A depends on B" — B must be ready before A initializes. Shutdown proceeds in reverse wave order.

For detailed diagrams of each subsystem's internals, see System Internals.

EventStreamService

EventStreamService has a constructor-time dependency: it passes a receive stream to BusService at Hassette construction time, before any service initializes. This structural ordering is enforced by child registration order rather than depends_on, which only expresses runtime readiness dependencies.

Deep Dive

For detailed Mermaid diagrams of every subsystem's internals — event routing, scheduler heap, state caching, the resource lifecycle state machine, and more — see System Internals.

See Also

  • Apps – how apps fit into the overall architecture.
  • Bus – subscribing to and handling events.
  • Scheduler – scheduling jobs and intervals.
  • API – interacting with Home Assistant.
  • States – working with state models.
  • Configuration – Hassette and app configuration.
  • Web UI – browser-based monitoring and management.
  • API Reference – full auto-generated reference for all public modules.
Advanced Topics

Once you've written a few automations, these topics give you more control: