Jev for Home Assistant

September 20, 2026 ยท View on GitHub

Tests hassfest HACS Action hacs_badge GitHub release License

Jev for Home Assistant

Ask TypeSafe Jev questions about your house and get numbers back. Jev is a decision model rather than a chat model, so it answers a typed question with a probability, a choice or a score, and this integration turns each answer into an entity you can automate on.

Full documentation

Not affiliated with TypeSafe. The API client is jevclient.

Every question becomes an entity, with the day's spend beside it

What it does

  • Questions become sensors: a probability, one of your options with its distribution, or a number that can land between levels. Add them in the UI, or in configuration.yaml, or both.
  • Four actions answer inside an automation and return a response variable: jev.noul, jev.choice, jev.score and jev.ask.
  • Point a question at entities, devices, areas, floors or labels in the normal picker and the state is built for you, so no template is needed.
  • A conversation agent for Assist, so spoken commands are routed by the same model and counted against the same budget.
  • Reports what it spends: calls, input tokens and estimated cost per day, plus a daily token budget that halts evaluation when it trips.
  • Fifteen worked examples, four of them pairing Jev with an LLM.
automation:
  - alias: Remind about the washing
    triggers:
      - trigger: state
        entity_id: binary_sensor.jev_laundry_forgotten
        to: "on"
        for: "00:10:00"
    actions:
      - action: notify.mobile_app
        data:
          message: The washing is done and still in the machine.

Installation

Requires Home Assistant 2026.9 or newer and an API key from typesafe.ai.

HACS

Not in the HACS default list yet, so add it as a custom repository once. hacs/default#11052 is queued; when it merges, steps 1 and 2 go away.

  1. HACS, then the three dot menu, then Custom repositories.
  2. Paste https://github.com/AboveColin/HA-Jev, set Type to Integration, Add.
  3. Search HACS for Jev, then Download.
  4. Restart Home Assistant.

Open your Home Assistant instance and open a repository inside HACS.

Manual

Copy custom_components/jev from the latest release into your config/custom_components/ directory and restart. HACS will not update a copy installed this way.

Configuration

Settings, Devices and services, Add integration, then Jev (TypeSafe). It asks for an API key and the address to send it to, and both are checked before the entry is created. The address already holds the TypeSafe API, so a key is all most people fill in.

Open your Home Assistant instance and start setting up a new integration.

OptionWhereDefaultDescription
API keyconfig flownoneYour TypeSafe key
API addressconfig flowhttps://api.typesafe.aiWhere the requests go. Leave it alone unless you run something of your own that speaks the same API
Daily input token budgetoptions0Stops evaluating for the day once spent. 0 means no limit
Price per million input tokensoptions0.042Only affects the estimated cost sensor
Fall back to this agentoptionsnoneWhere unrouted sentences go. Empty means the agent says it did not understand
Act only above this confidenceoptions0.6Below it, the sentence goes to the fallback instead
Allow whole-house commandsoptionsoffCommands naming no room or device. Turning everything off is always allowed

Use Reconfigure to replace the key or the address later, which keeps your entities and history.

Pointing it somewhere else

The address is checked before the entry is created, the same way the key is, so a wrong one fails there rather than later. Anything that answers POST /v1/systemone the way TypeSafe does will do: a proxy that holds the key, caches answers or meters what is spent across more than just Home Assistant. A path is kept as a prefix, so http://gateway.local:8093/jev becomes http://gateway.local:8093/jev/v1/systemone.

The key is sent as a bearer header. Over http that puts it on the wire in clear, where anything on the same network can read it, so the log says so once per setup unless the address is loopback. Leaving the field empty goes back to TypeSafe.

Actions

- action: jev.noul
  response_variable: laundry
  target:
    entity_id: sensor.washing_machine_power
  data:
    instructions: Is the laundry finished but still sitting in the machine?
    background: >-
      This machine draws under 5 W when idle and over 300 W while a programme runs.
    threshold: 0.7
- if: "{{ laundry.is_true }}"
  then:
    - action: notify.mobile_app
      data: { message: The washing is done and still in the machine. }

The same thing in the automation editor, and what a run of it looks like:

A question becomes a binary sensor you trigger onSix questions in one request, then three branches

The right-hand one is example 15. Six questions go in one request and five are thrown away, the action targets entities instead of building a template, and nothing acts until the confidence clears a bar. Its trace on a real instance, API call included:

The trace of one run, 0.31 seconds end to end

ActionYou give itYou get back
jev.noula yes/no questionnoul 0 to 1, is_true against your threshold
jev.choiceoptions:, 2 to 255choice, probabilities, confidence
jev.scorelevels:, 2 to 10, lowest firstscore, normalized, nearest_level, legend, probabilities, confidence
jev.askany mix, under your own keysthe same, under answers

All four take a template in state, or an object, or a list. They also take background: for standing facts about how to read the state, which is worth more attached to the question than to the state.

They send whatever you target, whether or not it is exposed to Assist. An automation names its entities on purpose, so the Assist exposure list is not consulted here. It is consulted for the conversation agent below. Watch that with include_attributes: true on a device_tracker, which puts coordinates in the request.

Questions

Settings, Devices and services, Jev, then Add question. Pick what kind of answer you want back:

Three kinds of answer

Name it, ask one thing, and say what it should look at. The schedule and the rest sit in a collapsed Advanced section, so a first question is four fields.

The form for a choice question

Before it saves, it shows you what it will send and what that answers right now:

The preview, with the trial answer

That last screen is worth the click. A question that reads a perfect state and still answers 0.5 is the common disappointment, and this is where you find that out rather than after the sensor exists. The trial costs one request, which the footer reports: 350 input tokens and $0.000015 in the shot above.

It also names which other questions it will share a request with. Questions that look at the same thing on the same schedule are sent as one call, because the API takes one state per request. You never declare that grouping, and this is where you see it.

Two worked examples

A washing machine that has finished but not been emptied. The readings are 1.4 W, the door shut, and 14 minutes since the programme ended, and the background field says what those numbers mean:

The laundry question answering 0.86

A cable modem where one reading sits near its limit. Downstream SNR of 31.2 dB is under the healthy 33, upstream power of 50.4 dBmV is near the 51 ceiling, and there are 1,184 uncorrected errors:

The connection question answering degraded

Worth reading that second one closely. It answers degraded at 0.59 with marginal right behind at 0.40, and a confidence of 0.46. That is the model saying the data is genuinely ambiguous rather than pretending otherwise, and it is why the actions return confidence at all: an automation can require 0.8 before it wakes anyone.

Both are sample data on a throwaway instance, not a real house.

Questions in YAML

The same thing in a file, which keeps working and is not deprecated:

jev:
  - name: Laundry
    scan_interval: 300
    entities:
      - sensor.washing_machine_power
      - binary_sensor.laundry_door
    questions:
      - name: Laundry forgotten
        type: noul
        instructions: Is the laundry finished but still sitting in the machine?
        background: >-
          This machine draws under 5 W when idle and over 300 W while a programme runs.
        threshold: 0.7
      - name: Nudge urgency
        type: score
        instructions: How urgently should someone be reminded?
        criteria: [Not at all, When convenient, Right now]
KeyRequiredDescription
nameyesNames the context and prefixes its entities
entitiesone of these twoEntities, devices, areas, floors or labels to read
stateone of these twoText or a template, alone or as a note beside the entities
scan_intervalnoSeconds between evaluations, minimum 30, default 300
trigger_entitiesnoWake on these instead of on whatever entities names
include_attributesnoSend every attribute of the picked entities, off by default
questionsyesEach with name, type, instructions, and criteria for choice and score

A context is one request, so keep related questions together. It is evaluated on scan_interval, or when an entity it watches changes, debounced by 5 seconds. Adding threshold: to a noul also creates a binary sensor to trigger on.

Voice

The integration adds a conversation agent. Settings, Voice assistants, pick your pipeline, set Conversation agent to Jev.

Assist answering through Jev

It sends one request per sentence, describing only the entities you exposed to Assist, and runs Home Assistant's own intents with what comes back. It turns things on and off, toggles them, sets a light's brightness and answers what something is set to. Anything else, anything phrased as two commands, and anything it is not confident about goes to the fallback agent whole, with nothing done first.

Locks are not among them and are never described to the model. Home Assistant reads turn_on on a lock as lock and turn_off as unlock, which is the opposite way round from how the command is spoken, and a probability with no reasoning should not be deciding whether a door opens. Lock sentences go to the fallback agent.

Against the built-in sentence matcher, it understands a command phrased a way nobody wrote a template for, and it returns a confidence the router can refuse to act on. Against an LLM agent, it is cheaper and it stops on its own: a command works out at about $0.0001 with 20 entities exposed and $0.0007 at the 150 entity cap, derived from the measured token cost per entity, and every one counts against the same daily budget as the sensors. A satellite that mishears a wake word all night trips that budget instead of running up a bill.

Brightness comes out of a regex, not out of a question, because Jev judges and does not calculate. 40 percent, 40% and 40 procent all work.

Commands naming no room and no device are refused unless you allow them, except turning everything off, whose worst case is a dark house.

Examples

01 laundry reminderone question, one threshold, one binary sensor
02 alert triagethree questions in one call, three notification paths
03 doorbell triagea choice on an intercom transcript
04 situation layernamed situations other automations trigger on
05 confidence gatingact, ask, or stay quiet
06 composite scoreseveral scores combined with your own weights
07 Jev gates the LLMa cheap typed decision in front of an expensive call
08 cascadelow confidence escalates to a reasoning model
09 guardrailthe LLM writes, Jev checks it against the source
10 extract then verifythe LLM pulls fields, Jev verifies each one
11 post and parcelsone attention queue across several channels
12 energy windowwhere to keep arithmetic and where to ask
13 voice commandsa command router, 12 questions per request
14 conversation agentwatching what the agent spends, and routing text Assist never saw

The LLM examples use ai_task.generate_data, so they work with Google Generative AI, OpenAI, Anthropic or a local Ollama. The voice command router follows TypeSafe's own smart home demo and builds its device options from your entity registry, so the answer is an entity_id you can act on.

Measurements

docs/measurements.md has what was measured against the live API: what an entity costs in tokens, why batching is nearly free, real latency from Europe against the published figure, and the two findings that changed this code.

Known limitations

  • Answers carry no reasoning, so there is nothing to audit afterwards.
  • Confidence has no published calibration evidence. Treat 0.9 as higher than 0.6 until you have measured it on your own questions.
  • Slower from Europe than the published 70 to 500 ms. Fine for a doorbell, too slow for a tight loop.
  • Not for safety decisions. A probability with no explanation should not hold a lock, a heater or a smoke alarm.
  • The conversation agent handles on, off, toggle, brightness and state questions. Locks, climate setpoints, anything needing words written and anything phrased as two commands go to the fallback agent.
  • Diagnostics include the last 20 sentences the agent routed. Read the file before pasting it into a public issue.

Troubleshooting

Turn on debug logging first. It prints every state sent, which is usually the answer:

logger:
  logs:
    custom_components.jev: debug
SymptomCause
An answer barely moves with the worldThe state does not say what you assumed, or it holds a number the model is being asked to compare
Answers sit near 0.5 with low confidenceThe question measures more than one thing. Split it
Entities unavailable, budget sensor onThe daily budget stopped evaluation
Entities unavailable, budget sensor offLook for one line saying TypeSafe is not answering
Setup fails with "TypeSafe did not answer"Connectivity, not configuration. Home Assistant retries
Voice commands all go to the fallbackCheck the traces in diagnostics. Each one records the reason
Voice acts on the wrong deviceThe names and areas in the entity registry are what the model reads
An error names a limitIt names your number too. 2 to 255 options, 2 to 10 levels, 250 entities

Contributing

Issues and pull requests welcome.

Python 3.14. The pinned pytest-homeassistant-custom-component requires it, and CI runs the same version, so 3.13 fails at install with "No matching distribution found".

pip install -r requirements-test.txt
pytest

204 tests run the integration inside a real Home Assistant with the API client replaced, so the suite spends nothing. quality_scale.yaml tracks this against Home Assistant's quality scale, and mypy --strict runs in CI.

Changelog

See the release history.