Skip to content

Introduce Home Assistant Labs#156840

Merged
frenck merged 10 commits into
devfrom
frenck-2025-0544
Nov 20, 2025
Merged

Introduce Home Assistant Labs#156840
frenck merged 10 commits into
devfrom
frenck-2025-0544

Conversation

@frenck
Copy link
Copy Markdown
Member

@frenck frenck commented Nov 18, 2025

Proposed change

This PR introduces Home Assistant Labs.

A feature that allows integrations to expose preview features that an user can opt-in to.

CleanShot 2025-11-20 at 13 59 00

It provides a standardized way to ship preview features that users can opt into before they become standard / generally available. Labs preview features are critical bug free, fully functional features that are being refined through real-world usage and feedback before becoming standard in Home Assistant. They differ from beta testing, which evaluates release stability.

They differ from betas. A beta test, tests for general stability of Home Assistant, while these previews are aimed at validating and iterating new features that might not have yet reached the envisioned feature set.

Demo video:

CleanShot.2025-11-18.at.22.53.03.mp4

A small example implementation is provided in the kitchen_sink integration.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

@home-assistant
Copy link
Copy Markdown
Contributor

Hey there @home-assistant/core, mind taking a look at this pull request as it has been labeled with an integration (kitchen_sink) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of kitchen_sink can trigger bot actions by commenting:

  • @home-assistant close Closes the pull request.
  • @home-assistant rename Awesome new title Renames the pull request.
  • @home-assistant reopen Reopen the pull request.
  • @home-assistant unassign kitchen_sink Removes the current integration label and assignees on the pull request, add the integration domain after the command.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component) to the pull request.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component) on the pull request.

Comment thread homeassistant/components/labs/__init__.py Outdated
Comment thread homeassistant/components/labs/__init__.py Outdated
Comment thread script/hassfest/manifest.py Outdated
Comment thread homeassistant/components/labs/__init__.py Outdated
Comment thread homeassistant/components/labs/__init__.py Outdated
Comment thread homeassistant/bootstrap.py Outdated
Comment thread homeassistant/components/labs/__init__.py Outdated
Comment thread homeassistant/components/labs/__init__.py Outdated
Comment thread homeassistant/components/labs/__init__.py Outdated
Comment thread homeassistant/components/labs/__init__.py Outdated

# Update storage
labs_data.data["features"][feature_id] = enabled
await labs_data.store.async_save(labs_data.data)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use the delayed save feature on the Store class async_delay_save? The user could click around causing multiple calls to this function in short succession.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've considered. It doesn't change as much + there is a process involved of confirmation and possible backups. We don't have many options and quick toggles anymore.

However, integration can respond to a change, so saving the state directly might avoid things like migrations issues or any other changes integration make on turning on/off the labs feature.

I've therefore opted to leave it as an immediate save.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would the migration issues or other issues be? That would be a bug in the base storage class.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not about the migration of the storage of Labs itself, it is about the integration that can do thing based on the change of this flag. It thus may have repercussions beyond this integration.

Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this has to do with the Store. The Store is used to save data on file. The source of truth during runtime is the labs_data.data attribute.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, but it are "experimental" features one is enabling; I feel that a direct write is more warranted, as we don't know or control what is happening next as a result out of this. It might be a full crash, migrations, many other things.

Besides that, considering this is not a high volume feature (at least not right now) and not a usual toggle to make (need confirmation, backups completion in between) it is less likely to need debouncing.

Comment thread homeassistant/components/labs/__init__.py Outdated
Comment thread homeassistant/components/labs/__init__.py Outdated
Copilot AI review requested due to automatic review settings November 20, 2025 13:13
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces Home Assistant Labs, a new system integration that provides a standardized way for integrations to expose preview features that users can opt into. The Labs system allows integrations to define preview features in their manifest.json, which are then surfaced through a WebSocket API and can be managed by users via the UI. The implementation includes comprehensive test coverage and an example preview feature in the kitchen_sink integration that demonstrates creating repair issues when enabled.

Key changes:

  • New Labs integration with WebSocket API for listing and updating preview features
  • Support for preview features in integration manifests with URLs for feedback, documentation, and issue reporting
  • Storage system to persist enabled preview feature states
  • Event system (labs_updated) to notify integrations when preview features are toggled
  • Example implementation in kitchen_sink integration showing repair issue creation

Reviewed Changes

Copilot reviewed 22 out of 23 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
homeassistant/components/labs/init.py Core Labs integration with storage, WebSocket handlers, and preview feature scanning
homeassistant/components/labs/const.py Constants, data classes, and type definitions for Labs
homeassistant/components/labs/manifest.json Labs integration manifest
homeassistant/components/labs/strings.json Translation strings for Labs UI
homeassistant/components/kitchen_sink/init.py Example implementation showing preview feature event handling and repair issue creation
homeassistant/components/kitchen_sink/manifest.json Added preview_features definition with URLs
homeassistant/components/kitchen_sink/strings.json Translation strings for preview feature and repair issue
homeassistant/generated/labs.py Auto-generated file containing preview feature definitions from manifests
homeassistant/loader.py Added preview_features property to Integration class
homeassistant/bootstrap.py Added labs to Stage 0 integrations and default integrations
script/hassfest/labs.py Script to generate and validate preview features from manifests
script/hassfest/main.py Added labs module to hassfest processing
script/hassfest/manifest.py Added preview_features validation to manifest schema
script/hassfest/translations.py Added preview_features translation schema
script/hassfest/quality_scale.py Exempted labs from quality scale requirements
script/hassfest/dependencies.py Added labs to allowed components and circular dependency exemptions
script/json_schemas/manifest_schema.json JSON schema for preview_features in manifests
tests/components/labs/test_init.py Comprehensive tests for Labs setup and storage
tests/components/labs/test_websocket_api.py Extensive WebSocket API tests covering all commands and scenarios
tests/components/labs/conftest.py Test fixtures for Labs tests
tests/components/kitchen_sink/test_init.py Tests for preview feature integration with repairs
CODEOWNERS Added Labs integration ownership

Comment thread tests/components/kitchen_sink/test_init.py
Comment thread tests/components/kitchen_sink/test_init.py
Comment thread homeassistant/components/labs/__init__.py
Comment thread homeassistant/components/labs/__init__.py
Comment thread tests/components/labs/conftest.py Outdated
@frenck frenck merged commit 482b5d4 into dev Nov 20, 2025
66 checks passed
@frenck frenck deleted the frenck-2025-0544 branch November 20, 2025 20:22
(preview_feature.domain, preview_feature.preview_feature)
in labs_data.data["preview_feature_status"]
)
for preview_feature_key, preview_feature in labs_data.preview_features.items()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use dict.values() instead.

await hass.async_block_till_done()

ws_client = await hass_ws_client(hass)
if preview_feature_enabled:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If all of the test code and all of the assertion code is different, using parameters doesn't make sense.

Just make a fixture for the setup code instead to reuse that in two different tests.

Comment thread script/hassfest/translations.py

async def test_async_setup(hass: HomeAssistant) -> None:
"""Test the Labs integration setup."""
assert await async_setup(hass, {})
Copy link
Copy Markdown
Member

@MartinHjelmare MartinHjelmare Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't set up integrations like this in tests. Use async_setup_component.


# Subscribe to labs feature updates for kitchen_sink preview repair
@callback
def _async_labs_updated(event: Event[EventLabsUpdatedData]) -> None:
Copy link
Copy Markdown
Contributor

@mik-laj mik-laj Nov 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this API could be simplified a bit by creating a helper function to create this listener function.

I'm thinking of something similar to the one below.

    entry.async_on_unload(
        preview_features.async_listen(
            domain="kitchen_sink", 
            preview_feature="special_repair",
            listener=lambda: _async_update_special_repair(hass)
        )
    )

This way, the integration author does not have to wonder whether Futures Lab uses the event bus and what the message format is.

This will slightly extend the existing public API we currently have.

async_is_preview_feature_enabled(hass, DOMAIN, "special_repair"):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we tend to avoid using core events nowadays for things that aren't user facing.

def _async_labs_updated(event: Event[EventLabsUpdatedData]) -> None:
"""Handle labs feature update event."""
if (
event.data["domain"] == "kitchen_sink"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use DOMAIN const here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have to, it is typed already.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh sorry the domain constant using kitchen_sink; yeah sure 👍



@callback
def async_is_preview_feature_enabled(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a function that retrieves all statuses for a given domain? I guess some component authors will want to display some warnings or something when at least one flag is enabled, or include information about the flag status in diagnostic data?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the need occurs we can evaluate that; it is a good idea, but I think we need a few actual needs and use cases before adding stuff like that.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would probably be worth adding information about enabled parameters for all integrations when it is available in the core. This way we already have the first function consumer.

"setup_times": async_get_domain_setup_times(hass, domain),

@github-actions github-actions Bot locked and limited conversation to collaborators Nov 22, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants