Skip to content

About

Jev + espresso machine + state machine (XState)

Resources

Code of conduct

Contributing

Stars

28 stars

Watchers

0 watching

Forks

Repository files navigation

☕ Jevspresso

A simulated espresso bar where every barista decision is made by Jev, TypeSafe's System One model. Type an order in plain English ("a cap but with almond milk"), and watch Jev grind, tamp, pull shots, steam milk, and serve. You can work the bar too, or break the equipment and see how Jev copes.

Live demo: https://jevspresso.dev

demo.1.mp4

Motivation

An LLM agent usually acts by writing tool calls, so the app has to check every action after the fact. Jevspresso does it the other way around: an XState v6 machine models what is physically possible at the bar, and Jev only chooses among the events the machine accepts right now. Jev never writes an action. It picks one from a closed set, and the bar takes it only if it is still possible.

The repo is an example of using XState and Jev together, through @xstate/jev in packages/jev.

Quick start

pnpm install
cp .env.template .env   # set TYPESAFE_API_KEY, or a gateway key
pnpm dev                # http://localhost:3000

Without a key, the page asks for one. pnpm test runs without a key, against a mock Jev.

XState + Jev in brief

The same pattern as the bar, on a lamp:

import { createActor, setup, types } from 'xstate';
import { z } from 'zod';
import { createJevLogic } from '@xstate/jev';

const lamp = setup({
  actors: {
    jev: createJevLogic({
      events: 'lamp.*',
      instructions: 'Do what the latest request in `requests` asks, if the lamp is not like that already.',
      noop: 'leave the lamp as it is',
      client, // your server-side TypeSafe call
    }),
  },
}).createMachine({
  schemas: {
    context: types<{ requests: string[] }>(),
    events: {
      'user.request': types<{ text: string }>(),
      // What Jev may do: runtime schemas, described.
      'lamp.turnOn': z.object({}).describe('switch the lamp on'),
      'lamp.turnOff': z.object({}).describe('switch the lamp off'),
    },
  },
  context: { requests: [] },
  // Jev decides on the lamp whenever it changes.
  invoke: { src: 'jev' },
  // A request is just context: Jev sees it change, and acts on it.
  on: {
    'user.request': ({ context, event }) => ({ context: { requests: [...context.requests, event.text] } }),
  },
  initial: 'off',
  states: {
    off: { on: { 'lamp.turnOn': { target: 'on' } } },
    on: { on: { 'lamp.turnOff': { target: 'off' } } },
  },
});

const actor = createActor(lamp).start();
actor.send({ type: 'user.request', text: 'it is getting dark in here' }); // → on
actor.send({ type: 'user.request', text: 'time to sleep' });              // → off

client sends Jev's request to the TypeSafe SDK's systemOne() on your server, so the API key stays there. src/lib/jev.ts is the bar's client.

Try it live at /light (src/machines/light.ts): the lamp there is a switch and a bulb, as a real one is, and you can break the bulb. Ask for light with the bulb broken and the switch on, and Jev replaces the bulb. It is never told about bulbs: it sees whether the room is lit, and replacing the bulb is the move that makes it so.

How it works

Sequence diagram: the customer orders; the bar asks Jev to parse it; whenever the bar changes, the barista agent lists the moves can() accepts, asks Jev which one, and sends it to the bar only if still possible; the bar serves the customer.
  • The bar (src/machines/espressoBar.ts) is one parallel machine: order intake, the grinder, espresso machine and steam wand, one portafilter, one milk pitcher, and each barista's hands. It models physics, not recipes. Mistakes are possible; recipes are data the cups are compared against.
  • The barista is a createJevLogic agent invoked at the top of the bar. Whenever the bar changes, it offers Jev every barista.* event that can() accepts, each described in the machine's own words plus what it would change (computed with the pure transition()). Jev picks one, and the agent sends it if the bar still accepts it.
  • Orders are parsed by one Jev call that answers a set of closed questions about the text (which drinks, how many, which milk). Unclear orders go to a router agent that asks the customer to confirm.
  • /light is the smallest example: a lamp with your requests in its context, and Jev invoked at its top.
  • /eval runs scenarios (rushes, breakdowns, sabotage) against real Jev and reports how many orders were served correctly and what it cost.

Other decision models

Jev (1.14) runs the bar by default. Pick another model in the header, or with ?model=: every decision (reading orders, routing replies, the barista's moves) goes to it.

One gateway key is enough to switch between models: OpenRouter carries all of them, and Vercel AI Gateway all but Kev and Clef. A model's own key works too, and is tried first.

Model ?model= Own key OpenRouter Vercel AI Gateway
Jev 1.14 (TypeSafe) jev TYPESAFE_API_KEY ✓ ✓
Kev 4B (Jared Palmer) kev KEV_BASE_URL (self-hosted); optional KEV_API_KEY ✓
Clef, Clef Flash, Clef Omni (Cloudflare) clef, clef-flash, clef-omni CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN ✓
GPT-6 Luna Decisions (OpenAI) openai ✓ ✓
Decision-1 (Microsoft) microsoft ✓ ✓
Decider V1.2 (Perplexity) perplexity ✓ ✓
d1 (Liquid AI) liquid ✓ ✓

Set them in .env (copy .env.template): OPENROUTER_API_KEY or AI_GATEWAY_API_KEY for a gateway. Or give a key on the page (a gateway's, TypeSafe's, or a Cloudflare account and token): it stays in your browser and goes along with the requests it can serve.

Days

Play a day (or open ?day=<seed>) to give the bar twelve customers and a few mishaps (parts breaking, ingredients running out), each at a fixed time in bar time. The same seed is the same day, whichever model runs it, so ?day=42&model=clef and ?day=42 can be compared.

The customers know what they meant: when the bar asks "did you mean…?", they answer yes or no, and say their order again if it was not taken (twice at most). The day's clock stands still while the bar asks. At the end, a scorecard reports orders read right, asks, drinks served and how long people waited, and the model's requests and latency, with a button to play the same day with another model.

Resources

About

Jev + espresso machine + state machine (XState)

Resources

Code of conduct

Contributing

Stars

28 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages