Cookbook
Agents & skills · for AI agents

Configure a page playbook (page-aware chat opener)

Give the agent a per-page briefing — background, an opening line and suggested questions — chosen automatically from the visitor's URL, over MCP.

MCP tools:list_page_contextsupsert_page_contextresolve_page_contextpage_context_stats

Principle — the opener is the only sentence guaranteed to be read. A generic "How can I help?" converts a page visit into nothing. A playbook lets the same agent open differently on each page, already knowing where the visitor is. More in Page playbooks.

A page playbook is a small briefing attached to a URL pattern. It has three parts:

  • context — private background for the agent, not shown to the visitor (who lands on this page, what they're deciding, what they usually worry about). Write background, not facts: prices, quotas and policy belong in a knowledge base, which outranks it.
  • greeting — the opening line the visitor actually sees.
  • questions — up to four suggested questions, so a visitor who hasn't formed one can just pick.

The right playbook is chosen from the URL: resolution order is explicit key → url_pattern → default. A single default catches every unmatched page, so nothing ever falls back to a blank box.

1. See what's already there

list_page_contexts()   # existing playbooks: match rules, greeting mode, position, which is default

2. Create or replace one

upsert_page_context(
  key="pricing",
  label="Pricing page",
  url_pattern="*/pricing",           # glob, path-only; ignores query string and trailing slash
  context="Visitors here are comparing plans and worrying about overage. "
          "They are usually the person who will sign off on the cost. "
          "Do not quote custom pricing in chat.",
  greeting="You're looking at our plans — want me to work out where overage would start for your volume?",
  questions=["What's included in the free plan?", "How is overage billed?", "Can I change plans later?"],
  greeting_mode="generated",         # generate opener + questions in the visitor's language (recommended)
  is_default=False,
)

greeting_mode="generated" writes the opener and questions per visitor language on the fly — so a Spanish visitor gets a Spanish greeting without you authoring five versions. Use "static" only when you want your exact greeting/questions verbatim.

3. Verify the match — always

resolve_page_context(url="https://acme.com/pricing?ref=x")   # → which playbook this URL hits, and how

Globs are easy to get subtly wrong (a missing *, one path level too many). A mismatch has no error message — the visitor just silently gets the default playbook. So after writing any url_pattern, resolve a real URL and confirm matched_by is url_pattern, not default.

4. Set a catch-all default

upsert_page_context(
  key="default",
  label="Everywhere else",
  context="General visitor to the site; intent unknown. Ask what brought them in before assuming.",
  greeting_mode="generated",
  is_default=True,                    # at most one default per tenant; setting this unsets any other
)

upsert_page_context is a full replace, not a patch: fields you omit fall back to defaults rather than keeping their old value. To change one field, list_page_contexts() first, merge, then upsert.

Once playbooks have been live for a while, page_context_stats() shows which ones get opened and which suggested questions get clicked — so you tune copy from data, not guesses.

Report back. Give the user the console link to review and edit these — https://console.agent4.io/#/page-contexts — and confirm each url_pattern resolves the way they expect (step 3). The chat widget needs no code change for a static site; single-page apps that have no distinct URL per view can name a playbook by its key instead.

Openings that collect on the spot

If a playbook's context explicitly instructs presenting a form at first contact — e.g. "PRESENT THE BOOKING FORM IN YOUR GREETING/OPENING: (1) preferred channel (phone / email — single), (2) phone or email (text)" — the generated opening will include that interactive form, and the starter questions are suppressed (the form is the guidance). Submitting counts as a normal visitor message, so save_contact / schedule_followup fire as they would mid-conversation.

Use it for intents where asking first is the whole point — "book a callback", "leave a message" — and keep it to 2–3 fields. For intents where the visitor wants answers first (support, troubleshooting), let the conversation start normally and collect in the first reply instead.

Records require a contact — by design

Every record-creating tool (submit_lead, escalate, request_booking, open_checklist) rejects the call unless contact.email or contact.phone is filled in — a record nobody can follow up on is noise, not a lead. The rejection message tells the agent what to do (ask for a contact first, never invent one), so make your playbooks collect email/phone up front — the greeting-time forms above exist precisely for this.

File form submissions deterministically

For intake-style playbooks (bookings, leads, complaints), append a machine marker to the context: [[inbox:submit_lead]], [[inbox:escalate]] or [[inbox:request_booking]]. Form submissions on that playbook are then filed by the platform before the model replies — the reply's reference is guaranteed to be the filed record's reference, and the contact is saved alongside. Keep the prose instructions too: they cover the free-conversation path (details given without the form), where the model still does the filing.