A free slot is booked
- What is true before
- No bookings
- What the action is asked
- Ana books Saturday 10:00
- What should happen
- A Booking for Saturday 10:00 is made, and nothing else
You decide what your product must do. Your agent writes it, over MCP. Typestate checks every step, writes the code with a program, runs your examples in Sandbox and keeps Live behind a change that you approve.
You and your agent agree on the plan
Your agent builds, and Typestate checks every step
Typestate runs your examples, then the action goes Live
Each action goes through 8 stages on its way to Live. You act at the 2 orange moments: you say yes to the plan, and you approve the change.
The bike shop below is invented. The refusal, the fixed words and the code are what Typestate printed for its steps.
Part 1 of 3
YouYour agent
1 of 8
Your agentYou
Before the first record type or action of a new feature, your agent asks who uses it, what done means, the rules, and what must never happen. It asks a few questions at a time, restates the plan in your words, and waits for your yes.
Typestate gives every agent this instruction when it connects. A small edit to an action that exists needs fewer questions.
Your agentWho books a repair slot: anybody with the link, or only customers who signed in?
YouAnybody. They give a name and an email.
Your agentTwo people want Saturday at 10:00. What happens to the second one?
YouThey are refused. One bike per slot.
Your agentAnd when somebody cancels?
YouThen the slot is free again.
Your agentThe plan: a record type Booking, and an action Book A Slot that refuses a slot somebody holds. Shall I build it?
YouYes.
Part 2 of 3
Your agentTypestate
2 of 8
Your agent
Your agent defines the record type first. A Booking has a slot, a name, an email and a state.
Then it writes the action Book A Slot as 3 steps. Each step holds one sentence in your words and the typed calls that do it: read, write, if, stop, answer and a few more. The sentence says what you want, and the calls say exactly what Typestate does.
Booking
slotdate and timenametextemailtextstatetextBook A Slot
Refuse with "That slot is taken." when somebody already booked that slot.
[
{
"kind": "read",
"record": "Booking",
"count": true,
"where": {
"slot": {
"input": "slot"
},
"state": "booked"
},
"as": "taken"
},
{
"kind": "if",
"condition": {
"value": {
"name": "taken"
},
"at_least": 1
},
"then": [
{
"kind": "stop",
"refuse": {
"words": "That slot is taken.",
"status": 409
}
}
]
}
]Store the booking with the name and email the customer gives.
{
"kind": "write",
"create": true,
"record": "Booking",
"set": {
"slot": {
"input": "slot"
},
"name": {
"input": "name"
},
"email": {
"input": "email"
},
"state": "booked"
},
"as": "booking"
}Answer the booking's id and slot.
{
"kind": "answer",
"fields": {
"id": {
"name": "booking.id"
},
"slot": {
"name": "booking.slot"
}
}
}3 of 8
Typestate
Every record type and field must exist, and every value must have the right type. A refusal must be quoted in its sentence, so the words your customer reads are words you can read in the step.
When a check fails, Typestate refuses the action and says which step and why. The first version of step 1 failed this way, and your agent fixed the sentence.
Step 1 refuses with "That slot is taken.", and its sentence does not quote these words.
First version
Refuse when somebody already booked that slot.
Fixed
Refuse with "That slot is taken." when somebody already booked that slot.
4 of 8
Typestate
A program, and no model, turns the calls of each step into English from a fixed set of phrases. You read what a step does without reading code.
The same program writes the TypeScript. The same steps always give the same code, and nobody edits it. To change the code, your agent changes the steps.
Refuse with "That slot is taken." when somebody already booked that slot.
Counts the Bookings whose slot is the given slot and state is "booked", as taken. When the value named taken is at least 1: Stops and refuses with "That slot is taken." (status 409).
Store the booking with the name and email the customer gives.
Adds a Booking: sets email to the given email, name to the given name, slot to the given slot, state to "booked", as booking.
Answer the booking's id and slot.
Answers with id = the booking's id, slot = the booking's slot.
You can read this code and not change it. Typestate wrote it from the steps, with a program and no model.
import { defineFlow, Refusal, type Json } from "@typestate/sdk";
type Input = { email: string; name: string; slot: string };
export default defineFlow<Input>(async (ctx, input): Promise<Json> => {
ctx.step?.("1.1");
const taken = await ctx.db.Booking.count({ slot: input.slot, state: "booked" });
ctx.step?.("1.2");
if ((taken >= 1)) {
ctx.step?.("1.2.then");
ctx.step?.("1.2.then.1");
throw new Refusal("That slot is taken.", 409);
} else {
ctx.step?.("1.2.else");
}
ctx.step?.("2.1");
const booking = await ctx.db.Booking.create(
{ email: input.email, name: input.name, slot: input.slot, state: "booked" },
{
onlyWhile: {
terms: [{ record: "Booking", where: { slot: input.slot, state: "booked" } }],
below: 1,
refusal: "That slot is taken.",
status: 409,
},
},
);
ctx.step?.("3.1");
return { id: booking.id ?? null, slot: booking.slot ?? null };
});Look at step 2 in the code. The write counts the bookings again in the same moment it stores the new one, so two people who book 10:00 in the same second cannot both get it.
5 of 8
TypestateYour agent
A model reads each sentence beside its fixed words. When the two say different things, Typestate asks your agent a question on the action. The build goes on, and Sandbox runs it.
Your agent answers each question once. It changes the calls, or it keeps them with one line of reason. A kept question goes to Live with the reason, and you can read it on the change later.
QuestionStep 1
The sentence refuses when somebody already booked that slot.
The calls count only bookings whose state is "booked", so a cancelled booking does not hold the slot.
keep_units reason: "The owner said a cancelled booking frees the slot."
A model writes the words of a question, so they differ from one run to the next.
Part 3 of 3
YouYour agentTypestate
6 of 8
Your agentTypestate
Your agent writes at least one example for each action: what is true before, what the action is asked, and what should happen. A model checks that each example agrees with the steps it speaks for.
Typestate runs every example against the real code in Sandbox, at an address of its own such as repairs--leipzig-bikes.typestate.dev, with a database of its own. Live waits until every example passes.
run_examples
- A free slot is booked (passed). Judge: agrees with its sentences - A held slot is refused (passed). Judge: agrees with its sentences - A cancelled slot is free (passed). Judge: agrees with its sentences
Examples run from Starter, the first plan with Live. On Free, your agent writes them and Typestate keeps them.
7 of 8
Your agentTypestateYou
When Sandbox is right, your agent makes a change. It lists every action, record type and web endpoint that differs from Live, with each change to stored data, and Typestate keeps a fingerprint of what is in it. An edit after that makes the change out of date.
Your agent shows you the change and records your approval. A change with an open question, or one that takes payments, you approve yourself in the cockpit.
Bookings for repair slots
Kept by your agent, read when you can
Step 1 of Book A Slot. Your agent kept it and gave this reason: The owner said a cancelled booking frees the slot.
No open questionsApprove and go Live
8 of 8
Typestate
Approving starts the deploy. Live has its own address, such as repairs--leipzig-bikes.typestate.live, and its own database, and it runs on Cloudflare close to whoever calls it.
Typestate records every run with each step it took and how long the step took. The code stays on the action's Code tab, read only, as evidence of what runs.
Request
POST /bookings
{ "slot": "2026-10-17T10:00",
"name": "Ana", "email": "ana@example.com" }Steps
Answer
{ "id": "0b5e2c1a-7d4f-4e39-9a51-3c8e2f6b1d07",
"slot": "2026-10-17T10:00" }The times are an example.
Free gives your agent Sandbox: it builds, and you try every action at its Sandbox address. Starter runs your examples and opens Live.
Starter: EUR 15 a month (EUR 17.85 incl. VAT).
Choose a plan