Skip to main content

Intents

Intents are the structured "verbs" behind voice and text control. A sentence like "turn on the study light" is matched by the conversation layer and executed as an intent (HassTurnOn with a name slot). Integrations can register their own intent handlers, and external clients can invoke intents directly over HTTP.

The intent identifiers below are literal protocol strings and must be sent exactly as shown.

Anatomy of an intent

An intent invocation carries:

  • intent_type — the identifier, e.g. HassTurnOn
  • slots — named parameters, each shaped {"value": ...} (plus optional text)
  • context: originating platform, text_input, language, assistant, device_id, satellite_id, conversation_agent_id

Handling returns an intent response with a response_type (action_done, query_answer, error), optional speech (plain or ssml), the matched/affected targets, and — on error — an error code and message.

Built-in intents

Registered by the intent component at setup: HassTurnOn, HassTurnOff, HassToggle, HassGetState, HassNevermind, HassRespond, HassBroadcast, HassSetPosition, HassStopMoving (cover/valve platforms), HassClimateGetTemperature, HassGetCurrentDate, HassGetCurrentTime, and the timer family (HassStartTimer, HassCancelTimer, HassCancelAllTimers, HassIncreaseTimer, HassDecreaseTimer, HassPauseTimer, HassUnpauseTimer, HassTimerStatus).

The on/off handlers translate to the appropriate domain service automatically (e.g. covers open/close, locks lock/unlock, buttons press).

Registering custom intents

The handler API (apexos.helpers.intent)

from apexos.helpers import intent

class CountItemsIntentHandler(intent.IntentHandler):
intent_type = "CountItems"
description = "Counts items on a list"

@property
def slot_schema(self):
return {"list_name": str}

async def async_handle(self, intent_obj):
list_name = intent_obj.slots["list_name"]["value"]
response = intent_obj.create_response()
response.async_set_speech(f"{list_name} has 3 items.")
return response

intent.async_register(apex, CountItemsIntentHandler())

Key points from the helper:

  • intent.async_register(apex, handler) stores the handler in a registry keyed by intent_type; registering the same type twice overwrites (with a warning). intent.async_remove(apex, intent_type) unregisters; intent.async_get(apex) lists handlers.
  • IntentHandler attributes: intent_type (required), optional platforms (restricts which callers may trigger it via async_can_handle), description, and a slot_schema used to validate slots (each slot is validated as {"value": <validator>}; extra slots are allowed).
  • intent.async_handle(apex, platform, intent_type, slots, ...) is the single entry point: it looks up the handler, validates slots (InvalidSlotInfo on failure), and returns the handler's IntentResponse. Unknown types raise UnknownIntent; handler failures surface as IntentHandleError / IntentUnexpectedError.

For intents that boil down to a service call, subclass ServiceIntentHandler / DynamicServiceIntentHandler. These provide a standard slot schema for targeting — name, area, floor, optional domain, device_class, preferred_area_id, preferred_floor_id, plus your declared required_slots/optional_slots — and use the target-matching engine (async_match_targets) to resolve entities by name/area/floor with constraint checks (required features, states, device classes) and helpful match-failure errors.

The intent platform

An integration can ship an intent.py platform module exposing:

async def async_setup_intents(apex) -> None:
"""Register this integration's intents."""
intent.async_register(apex, MyIntentHandler())

The intent component discovers and calls this for every loaded integration that provides the platform.

Invoking intents over HTTP

POST /api/intent/handle (requires a bearer token) executes an intent directly:

curl -X POST \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"name": "HassTurnOn", "data": {"name": "study light"}}' \
http://apexos.local:1702/api/intent/handle

Body fields: name (required), data (slot values, wrapped into {"value": ...} automatically), optional language, assistant, device_id, satellite_id. The response is the serialized intent response; handling and match failures are returned as an error-type response rather than an HTTP error.

The conversation layer

The conversation component turns free text into intents:

  • REST: POST /api/conversation/process with {"text": "...", "language"?, "conversation_id"?, "agent_id"?, "device_id"?, "satellite_id"?} returns the conversation result (including the intent response).
  • WebSocket: conversation/process (same fields), conversation/prepare (preload language models), conversation/agent/list, conversation/sentences/list, plus debug commands (conversation/agent/apexos/debug, conversation/agent/apexos/language_scores) and chat-log subscriptions (conversation/chat_log/subscribe, conversation/chat_log/subscribe_index).
  • Service: conversation.process does the same from automations.

The built-in agent matches sentences against bundled sentence templates and any YAML files the user places under <config>/custom_sentences/<language>/, mapping matched sentences (and their slot captures) onto registered intents — so a custom intent handler plus a custom sentence file is the complete recipe for new voice commands.