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
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.
pnpm install
cp .env.template .env # set TYPESAFE_API_KEY
pnpm dev # http://localhost:3000Without a key, the page asks for one. pnpm test runs without a key, against a mock Jev.
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' }); // → offclient 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.
- 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
createJevLogicagent invoked at the top of the bar. Whenever the bar changes, it offers Jev everybarista.*event thatcan()accepts, each described in the machine's own words plus what it would change (computed with the puretransition()). 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.
/evalruns scenarios (rushes, breakdowns, sabotage) against real Jev and reports how many orders were served correctly and what it cost.
@xstate/jev: options,createJevLogic, caching, loop detection, the client- XState docs
- TypeSafe docs and JavaScript SDK