An assistant that guesses is worse than none.
Building a SaaS whose whole pitch is "trust what the AI extracted", and the eight problems we hit on the way, including the one where our first diagnosis was wrong.
What it does
Demo Agent Suite is for agencies that sell AI receptionists. Their problem is a sales problem: describing an AI assistant to a dentist or a plumber does not land, but showing one answering questions about that exact business does. So the product takes a prospect's web address, crawls the site, works out what the business does, and builds a branded demo of an assistant running on a copy of the prospect's own pages. The agency shares the link. The prospect sees their own business, answered correctly.
The stack is Lovable on the front, Supabase underneath, Firecrawl for the crawling, and a model call for the extraction. Around the core sits everything a product needs to charge money: accounts and onboarding, plans with a paid trial, a subscriber dashboard, transactional email, and the live agent widget wired in.
The rule that decided everything
The worst thing this product can do is be confidently wrong. If the demo tells a dental prospect their opening hours are nine to five and they are not, the agency has lost the deal and the product has lost the agency. So the standard from the start was: not found always beats a guess. Every extracted fact is marked found, not found or uncertain, and anyone can open the raw data behind a demo and see exactly what was read.
We did not arrive at that rule by thinking hard. We arrived at it by watching the first version get it wrong.
Eight things that went wrong, and what we did
- Sites that refuse to be embedded. Before writing any client code we tested against five real business sites. Two of the five blocked being shown inside another page at all. So the product checks the headers first and, when a site says no, falls back to a faithful snapshot that is clearly labelled as one, instead of a blank frame.
- The homepage is not the business. Same five sites: two had real gaps because the services or the hours lived on another page. A single fetch of the homepage was never going to be enough. The crawl now maps the whole domain, a model picks the pages likely to matter and skips the ones that do not, and extraction runs across all of it together.
- The confident wrong answer. A site had a one-off open day on its homepage with a time. The extraction read that as the regular opening hours. Plausible, false, and exactly the failure the product exists to prevent. The extraction prompt now has an explicit rule against inferring recurring facts from dated announcements, and the fix was re-tested against the same site and against one whose hours had been correct before, to make sure it had not broken anything.
- The wrong model, billed the expensive way. The platform's built-in AI gateway was convenient enough to hide two problems: it billed against platform credits rather than the client's own account, and it did not support the model every prompt had been written and tested against. Caught before launch. Calls now go straight to the client's own API key, on the model the prompts were built for.
- Valid input, rejected. Typing a bare domain without https:// failed browser validation before the backend ever saw it. Replaced with a plain field that normalises whatever is pasted: adds the scheme, strips a path back to the root, always emulates the homepage.
- A site blocking the server, not the browser. One site returned a 403 to the backend and loaded fine from a laptop. Not an embedding rule; the site's infrastructure was refusing datacenter traffic. Two fixes: every external call now has a bounded timeout and resolves to a clear state rather than a spinner that never ends, and the snapshot can be built through the crawler's own infrastructure instead of ours. A small number of sites will still refuse. That is an honest fallback message, not a problem worth chasing forever.
- The snapshot that rendered blank. After a layout pass, the fallback view for blocked sites came up as a solid rectangle. Extraction was fine; the display was not. Fixed and re-verified across fresh sites in three different verticals.
- The load test where our first diagnosis was wrong. Twenty sites submitted at once. Thirteen failed at the same instant, and several of the ones that finished seemed to contain another site's data. The second symptom looked like a serious cross-contamination bug. It was not. Checking the actual rendered demos, rather than the JSON copied from them, showed every demo was correct; the test script had twenty browser tabs sharing one clipboard, so copies were racing each other. The first symptom looked like a concurrency ceiling in the pipeline. It was not either. The API key used for testing had been suspended for non-payment. Topped up, eighteen of nineteen sites completed in under a minute each, and the nineteenth returned a clean error that the script simply had not been checking for. Zero real hangs. We record this kind of thing so it is not mistaken for a product bug when it resurfaces.
Two things nobody asked for
A "preview blank? show snapshot" button on live embeds, because a third party's cookie script can still break a frame that technically loaded. And a collapsible raw-data view on every demo with one-click copy, because a product whose pitch is "trust what we found" should let you see what it found.
What we would tell anyone building on an AI extraction
- Decide what the system says when it does not know, before you decide anything else.
- Test against real sites, early, and expect a third of them to be hostile in some way.
- When a load test looks like a data bug, look at the rendered output before the logs.
- Write the failures down as they happen. The log is worth more than the changelog.