# Events and Rich Output

> Handle Thalovant speech replies, display items, policy events, and normalized SDK responses.

Source: https://docs.thalovant.com/developers/events-rich-output/

---
A reply is more than the sentence the hub speaks. Alongside the text there can be display items for a screen, the raw events the runtime emitted while it worked, and a policy decision when access was refused. Read the normalized fields first; they answer most apps. Reach for the richer ones when you need them, not before.

## What A Reply Contains

| Output | Use it for |
| --- | --- |
| `text` | The collected answer text, which can contain SSML. |
| `display_text` | Answer text with markup removed for visual display. |
| `display_items()` | Text, choices, tables, images, and attachments. |
| `events` | The collected events, including their `name`, `data`, and `context`. |
| `handled`, `ok`, and `failure_event` | Whether the request completed and any recorded runtime failure. |

Those are the Python names; Node spells them `displayText`, `displayItems()`, and `failureEvent`, and its `events` array carries the same events. Nothing called `speech`, `raw_event`, or `policy_denied` exists on a reply in any SDK, whatever older examples suggest. The language's [SDK page](/developers/sdks/) has the exact API.

## Event Names

Where a language exposes constants for these, use them. The names underneath don't change from one SDK to the next.

| Event | Meaning |
| --- | --- |
| `recognizer_loop:utterance` | A client sends a user utterance. |
| `speak` | The hub returns speech or reply text. |
| `ovos.intent.unmatched` | A request matched no skill (current OVOS name). |
| `complete_intent_failure` | A request matched no skill (legacy Mycroft name; still emitted by older runtimes). |
| `hive.policy.denied` | Access policy blocked the request. |
| `mycroft.skill.handler.start` | A skill handler started. |
| `mycroft.skill.handler.complete` | A skill handler finished. |

## Display Items

Display items exist so an app with a screen never has to parse the spoken sentence to find the table inside it.

  
    A paragraph, title, subtitle, or status message.
  
  
    A selectable option that can map back to `send_action`.
  
  
    Rows and columns for comparison, status, or inventory style output.
  
  
    A URL or attachment reference that the client can render.
  

## Python Example

```python
from thalovant import ThalovantClient

with ThalovantClient.from_identity_file("_identity.json") as client:
    reply = client.ask("Show the next maintenance steps.")

print(reply.display_text)

for item in reply.display_items():
    print(item.kind, item.text, item.data, item.url)
```

A `choices` item keeps its choices in `item.data`; each one can carry a `title` and a string `payload`, and the payload is what the app hands to `send_action` when the person picks it. Render the kinds your app understands and skip the rest.

## Client Behavior

| Case | Recommended handling |
| --- | --- |
| Answer text is present | Show `display_text` in a visual client; handle `text` or `utterances` according to your voice renderer's SSML support. |
| `display_items()` returns items | Render the items the client understands and ignore unknown kinds. |
| A `hive.policy.denied` event arrives | Show a safe denied message and keep the request ID for troubleshooting. |
| A request raises a runtime error | Handle it as a failed request; some failures are raised before a reply object exists. Intent inventory also has a typed policy error. |
| A reply has no useful output | Check `handled`, `failure_event`, and event names before presenting it as an answer. Redact event data before logging diagnostics. |

  Unknown display item kinds should be ignored, not treated as fatal. That keeps older clients compatible with newer skills.
