I put a Claude agent on my marketing site. The hard part was deciding what it can't say. PropSaaS Growth.

Most write-ups of "we added an AI agent to our website" spend their length on the stack. The stack is the easy part, and it got easier again this year. What actually determines whether a website agent helps or hurts you is a product decision that has nothing to do with engineering: deciding which questions the agent is allowed to answer, and what it does instead when a visitor asks one of the others. This is the full build for the agent running on this site, including the parts that are still wrong.

What I Built, and What It Actually Costs

The agent is a single Cloudflare Worker exposing one endpoint, POST /api/chat. The frontend is a vanilla-JS widget injected at the bottom of the homepage. It keeps the conversation in localStorage and posts the entire message history on every turn, which means the Worker holds no session state at all. Any isolate can serve any request. There is no session store to scale, expire, or leak.

Inside the Worker, the request runs through a standard agent loop against Claude Sonnet 4.6 with a single tool available. Thinking is disabled and reasoning effort is set to low, because a visitor asking what we do does not need deliberation, they need a fast answer. Responses cap at 1,024 tokens. The loop runs a maximum of five iterations before it returns whatever it has.

The cost shape is the part people usually get wrong in both directions. A full visitor conversation of three to five turns costs roughly $0.01 to $0.02 in model charges. Two hundred conversations in a month is about $4. Edge compute at marketing-site traffic is a rounding error against that. The system prompt is marked for ephemeral caching, so the grounding document is not re-billed at full rate on every turn of the same conversation, which is what keeps the per-turn cost from climbing as the document grows.

That is the whole architecture. No queue, no database, no orchestration framework, no RAG pipeline. The interesting decisions are all upstream of it.

Why There Is No Vector Database

The agent's entire knowledge base is one file: an 89-line markdown document covering who we work with, the three service lines, how we work, what we do not do, how pricing works, and when a visitor should book a call. It gets bundled into the Worker at build time as a string and concatenated onto the system prompt. That is the whole retrieval layer.

Retrieval exists to solve a specific problem: you have more context than fits in a prompt, so you need to select the relevant slice per query. A B2B SaaS marketing site does not have that problem. The set of things a visitor can legitimately ask about your company is small, stable, and already written down somewhere. Ours fits in a few thousand tokens with room to spare. Adding embeddings, a vector store, and a chunking strategy to that would introduce retrieval failures into a system that currently cannot have them, because the agent always sees everything.

The second benefit is more important than the cost one. Because the grounding is a single human-readable file, changing what the agent knows is editing prose and shipping it. It reviews in a pull request. A non-engineer can read the whole thing and tell you whether it is right. Compare that to auditing what a retrieval system will surface for a given query, which is a much harder question to answer confidently, and one you have to re-answer every time the corpus changes.

There is a discipline that comes with this. The same file is the single source of truth for a local command-line version of the agent I use to test prompt changes before deploying. One file, two consumers, no drift. Reach for retrieval when your answerable surface is genuinely too large for a prompt or changes faster than a person can maintain it. On a marketing site, neither is usually true.

The Four Things the Agent Cannot Say

The grounding document ends with a section titled "What we will not answer in writing." It lists four categories, and they are the substance of the whole build:

  • Specific pricing, including ranges and any version of "what would this cost for us."
  • Specific timeline commitments.
  • Whether we are a fit for a particular named company.
  • Comparisons against named competitors, whether agencies or in-house alternatives.

Look at that list next to what a visitor actually wants to know, and the tension is obvious. Those four categories are close to the complete set of questions a serious buyer arrives with. A curious reader asks what you do. A buyer asks what it costs, how long it takes, whether it works for a company like theirs, and how you compare to the alternative they are also considering. The agent is forbidden from answering all four.

That looks like a broken product until you consider what the alternative does. An agent that answers "roughly what does an audit cost" with a number has resolved the visitor's open question. They now have what they came for, and the reason to talk to a person is gone. If the number is wrong for their situation, and it usually is, because scope depends on their footprint and their team and their competitive position, then the agent has also anchored them to a figure that a real conversation now has to walk back.

Removing prices from the grounding document is not sufficient on its own, and this is the part that surprised me. A language model with no prices in front of it will still produce an estimate if asked directly, because estimating is what it does. The system prompt has to carry an explicit prohibition: never quote a price, fee, range, or duration, including in cases where the source material mentions numbers. Refusal has to be specified. It is not a property you get for free by withholding data.

Why Refusing to Answer Is the Conversion Event

The design move that makes the restriction work is reclassifying those four questions. They are not information requests to be declined politely. They are buying signals, and the system prompt treats them that way.

The instruction is explicit that any question about price, fee, cost, scope, timeline, "what would this be for us," "would we be a fit," "let's talk," "interested," or "next steps" constitutes intent, and that on detecting intent the agent must follow the booking protocol rather than write a prose answer. If the visitor has already given their name and company earlier in the conversation, the agent calls the booking tool immediately and does not also answer the question. If it does not have those details yet, it asks for them in one short reply, and still does not answer the question.

The tool takes three arguments: name, email, and a one-sentence description of what the visitor wants to discuss, in their own words. It returns a Calendly URL with all three prefilled, so the visitor lands on a booking page with the form already filled in and the topic captured in their framing rather than mine. The highest-intent moment in the conversation produces an action instead of an answer.

This is a qualification gate as much as a conversion one. Someone who will not give a name and email in exchange for a pricing conversation was not going to become a client. Someone who will has just self-identified, and the topic field tells me what they care about before the call starts. Working out which questions signal intent is the same exercise as defining who you actually sell to, and the answer should come from the same place.

One Tool Definition, Three Surfaces

The booking tool is not exclusive to the chat agent. The same capability is exposed three ways on this site, and all three produce an identical prefilled booking URL.

The first is the chat agent described here, where Claude calls the tool during a conversation with a human. The second is a WebMCP tool on the homepage, which lets an AI agent acting on someone's behalf discover and call the booking function directly, without a person ever reading the page. The third is a hosted MCP server, which any MCP-capable client can connect to and call the same function remotely. I wrote up how those agent-discovery surfaces were built separately.

Designing the tool once and exposing it in three places is worth more than any individual surface. The schema is small, the required fields are the minimum that makes the booking useful, and the behavior is identical everywhere. When the booking flow changes, it changes in one place. This matters more as browser-based agents move from preview to general availability, because the visitor who never loads your page still needs a way to transact with you.

The Guardrails That Earn Their Keep

An endpoint that calls a paid model API on behalf of anonymous visitors is an obvious target. The limits that matter are unglamorous:

  • Per-IP rate limiting via a token bucket allowing a burst of 10 requests and refilling at roughly 12 per minute. Enough for a real conversation, not enough to run up a bill.
  • A conversation length cap of 60 messages per request, rejecting anything longer before it reaches the model.
  • A message size cap of 4,000 characters, which stops the obvious prompt-stuffing attempt.
  • A hard ceiling of five agent-loop iterations, so a tool-calling loop cannot run away.
  • Cross-origin access pinned to the site's own domain, so the endpoint is not trivially embeddable elsewhere.
  • Structured JSON logging on every turn, capturing the visitor's message, the reply, tool calls, token counts, and latency.

Validation runs before the model call, not after. Role checks, message shape, and the requirement that the last message came from the user are all enforced at the edge of the handler. It is cheaper to reject a malformed request than to pay for one.

The rate limiter has a known weakness worth naming, since it is the kind of thing that gets quietly omitted from build write-ups. It holds its buckets in memory, per isolate. When Cloudflare recycles an isolate, the counters reset, and a determined abuser could ride that. Moving the state into a durable store fixes it. I have not done that yet, because at current traffic the exposure is a few dollars, and I would rather ship the honest version than describe an architecture I did not build.

Teaching an Agent Your House Style

The agent is a brand surface. It writes in front of prospects, so it needs the same editorial rules a writer would get, and those rules have to be stated with more precision than you would use with a person.

Ours speaks in first person plural, as the company rather than about me in third person. It opens replies without preamble, and the prompt names the specific openers it is banned from using, including "Great question," "I'd be happy to help," and "Absolutely." It never names a client, and uses category descriptions instead. It writes plain text with no dashes of any kind, which required spelling out the em dash, the en dash, and the hyphen used as a dash separately, plus an instruction to write "2 to 3 weeks" rather than the hyphenated form. Vague style guidance does not survive contact with a language model. Enumerated bans do.

The rule that matters most is the last one: if a question is not answered by the grounding document, say so plainly and offer a booking. Do not invent services, prices, or methods. The default failure mode of a language model is a fluent, confident, entirely invented answer, and on a marketing site that failure lands in front of exactly the people whose trust you are trying to earn.

What I Got Wrong

Two things, one of which I found while writing this post.

There is no measurement layer. Every conversation turn logs as structured JSON, which sounds like instrumentation until you notice the logs are streamed and never persisted. No analytics binding, no database, nothing written to durable storage. I can watch conversations live. I cannot answer "how many people used this last month," "which questions did it handle badly," or "did any of this produce a booking." For someone who tells clients that everything ships with measurement attached, shipping a conversion surface with no persisted record of whether it converts is not a defensible oversight. It is the first thing to fix.

The grounding document has had a placeholder in it since launch. The section headed "Who runs PropSaaS Growth" contains a bracketed note to myself to write two or three sentences of bio in my own voice. It is still there, ten weeks later, sitting in the agent's source of truth. The agent handles it correctly, in that its instructions tell it to decline anything the document does not answer, so it declines rather than reading the placeholder aloud. That is the guardrail working. It is also the most-asked question about a consultancy going permanently unanswered by the thing on the page whose job is answering questions.

These two failures share a cause. The launch checklist covered everything needed to make the agent work and nothing needed to know whether it was working. That is a very easy checklist to write by accident, and the absence of the second half is invisible precisely because the first half succeeded.

Should You Put an Agent on Your Site?

Some honest criteria, given the above.

It is worth doing when your answerable surface is small and stable, when there is a single well-defined conversion action a visitor can take, and when you can name the questions that must route to a human instead of being answered. Those three conditions describe most B2B SaaS consultancies and a lot of vertical SaaS companies with considered, sales-led purchases.

Skip it when your product genuinely needs documentation-scale answers, which is a support problem with different economics and different tooling. Skip it when nobody owns the grounding document, because an unmaintained one degrades into a liability that speaks in your brand voice. Skip it when the only reason on the list is that competitors have one.

One expectation to set clearly: this does nothing for AI search visibility. The widget renders client-side after a visitor interacts with it, so no crawler and no AI engine will ever see a word of the conversation. It is a conversion layer for people already on your page. Whether ChatGPT recommends you in the first place is a separate program with separate work behind it, and conflating the two is a common and expensive mistake.

If you want a partner to run the AI-search half of that properly for a PropTech, FinTech, or B2B SaaS site, see our services.

The takeaway

The engineering behind a website AI agent is now a solved, cheap, afternoon-sized problem. A Worker, a model call, one markdown file, and a single tool covers it. What still takes judgment is the product design: naming the questions the agent must refuse, and converting the highest-intent refusals into an action rather than an apology. Get that wrong and you have built a machine that efficiently talks buyers out of contacting you.

The failure I would warn hardest about is the one I walked into. It is entirely possible to ship an agent that works, looks good, and behaves correctly on every edge case you thought to test, while having no way at all to tell whether it earned anything. Decide what you are going to measure before you decide what model to call, because the first question is the one that determines whether any of the rest was worth doing.

Frequently asked questions

Does a website chat agent help my AI search visibility?

No. A chat widget renders client-side after a visitor interacts with it, so crawlers and AI engines never see the conversation. It is a conversion layer for people already on the page, and it does nothing for whether ChatGPT or Perplexity cites you. Treat the two as separate programs with separate measurement.

What does it cost to run an AI agent on a marketing site?

The model calls are the only meaningful cost. On our setup a full visitor conversation of three to five turns runs roughly $0.01 to $0.02 in Anthropic API charges, so 200 conversations a month lands around $4. Edge compute at marketing-site traffic volumes is a rounding error. Caching the system prompt is what keeps the per-turn cost flat as the grounding document grows.

Do I need a vector database for a website AI agent?

Almost certainly not. Retrieval solves the problem of selecting from more context than fits in a prompt. A marketing site's answerable surface is small, stable, and already written down, so the whole thing fits in the system prompt with room to spare. Reach for retrieval when the corpus is genuinely too large or changes faster than a person can maintain it.

What happens when a visitor asks something the grounding document does not cover?

The agent says so plainly and offers a booking. That behavior has to be written into the system prompt as an explicit instruction, because the default failure mode of a language model is a confident, plausible, invented answer. Declining is a feature you specify, not a property you inherit.

Is a chat agent the same thing as WebMCP?

No, and they serve different visitors. A chat agent talks to a human who is reading your site. WebMCP exposes a tool to an AI agent acting on a person's behalf, so the visitor may never load your page at all. They can share one tool definition, which is what we do, but shipping one does not give you the other.

How do you stop a website agent from inventing prices?

Two layers. The grounding document contains no prices, so there is nothing accurate to leak. The system prompt then forbids quoting a price, fee, range, or duration under any circumstances, including cases where the source material mentions numbers. Removing the information is not enough on its own, because a model will still estimate if it is not told to refuse.

Gemma Smith

Gemma Smith, Founder, PropSaaS Growth

SEO, AEO, and content strategy for PropTech, FinTech, and B2B SaaS companies. 10+ years in PropTech. Active engagements with vertical SaaS platforms. AirOps Champion.