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.HassTurnOnslots— named parameters, each shaped{"value": ...}(plus optionaltext)- 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 byintent_type; registering the same type twice overwrites (with a warning).intent.async_remove(apex, intent_type)unregisters;intent.async_get(apex)lists handlers.IntentHandlerattributes:intent_type(required), optionalplatforms(restricts which callers may trigger it viaasync_can_handle),description, and aslot_schemaused 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 (InvalidSlotInfoon failure), and returns the handler'sIntentResponse. Unknown types raiseUnknownIntent; handler failures surface asIntentHandleError/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/processwith{"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.processdoes 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.