Tavo Is My Test Kitchen

There is a delicatessen in Lekki that does not exist.
It has two branches, a menu, a pastrami on rye, delivery zones across Lekki Phase 1 and Victoria Island, and a WhatsApp number that answers. I modelled it on Katz’s in New York, mostly because a fake deli is more fun to build than Test Restaurant 1.
A few weeks ago I ordered from it. Asked what sides they had, went back and forth about pastrami, typed an address, picked a zone, got a Paystack link and paid. ₦160,650, on test keys, but every other part of it real: the charge went through, the order was created, and it landed on the kitchen board.
I did not write a single line of the code that did that.
What it is
Tavo is a Restaurant OS. Ground up, my own thing, on a VPS I rent from OVH.
Menu and catalog with modifiers and combos. An orders board with a real state machine. An offline-first POS for a tablet in a shop with bad internet. Inventory with recipes. Customers and loyalty. Payments through Paystack. Delivery zones. Reports and end of day. Roles and permissions. A public menu page a customer can scan into. And a WhatsApp agent, which is the reason this stopped being a toy.
NestJS, Postgres and Redis for the core, Next.js for the admin, a separate PWA for the POS. A schema per module, row-level security for tenancy, everything in UTC with a per-location cutoff so reports come out timezone-correct by construction.
Four domains are live. The deli is a prop. None of the rest of it is. I started the repo on July 6; it is now 836 commits, 81 migrations and 428 test files, as of publishing.
The vibe coding part
I did not open an editor. I opened Claude Code and stayed in the conversation. Every feature was built by an agent, reviewed by an agent, tested by an agent, deployed by an agent.
That is vibe coding. But the word carries the assumption that makes people dismiss it: that vibe coding means no spec. Describe the vibe, ship what comes out. You can work that way, and it produces exactly what you would expect. Something that demos beautifully and falls over the first time a real person does something slightly unusual.
I moved the effort upstream instead.
Before any code, I browsed a restaurant backend I happen to know intimately, module by module, and wrote a PRD for each one. Twelve documents, about 30,000 words, each carrying a section marked “what works well, adopt it” and “what’s weak, redesign it.” Not a clone. A grounded starting point with the disagreements written down.
Then a plan document before every feature. There are 67 in the repo. Each names the files it will touch, the tests that prove it, and the thing that would make it wrong. Then the code, then a review that reads the whole branch rather than the diff, then a deploy that verifies six live surfaces before it flips the symlink.
So: was it vibe coding? Yes, I never wrote the code. But the win was not code generation. It was affording a level of specification and review that would have been economically insane for one person by hand.
The expensive, boring, load-bearing parts of engineering suddenly cost almost nothing. So I do all of them, every time. That is a stranger outcome than “AI writes code fast,” and a more useful one.
Ada
A restaurant connects its WhatsApp Business number through Meta’s embedded signup. A customer messages it. An agent picks up, with a persona the restaurant names. At my imaginary deli she is called Ada.
Ada has 18 tools: get the menu, find an item, manage the cart, quote a delivery zone, apply a coupon, reorder, confirm, check payment, hand off to a human. She gets eight tool-calling steps per turn and no more.
One rule holds up everything else: she is not allowed to do arithmetic. Every number that reaches a customer, every price, every upcharge, every delivery fee, every total, is verbatim output from a tool that read it out of the database. If the model wants to say a number, it has to go and get the number.
I do not trust a language model with money and I never will. In that pastrami order at the top, a smoke test checked against database truth line by line, the rule held perfectly: nine menu prices, four side upcharges, three modified cart lines, four zone fees, and the ₦160,650 total that went to Paystack. Every figure verbatim.
That rule was the easy part. The hard parts were not in the model at all.
What it taught me that no PRD would have
WhatsApp is a 4,096 character pipe, and a menu does not fit in it. Two production turns died on the model timeout, both of them menu requests. Speed was not the problem. A thousand-item menu is roughly 20,000 tokens, re-sent to the model on every step of the turn, and WhatsApp caps a text message at 4,096 characters, so it could never have been delivered however fast the query got. The fix was to stop pushing menus through the conversation at all: a price-resolved menu snapshot cached in Redis and rebuilt once per menu change rather than once per customer message, plus a public menu page the agent links to. Short menus are still read out inline; long ones get a link and a category summary. And because WhatsApp has no markdown, everything the model writes goes through a formatter that strips it and splits the reply into at most two messages, cut at a sentence boundary. A bot that answers in five paragraphs reads as a bot no matter how good the words are.
Delivery addresses, without paying Google a kobo. The rule I set was that a typed address is never geocoded — not “we’d rather not,” but never, as a hard constraint. Instead: WhatsApp already has location sharing sitting in the attachment menu, and it hands you a latitude and longitude for free. The merchant draws their own delivery zones as polygons in the admin, and a point-in-polygon check, about eighty lines of ray-casting maths with no runtime dependency, prices the order. A customer who will not share a pin picks their area off a list instead. The typed address is still recorded for the rider, and never used to work out where anyone is. Even the map in the zone editor is free: MapLibre against a Protomaps extract of Nigeria, self-hosted on the same VPS, because OpenStreetMap’s public tile server is not licensed for production apps. Geocoding exists in exactly one place, admin-only and district-level, for recentering that editor. The customer path costs nothing per order.
Discounts are where a chat agent most wants to be a salesperson. A model in a conversation will cheerfully tell you your loyalty balance covers most of the order, or work out what your total comes to after a coupon. Both are inventions with a currency symbol in front of them. So the agent can mention an offer that is genuinely running on WhatsApp, and it can ask once at checkout whether you have a code, and it can pass whatever you say to the pricing engine. It cannot know a code. There is no list of them anywhere in its context, so it can neither leak one nor guess one. And it cannot combine any two numbers it has been handed. If you want a total, it has to build the cart and let the tools price it.
None of these were model problems. They are the medium telling you what it is, and it only says so once you are inside it. All three are settled now — and not one of them was settled at a whiteboard.
Why I keep it running
Orda was acquired by Moniepoint Group earlier this year and now sits inside Moniebook as the restaurant layer. Which means the codebase I write PRDs against serves more than 30,000 businesses. Real ones, with money moving through them today.
At that scale a PRD is an expensive object. It commits engineers, a roadmap slot, and a migration against production data that someone’s payroll depends on. The cost of being wrong is not “we rewrite it.” It is months, and trust.
So tavo is where I go and be wrong first. I do not want to reason about whether a menu fits in a chat message; I want to have already watched two turns die trying. I do not want a whiteboard estimate of whether POS sync survives a busy bar; I ran nine load scenarios and 110,000 requests, found 27 times more headroom than we need, and found that what breaks first is a fixed database pool that dies of heap exhaustion instead of degrading. I crashed the server twice on purpose to prove it.
So when I write the PRD, the unknowns are already spent. I am not proposing a design. I am reporting from one.
Lately it has stopped being just mine. I have been putting the bot in front of other people at Moniebook, and a working thing you can text is a completely different conversation from a slide about a working thing you could text. People argue with a demo. Nobody argues with a deck.
Then a merchant got hold of it. Someone who had been looking for exactly this in the market and could not find it. That is the signal I could not have manufactured. Not “would you use this,” which everybody says yes to. Someone already out there looking, who found it, and wants to know when they can have it.
Go and break it
Text Ada: +1 (202) 918-4376. An American number, for a Lagos deli that does not exist, modelled on a New York institution. Order something. Try to trip her up. I would genuinely like to know how you managed it.
The rest is at admin.usetavo.co. Sign up, build a menu, set prices, get a storefront link, push orders around the board. Free, no sales call, and I am not going to email you.
One caveat, and I mean it kindly: do not run your business on this. Not “you probably shouldn’t.” Do not. This is one person’s playground on a single VPS, with no support, no uptime commitment and no plan for your data. I ship breaking changes on Tuesday nights because I felt like it, and I might turn the whole thing off with no notice, because the entire point of it is that I can. If your real revenue is in there the day I do, that is a bad evening for both of us, and only one of us will have earned it.
Go and break it. That is what it is for. Just do not feed your family with it.
Related Musings
Buttons Were for Humans
A 60-message Slack debate about a pop-up made me wonder: if agents are increasingly the ones navigating the software we build, who are we still designing for?
May 7, 2026 AIGetting Started with RAG
A practical introduction to retrieval-augmented generation: what it is, why it matters, and how I've been using it.
Feb 15, 2026