Skip to content

// Concept

Capabilities, not variants

There is no edition of Halbert to choose, no licence tier that unlocks a feature, and no list of flags to set before a feature will work. What this machine can do is decided by looking for the thing: a capability is a named question with a cheap answer, and the feature that depends on it is on when the answer is yes.

The practical consequence is that “can it do X on my machine” is not a question the documentation can answer for you. Start it and look at what is offered: the list of capabilities this machine answered yes to is the same list the product consults.

Every feature in the product is gated by the same function, has_capability(), called at twenty-two places across fifteen modules. A feature that decides for itself, by consulting something else, is a bug rather than a variation.

That matters more than it sounds. A second way to answer the same question is a second answer waiting to disagree with the first, and the disagreement will be found by a user rather than by a test: one screen offering something another screen has already decided this machine cannot do.

Three steps, in this order, for every capability:

  1. An explicit entry under capabilities: in being.yml wins outright.
  2. A probe, where one exists — the actual look for the actual thing.
  3. The install preset’s default, otherwise.

The probes are deliberately cheap: a config file that exists, a token that is set, an address that is configured. None of them is a network call, because this runs at every start and a start that waits on a remote host is a start that hangs.

Here is a machine given the home preset and nothing else. The preset defaults config_watcher off and ha_connection on; the probes answer differently, and the probes win:

applescript False
audio False
config_watcher True <- preset says off; a config registry is present
discovery False
ha_connection False <- preset says on; no Home Assistant is configured
ingestion False
local_llm False
mcp_client False
scheduler False
secure_model False
secure_model_allowed False
sourceprep False
terminal False
web False

Add two lines to that machine’s being.yml:

capabilities:
terminal: true

and terminal reads True on the next start, with nothing else changed. That is the whole of the override mechanism, and it is the reason a machine never has to lie about what it is in order to do one extra thing.

An installation carries a variant — the shape it was set up as. It chooses the starting defaults and stops there: no backend feature asks the label when it is deciding what to do, and with one deliberate exception below the label does not overrule a probe.

This replaced a system where the label was the gate: a node labelled for the house could not run a terminal, a scheduler, ingestion or a local model, by definition, whatever hardware it had. A capable machine doing two jobs at once had no way to say so. The presets survive because defaults are useful; the gate did not survive because it was answering a question about labels when the question was about hardware.

_is_home_variant is what is left of it. It is legacy, it survives in exactly three files, and the only one of those in shipping code is a function that nothing calls — the other two are comments in tests recording that the patches they name are dead. Nothing should add a fourth.

One capability departs from the order above, deliberately: on a home-variant node sourceprep stays off even when the probe would find a daemon, so that an inherited environment variable cannot quietly switch it on. An explicit capabilities: {sourceprep: true} still opts that node in.

Where the label still decides something you can see

Section titled “Where the label still decides something you can see”

One place, and it is in the interface rather than the backend: the model picker filters its rows by variant, so a home instance is not offered the secure slot at all. The backend gate on that same feature is a capability, as everything else is. If you are on a home instance and looking for a row the documentation mentions, that is why it is not there.

  • Configure the thing, then restart. Capabilities are probed once at start and cached. Connect a local model, or point at Home Assistant, and the capability follows at the next start rather than at the next second. Exactly one setting re-probes as you flip it, and that is the web-search switch, because it governs egress and a delay there is a different kind of wrong.
  • A capability is not a permission. It says the feature may exist here, not that any particular action is allowed. applescript lets the tool surface register; a per-call switch still gates every execution. web allows a search to leave the machine; it is off unless you turn it on and no preset ever turns it on.
  • The reference is generated from the registry. The capability reference is emitted from the same file the probes live in, so it cannot drift from what the machine actually checks.
  • No command prints your capability list. The dashboard reads the registry and the reference page lists what exists, but there is no CLI subcommand that tells you what this machine answered yes to. The start-up log records it once, as an enabled and a disabled set.

Model locality and the secure tier is a decision with the same shape — one judge, no second opinion — on a question where the wrong answer has a cost you cannot take back. Connect a local model turns a capability on by configuring the thing it probes for.