Build your agent
Curated answers & CTAs
Curated answers and CTAs are the two kinds of "deterministic" behavior the widget supports — they bypass the LLM in favor of exact text or structured calls-to-action. Use them where you can't tolerate the model paraphrasing or going off-script.
When to use curated answers
Curated answers are for questions where the response has to be exact:
- Pricing — visitors want numbers, not "around $99/month".
- Legal / compliance — return policy, GDPR statement, contract terms.
- Routing — "how do I contact you?" where you want to push a specific URL.
- Brand-critical FAQs — your highest-stakes 5-10 questions.
Authoring
From /app/agents/{id}/curated:
- Question pattern (
question_patterncolumn) — one or more trigger keywords. Use commas to split a single field into independent OR-tokens (pricing, refund, web design). The matcher splits on commas, lowercases each token, and short-circuits the LLM if ANY token appears as a substring of the visitor's message. Case-insensitive. Add variants (price, pricing, how much) so misspellings still hit. - Answer — the exact text to stream back. Markdown is supported (bold, links, lists). Anchor markdown links inside the answer if you want clickable citations — there is no dedicated citation_url column.
- Priority — higher wins when multiple curated entries match the same input.
- Slug + KB title + KB published — toggle the row into a public Knowledge Base article. When
kb_published = truethe answer renders at/kb/{workspace.slug}/{slug}and the bot can recommend it mid-chat via thesend_kb_articletool. - Lang — optional ISO code so a single agent can serve curated answers per language.
- Enabled — boolean toggle. Lets you author then publish, or pause a curated row without deleting it.
- Conditions (
conditionsJSON) — optional matching constraints (e.g.page_url_prefix,visitor_lang) layered on top of the question pattern.
How matching works
Before the RAG pipeline runs, the message goes through
CuratedAnswerMatcher. If any trigger matches, the curated
text is returned and we never call retrieval or the LLM. That makes
curated answers fast — usually under 100ms end-to-end — and
cheap (no inference cost).
The streaming behavior matches the LLM's: tokens stream out one at a time over a small interval so the visitor sees the same typing animation. They have no way to tell a curated answer from a generated one.
CTAs
A CTA is a card the visitor can click — a button, a link, or both — rendered inline in the chat panel. Each CTA has:
| Field | Purpose |
|---|---|
title | One-line headline. |
description | Optional supporting text. |
buttons | 1 or 2 buttons. Each has a label and an action (URL, send_message, lead_capture, dismiss). |
conditions | When to show. Same shape as behavior-rule conditions. |
Common CTA patterns
- Pricing reveal — when visitor asks about cost, show a card with "Compare plans" / "Talk to sales" buttons.
- Demo upsell — after 3 turns of product Q&A, show "Book a 15-min demo".
- Exit intent rescue — when cursor leaves toward the address bar, show "Before you go — quick question?" with a one-click lead form.
- Scroll-deep nudge — at 80% page scroll, offer "See it in action" → demo URL.
Editing
/app/agents/{id}/ctas is the management page. The form is
a structured builder — you don't need to write JSON. Save creates or
updates a behavior rule with kind=cta. Disable to keep the
rule but stop showing it.
pending_curated_match in the
conversation debugger).
A language tag on an answer prefers it for conversations in that language — it never excludes. The conversation's language comes from the visitor's browser locale, which regularly disagrees with the language they actually type (a Dutch visitor on an English-configured browser), so keywords do the real selecting and the language tag only breaks ties when the same keyword is pinned in several languages.
Testing CTAs and curated answers
The live preview on the agent's Customize page — and the same widget
floating on every other page of the agent — is your sandbox. It is the
real widget, but its conversation carries is_playground=true,
so it doesn't count against your monthly quota and stays out of the
analytics report.
Type a trigger phrase and confirm the curated answer fires. Type something close-but-not-quite and confirm it doesn't (otherwise your triggers are too loose).