plugins/home-assistant-dev/skills/ha-architecture/SKILL.md
Home Assistant core internals — event bus, state machine, service registry, and integration loading. Use when asking about the hass object, event propagation, state management, service registration, or how integrations load.
npx skillsauth add l3digital-net/claude-code-plugins ha-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Home Assistant runs on a single-threaded asyncio event loop. All code shares this loop — blocking it freezes automations, the UI, and entity updates.
Every integration receives HomeAssistant instance (hass) — the central hub for all core systems:
from homeassistant.core import HomeAssistant
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# hass.bus — Event bus for pub/sub communication
# hass.states — State machine for entity states
# hass.services — Service registry for actions
# hass.config — System config (location, units, timezone)
...
The nervous system of Home Assistant. All component communication flows through events.
from homeassistant.core import callback, Event
# Fire an event
hass.bus.async_fire("my_custom_event", {"key": "value"})
# Listen for events (@callback = sync, no I/O allowed)
@callback
def handle_event(event: Event) -> None:
entity_id = event.data.get("entity_id")
new_state = event.data.get("new_state")
unsub = hass.bus.async_listen("state_changed", handle_event)
entry.async_on_unload(unsub) # Always clean up on unload
Key events: state_changed, homeassistant_start, homeassistant_stop, call_service, automation_triggered.
Tracks current state of every entity. States are immutable snapshots.
state = hass.states.get("sensor.temperature")
if state is not None:
value = state.state # Always a string
attrs = state.attributes # Dict of attributes
last_changed = state.last_changed
Special state values: "unavailable" (coordinator failed/device offline), "unknown" (no data yet).
Services (now called "actions" in UI) control devices. Register in async_setup, not async_setup_entry:
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def handle_action(call: ServiceCall) -> None:
entity_id = call.data.get("entity_id")
await do_something(entity_id)
hass.services.async_register(DOMAIN, "my_action", handle_action)
return True
configuration.yamlmanifest.json loaded firstasync_setup(hass, config) called (if present)async_setup_entry(hass, entry) calledasync_setup_entrySingle-threaded event loop: All async code shares one loop. Never block it.
Immutable state: hass.states.get() returns a frozen snapshot.
Integration isolation: Each integration manages its lifecycle via config entries. Store coordinator in entry.runtime_data (not hass.data).
development
Use when you're stuck or missing current information mid-task - the same command/API/approach failed twice, an error looks like a changed or deprecated API, or you need the current version of something, a fact from after your training cutoff, or to verify something you cannot confirm from the code in context. Starts with a cheap inline lookup and only escalates to a full research sweep if that fails. Do not use for routine pre-emptive checks before ordinary library work - for deliberate research, use /qdev:research.
documentation
Update Outline wiki documentation with implementation-level details from the current session by dispatching the up-docs-propagate-wiki sub-agent. This skill should be used when the user runs /up-docs:wiki.
documentation
Update repository documentation (README.md, docs/, CLAUDE.md) based on session changes by dispatching the up-docs-propagate-repo sub-agent. This skill should be used when the user runs /up-docs:repo.
documentation
Update Notion pages with strategic and organizational context from the current session by dispatching the up-docs-propagate-notion sub-agent. This skill should be used when the user runs /up-docs:notion.