Demos / Your own UI · Halcyon
Halcyon
Your own UI: the UI-less client hands you the conversation as data, so every pixel is yours. In a sleep app, the agent’s breathing widget arrives as data and drives the app’s own orb.
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
- Author the widget with its two parameters. See Build a widget.
- Create the agent, grant the widget and open it to the embed surface. See How embedding works.
- On your page, start the UI-less client, draw its events as the events reference describes, and turn widget payloads into your own components as in Draw widgets in your own UI.