---
title: "Smart home assistant demo walkthrough"
type: guide
tags: [demo, fan-out, speculative, llm-fallback, smart-home]
created: 2026-09-17
updated: 2026-09-17
confidence: medium
sources:
  - raw/docs/demos.md
  - raw/docs/demos__smart-home.md
  - raw/docs/patterns__fan-out.md
jev_version: "jev-1.13.0"
summary: "TypeSafe's smart home demo: one speculative fan-out per utterance routes device commands, while an LLM handles compound splitting and conversational fallback."
---

# Smart home assistant demo walkthrough

> **TL;DR** The demo evaluates every user utterance against one long question list — category, domain, device type, action, plus a Noul for "is this more than one command" — and lets code pick the answers that matter. An LLM is called only to split compound requests and to answer conversational ones. **The source code is not public yet** ("will be available on GitHub at release"), so this page documents the described design, not a file listing.

## Scope and caveat

`raw/docs/demos.md` lists exactly one demo:

> [Smart Home Assistant Demo](/demos/smart-home) - Evaluate user smart home requests with speculative questions and LLM fallback.

`raw/docs/demos__smart-home.md` describes itself as "Demo code: a smart home assistant that uses TypeSafe to evaluate user requests," and links a Loom walkthrough video (`https://www.loom.com/embed/18c4dbcf8db546dfb2d7f2ef018e78e4`). But the page's closing section says:

> This demo is a simple Vite/React single-page app that uses the TypeSafe API to evaluate user requests. The full source code will be available on GitHub at release. Its README includes instructions for running the demo locally and an overview of which bits of the source code are responsible for which parts of the demo.

So: the stack is a Vite/React single-page app, and the repository was not yet published as of the 2026-09-17 capture. Everything below is the design as the docs describe it. The exact `instructions` and `criteria` strings are **not** given by the source — this page quotes the questions as the docs phrase them in prose and does not invent JSON for them.

## Step 1: understand the worked example

The docs walk one request through the system:

> "Turn off all of the lights in the house"

> This is a very simple request, and our code will only need to consider the answers to the following questions:
>
> * "What category of request is this?" (smarthome command)
> * "What domain is this request targeting?" (whole house)
> * "What type of device is this request targeting?" (lights)
> * "What action should be taken on the lights?" (turn off)

Four questions, four answers, one device command. The demo's question list is longer than four — "Each user request is evaluated against a long list of questions, including many that will end up irrelevant for most requests."

Mapping to primitives (inferred — the docs give the question text but not each question's `type`): the first three read as [[concepts/choice|Choice]] questions over fixed option sets (categories, domains, device types), and "what action should be taken on the lights" as another Choice over that device's action set. The one question whose type *is* stated is the compound-request detector, which is a Noul.

## Step 2: see why the last question is speculative

> Notice that the last question is written with the assumption that the user is issuing a command to lights, and we ask it before we know what the user is actually requesting. This is what we call a "speculative question" - we ask it before we even know if it's relevant, allowing us to evaluate all questions in parallel and rely on code to filter out the irrelevant results after the fact. This is a key pattern for building systems that can handle a wide variety of user requests with a single set of questions.

That is [[patterns/fan-out]] exactly: the premise ("the user is commanding lights") is stated inside the question, the question is asked unconditionally, and code discards the answer when the category or device type says it does not apply.

## Step 3: know what the sequential version would cost

The docs spell out the anti-pattern, verbatim:

> The wrong way to do this would be to separate the questions in to multiple API calls, waiting to ask questions only once you are certain you need the answer:
>
> * "What category of request is this?" (smarthome command)
>
> Then, only once you know it's a smarthome command:
>
> * "What domain is this request targeting?" (whole house)
> * "What type of device is this request targeting?" (lights)
>
> Then, only once you know it's targeting lights:
>
> * "What action should be taken on the lights?" (turn off)

> This approach optimizes for a minimum number of questions, but it ends up being much slower and more expensive than batching all of the questions in to one upfront API call.

Three serialized round trips, each re-sending the same utterance, versus one call. For a voice-style interface where the user is waiting, that is the whole product.

## Step 4: how the code uses the answers

The docs describe three distinct consumption paths:

### a. Device commands — pure code

For the `smarthome command` category, code reads the domain, device type, and action answers and issues the corresponding device call. No LLM is involved on this path. (The docs describe the routing in prose; the dispatch code itself is not published.)

### b. Compound requests — Noul gate, then an LLM splitter

> **Splitting a compound user request:** One of the questions in this demo is a Noul question identifying if the user request is asking for more than one distinct action. If this is true, the system uses an LLM to split the request into a list of atomic commands. The split requests are then evaluated by TypeSafe individually.

The loop, then: one fan-out call per utterance → if the compound Noul is high, an LLM turns one utterance into N atomic utterances → each of those goes back through the same fan-out call. A Noul returns a probability in 0–1 with no separate `confidence` field, so this gate is a threshold on `noul` itself; the demo's threshold value is not published.

### c. Conversation — LLM fallback

> **Falling back to a conversational LLM:** When TypeSafe determines that the user query is a request for general information or conversation, the system calls an LLM to generate a freeform response. This allows an interactive system to handle requests with known deterministic behavior in a fast and cost efficient way, while still allowing for the flexibility provided by a generative LLM when needed. The initial TypeSafe response is so fast compared to the LLM response that it adds negligible latency to the overall system.

That last sentence is the architectural claim worth remembering: putting Jev in front of an LLM does not meaningfully slow down the requests that end up at the LLM anyway, and it removes the LLM entirely from the requests that do not. This is [[patterns/intent-routing]] with a generative escape hatch.

## Step 5: adapt it

What to change for your own domain (inferred from the described design):

- **The category question** is the top-level router: device command vs. conversation vs. whatever else your app does.
- **The domain and device-type questions** are the addressing scheme — rooms, zones, appliance classes. Keep option sets bounded; high-cardinality device catalogs want [[cookbooks/hierarchical-classification]].
- **One speculative action question per device class.** With N device classes you have N action questions in the fan-out, and code reads the one the device type selected.
- **The compound Noul** is generic; it belongs in any natural-language command surface.
- **Keep questions and thresholds in one file.** The [[reference/agent-skill|agent-skill]] guidance: "Put the constants (questions and thresholds) in a single place so they're easy to review."

## Gotchas

- **The source is not published.** Do not cite specific files, function names, or threshold values for this demo; they are not in the sources as of 2026-09-17.
- **The speculative action question needs an explicit premise.** Asked of "what's the weather," "what action should be taken on the lights" still returns an answer. It is only harmless while code ignores it.
- **Free of latency, not free of tokens.** Every utterance pays for the whole question list. See [[reference/models-and-pricing]].
- **Compound splitting re-enters the pipeline.** A splitter that emits N commands means N more Jev calls; budget for the worst case, not the average.
- **Client-side keys.** It is a single-page app; the agent skill's rule still applies — "Keep API credentials server-side in web apps." The demo's own key handling is not documented.

## Related

- [[patterns/fan-out]] — the pattern this demo exists to show
- [[patterns/intent-routing]] — the LLM/code/human routing shape
- [[concepts/noul]] — the compound-request detector's primitive
- [[concepts/choice]] — category, domain, device, action
- [[guides/quickstart]] — making the same call yourself
- [[concepts/use-case-map]] — other interactive-assistant use cases
- [[patterns/overview]] — the pattern catalog

## Sources

- raw/docs/demos.md (https://docs.typesafe.ai/demos)
- raw/docs/demos__smart-home.md (https://docs.typesafe.ai/demos/smart-home)
- raw/docs/patterns__fan-out.md (https://docs.typesafe.ai/patterns/fan-out)
