A concierge that read everything we ever published
Every agency site has a chat bubble. Most of them are a form wearing a costume. Ours had to know the work, the prices' whereabouts, the founder's boundaries, and all twenty-one essays in this Journal — so we built it the way we build everything else, and wrote down what it cost.
On this page
There is a small pill in the corner of this page that says ask the studio. Behind it is the thing every consultancy website has promised since 2016 and almost none has delivered: a chat box that actually knows the company it fronts. Ask it what a web platform engagement includes and it recites the real deliverables. Ask it what we think about generated code and it quotes the essay. Ask it where Edward lives and it politely declines, because we taught it manners along with facts.
We could have pasted a third-party widget above the footer and moved on. But we have printed we conjure every line of code & pixel under our own name, and a rented chat bubble streaming someone else's branding through our corner would have argued against every page it floated over. So the concierge is ours end to end: a Supabase Edge Function, an OpenAI model, a client the size of a business card, and a knowledge layer with a story we did not expect to tell.
Identity without an account
The first design decision was who the visitor is. Chat history should survive a page reload — losing the conversation because you checked the pricing page is amateur hour — but nobody wants to create an account to ask a question, and we did not want to run anonymous authentication for what is, structurally, a guestbook with opinions.
So identity is a token: the widget mints a random thread id and a secret in your browser's storage, and the server keeps only a hash of the secret. Present the pair and the conversation continues; lose them and you simply start fresh. The thread tables are locked to the service role — no public read policy exists at all, so there is nothing to misconfigure. It is the same shape as the share tokens our quote pages have used since day one, which is exactly why we trusted it.
Always 200
Replies stream token by token over server-sent events, and the transport has one rule inherited from the /blueprint generator: the answer is always an HTTP 200 event stream, even when it is bad news. Rate limited? That is an error event. Thread token invalid? An error event. The alternative — a bare 4xx after the CORS preflight has already succeeded — is a failure the browser can see but your JavaScript cannot read, which is the worst of both worlds.
The stream also carries more than text. When the model finishes, a done event delivers the stored reply's id, which is what makes the little thumbs under the answer work. When a lead is captured mid-conversation, a lead event tells the page so our analytics can count it. The protocol stays three verbs wide and we intend to keep it that way.
Abuse is bounded by arithmetic rather than optimism: ten requests a minute per address through the same durable rate limiter the contact forms use, two thousand characters a message, a twenty-message context window, a thousand tokens of reply. A bot that wants to spend our inference budget has to get in line behind everyone else's curl.
The file we wrote for other robots
The interesting problem was knowledge. A concierge that answers from a model's general training is a liability in a nice suit: it will invent services we do not sell and prices we never quoted. Everything it says about the studio has to come from the studio.
Here is the part we did not plan. Months ago we published /llms-full.txt — a machine-readable digest of the whole site, maintained automatically from the same content the pages render, so that other people's AI assistants would describe us accurately. When we went looking for a grounding corpus for our own concierge, it was already sitting there: current, complete, and tested by strangers' robots for half a year. The function fetches it, caches it briefly, and hands it to the model with every question. The file we wrote for other robots turned out to be the brain of ours.
On top of the digest sit two more tiers. A short, hand-written canon carries the rules that must never drift: the brand's banned words, the refusal to invent numbers, and a founder profile with an explicit boundary — everything the concierge may say about Edward personally fits in six bullet points, and it is instructed to refuse the seventh. Not because the model would otherwise gossip, but because a public bot's limits should be a design decision, written down, not an emergent property.
Teaching it the Journal
The third tier is this Journal. Twenty-one essays about how we build things is exactly the material a visitor's hardest questions want — how did you make Safari fast, what broke in the museum, why publish prices at all — but at some sixty thousand words it cannot ride along in every request.
So the essays are cut into passages and embedded into vectors, and every incoming question is embedded the same way and matched against them. The four closest passages, above a similarity floor, travel with the prompt; the model is told to quote freely and link the source. Ask how we made Mobile Safari fast and the concierge answers with the actual numbers from /articles/making-mobile-safari-fast — the 250-to-950-millisecond TTFB that became single digits, the 91 kilobytes of JavaScript that left the initial bundle — because it is reading the same paragraphs you would.
The index rebuilds automatically on deploy. Which produces a sentence we enjoy typing: by the time you read this, the concierge has read it too. Articles about the machine become part of the machine — ask it how it works and it will cite this very page at you.
When someone wants to hire us
A studio chat box has one commercial job: notice when a conversation stops being curiosity and starts being a project. The model carries a single tool for this — it can file a lead with a name, an email, and a one-line brief, and it is instructed to use it at most once, only with details the visitor actually typed, and never to ask twice. The lead lands in the same table as the contact form's, the studio gets pinged, and the visitor gets one sentence of confirmation instead of a follow-up sequence.
Mechanically this is the least glamorous part of the build and the most instructive: mid-stream, the model pauses its own reply, calls the tool, receives the result, and continues talking — two upstream requests stitched over one connection to the browser, invisible unless you are watching the wire. The visitor experiences a sentence. That asymmetry is most of what engineering is.
Numbers we can print
- ~5,800
- prompt tokens riding with every question
- 130
- Journal passages in the vector index
- 6
- locales the concierge greets you in
- 10/min
- requests per visitor before the limiter objects
Measured on ship day, not estimated — the reply that produced the token count said, in full, "OK".
Every reply writes its own token receipts into the database, so the monthly cost of the concierge is a query, not a guess. We can also see which answers earn the thumbs-up under them and which do not — the ratings land next to the receipts. A feature you cannot measure is a feature you cannot defend; this one ships with its own ledger.
Limits, stated out loud
- The Journal index is English-only for now. Cross-language matching works more often than it should, but a Russian question meets English passages; embedding the translated essays is the honest fix and it is on the list.
- The model sees the last twenty messages of a thread. A long conversation's opening eventually falls out of view; a rolling summary is designed but not built.
- Rate limiting is per address, so an office behind one router shares an allowance. Nobody has hit it yet; the day someone does, the key gets smarter.
- The concierge speaks for the studio but it is still a language model. It is grounded, bounded, and measured — and if it ever says something the site does not, the site wins.
Whether a chat box belongs on a studio site at all is a fair question — we wrote a whole essay about tools that respect you, and a bad concierge is the opposite of that. Our answer was to hold it to the same bar as everything else here: it streams because waiting is rude, it refuses because boundaries are content, it cites because it read the sources, and it leaves when you are on a page that has its own furniture. The museum keeps its own chat; ours stays out of the frame.