# How the Halcyon demo works

This demo shows your own UI: the UI-less client hands the page the conversation as data, and the page draws all of it. Watch the strip above the app. It lights when the agent's breathing-timer widget arrives and the app draws it on its own orb instead of a chat card.

Halcyon is a fictional sleep and wind-down app. You tell it what's keeping you awake, it answers in a few quiet sentences, and when a breathing exercise would help, the big orb on the left stops its idle breath and breathes with you. Everything you see is the app's own design. There is no Mindset chat window anywhere on the screen, and that is the point of this demo.

## The UI-less client

Most demos embed the `<mindset-agent>` element, which brings its own chat. Halcyon uses the UI-less client instead: the page imports `mindset-agent-uiless.js` by URL and calls `createAgentConversation({ agent: "halcyon", getSession })`. That returns a conversation with no UI at all. The page subscribes with `on(listener)` and draws the transcript itself from the typed events: `text_delta` streams into a frosted-glass bubble, `complete` closes it, and `run_error` becomes a gentle error card with a retry. The visitor's words go out through `send(text)`, and the Stop button calls `stop()`. The bubbles, the pill composer and the gradient sky are ordinary page markup and CSS.

The composer starts closed. It opens only once the session is fetched and the conversation exists, and a visually hidden `aria-live` status says "Starting Halcyon" until then, the same hold the UI-less reference page uses so nobody types into a box that isn't listening yet.

## A widget the page draws as an orb

The agent has one widget, `halcyon-breathing-timer`, with two parameters: `minutes` and `pattern` (box, 4-7-8 or slow exhale). In the element it would render as a small card. The UI-less client has no widget renderer, so the call arrives as a `widget-payload` event carrying `kind`, `doc` and `summary`, where `doc.data` holds the agent's arguments. The page reads those two values and hands them to the orb, which follows the pattern step by step with "Breathe in", "Hold" or "Breathe out" under it, then returns to its idle breath. The transcript keeps a dashed bubble noting that a timer started. The switch happens only when that event arrives, never on a timer of its own.

## Situational awareness and care

With every turn the page sends situational awareness saying whether a session is running, how long is left, or that one just finished. The agent reads it, so it never starts a second timer and can ask how the last one felt. Its policy rules keep it to relaxation: it never gives medical advice, and it points to a doctor or another professional for anything beyond winding down.

## Motion

The orb and the sky drift continuously because they are ambient brand surfaces. Under `prefers-reduced-motion` the orb rests still, and the page checks `matchMedia` so the timer counts each step in text instead.

## Build the same thing

1. Author the widget with its two parameters. See [Build a widget](https://docs4.mindset.ai/docs/ams/build-a-widget).
2. Create the agent, grant the widget and open it to the embed surface. See [How embedding works](https://docs4.mindset.ai/docs/sdk/how-embedding-works).
3. On your page, start [the UI-less client](https://docs4.mindset.ai/docs/sdk/the-ui-less-client), draw its events as the [events reference](https://docs4.mindset.ai/docs/sdk/events-reference) describes, and turn widget payloads into your own components as in [Draw widgets in your own UI](https://docs4.mindset.ai/docs/sdk/draw-widgets-in-your-own-ui).
