> ## Documentation Index
> Fetch the complete documentation index at: https://polyai-mintlify-665e356f.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Tutorial: Add a simple topic

> PolyAcademy Level 1 – Create a simple FAQ-style Managed Topic with good naming, sample questions, and speakable content.

export const LessonMeta = ({level, difficulty, time}) => {
  const levelConfig = {
    1: {
      badge: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
      label: 'Level 1'
    },
    2: {
      badge: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200',
      label: 'Level 2'
    },
    3: {
      badge: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
      label: 'Level 3'
    }
  };
  const difficultyConfig = {
    Beginner: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
    Intermediate: 'bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200',
    Advanced: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
  };
  const lvl = levelConfig[level] || levelConfig[1];
  const diffColor = difficultyConfig[difficulty] || difficultyConfig['Beginner'];
  return <div className="flex flex-wrap items-center gap-2 my-4 not-prose">
      <span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${lvl.badge}`}>
        {lvl.label}
      </span>
      <span className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-semibold ${diffColor}`}>
        {difficulty}
      </span>
      {time && <span className="inline-flex items-center gap-1 text-xs text-gray-500 dark:text-gray-400">
          <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z" />
          </svg>
          {time}
        </span>}
    </div>;
};

export const FillBlank = ({prompt, answer, hint, explanation}) => {
  const [value, setValue] = useState('');
  const [submitted, setSubmitted] = useState(false);
  const normalize = s => s.trim().toLowerCase().replace(/[^a-z0-9_]/g, '');
  const answers = Array.isArray(answer) ? answer : [answer];
  const isCorrect = answers.some(a => normalize(value) === normalize(a));
  const handleSubmit = e => {
    e.preventDefault();
    if (value.trim()) setSubmitted(true);
  };
  const handleReset = () => {
    setValue('');
    setSubmitted(false);
  };
  return <div className="my-6">
      <p className="mt-0 mb-3 text-sm font-semibold leading-relaxed text-gray-900 dark:text-gray-100">
        {prompt}
      </p>
      <form onSubmit={handleSubmit} className="flex flex-col gap-2.5">
        <div className="flex gap-2">
          <input type="text" value={value} onChange={e => {
    setValue(e.target.value);
    setSubmitted(false);
  }} placeholder={hint || "Type your answer…"} className="flex-1 rounded-xl border py-2.5 px-4 text-sm font-mono border-gray-200 bg-white text-gray-900 placeholder-gray-400 outline-none focus:border-gray-400 focus:ring-2 focus:ring-gray-200 transition-all duration-150 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:placeholder-gray-600 dark:focus:border-gray-500 dark:focus:ring-gray-700" />
          <button type="submit" className="rounded-xl border py-2.5 px-5 text-sm font-medium transition-all duration-150 border-gray-800 bg-gray-800 text-white hover:bg-gray-700 hover:border-gray-700 dark:border-gray-200 dark:bg-gray-200 dark:text-gray-900 dark:hover:bg-white">
            Check
          </button>
        </div>
        {submitted ? <div className={`py-3 pl-4 pr-3.5 rounded-r-xl text-sm leading-relaxed border-l-4 ${isCorrect ? 'border-green-500 bg-green-50 dark:bg-green-900 dark:border-green-500' : 'border-red-500 bg-red-50 dark:bg-red-900 dark:border-red-500'}`}>
            {isCorrect ? <>
                <span className={`font-semibold !text-green-800 dark:!text-green-200`}>Correct.</span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">{explanation}</span>
              </> : <>
                <span className="font-semibold !text-red-800 dark:!text-red-200">Not quite.</span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">The answer is <code className="!text-gray-800 dark:!text-gray-200">{answers[0]}</code>. {explanation}</span>
              </>}
          </div> : null}
      </form>
      {submitted ? <button type="button" onClick={handleReset} className="mt-2 text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 underline underline-offset-2 cursor-pointer transition-colors duration-150">
          Try again
        </button> : null}
    </div>;
};

export const Quiz = ({questions = []}) => {
  const [selected, setSelected] = useState({});
  const [resetCount, setResetCount] = useState(0);
  const letters = ['A', 'B', 'C', 'D'];
  const handleSelect = (qIdx, optIdx) => {
    if (selected[qIdx] !== undefined) return;
    setSelected(prev => ({
      ...prev,
      [qIdx]: optIdx
    }));
  };
  const handleReset = () => {
    setSelected({});
    setResetCount(c => c + 1);
  };
  if (!questions?.length) return null;
  const getOptionClasses = ({hasAnswered, isThisCorrect, isThisSelected}) => {
    if (!hasAnswered) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-pointer border-gray-200 bg-white text-gray-700 hover:border-gray-300 hover:bg-gray-50 hover:shadow-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:hover:border-gray-500 dark:hover:bg-gray-700',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-300',
        icon: null
      };
    }
    if (isThisCorrect) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-green-400 bg-green-50 text-green-900 font-medium dark:border-green-500 dark:bg-green-950 dark:text-green-100',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-green-500 text-white dark:bg-green-500',
        icon: <svg className="shrink-0 w-4 h-4 text-green-500 dark:text-green-400 ml-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
          </svg>
      };
    }
    if (isThisSelected) {
      return {
        btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-red-400 bg-red-50 text-red-900 dark:border-red-500 dark:bg-red-950 dark:text-red-100',
        badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-red-500 text-white dark:bg-red-500',
        icon: <svg className="shrink-0 w-4 h-4 text-red-400 dark:text-red-400 ml-auto" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2.5}>
            <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
          </svg>
      };
    }
    return {
      btn: 'flex w-full items-center gap-3 py-2.5 px-4 rounded-xl text-sm leading-normal transition-all duration-150 text-left border cursor-default border-gray-100 bg-white text-gray-400 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-500',
      badge: 'w-6 h-6 rounded-full text-xs font-bold flex items-center justify-center shrink-0 leading-none transition-all duration-150 bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-500',
      icon: null
    };
  };
  return <div key={resetCount} className="my-6">
      {questions.map((q, qIdx) => {
    const answer = selected[qIdx];
    const hasAnswered = answer !== undefined;
    const isCorrect = answer === q.correct;
    return <div key={String(qIdx)} className="mb-8">
            <p className="flex items-start gap-2.5 font-semibold text-sm mb-3 mt-0 leading-relaxed text-gray-900 dark:text-gray-100">
              <span className="inline-flex items-center justify-center w-5 h-5 rounded-full bg-gray-800 dark:bg-gray-200 text-white dark:text-gray-900 text-xs font-bold shrink-0 mt-px leading-none">
                {qIdx + 1}
              </span>
              {q.q}
            </p>

            <div className="flex flex-col gap-2">
              {q.options.map((opt, i) => {
      const isThisCorrect = i === q.correct;
      const isThisSelected = i === answer;
      const {btn, badge, icon} = getOptionClasses({
        hasAnswered,
        isThisCorrect,
        isThisSelected
      });
      return <button key={String(i)} type="button" onClick={() => handleSelect(qIdx, i)} className={btn}>
                    <span className={badge}>{letters[i]}</span>
                    <span className="flex-1">{opt}</span>
                    {icon}
                  </button>;
    })}
            </div>

            {hasAnswered ? <div className={`mt-3 py-3 pl-4 pr-3.5 rounded-r-xl text-sm leading-relaxed border-l-4 ${isCorrect ? 'border-green-500 bg-green-50 dark:bg-green-950 dark:border-green-500' : 'border-red-500 bg-red-50 dark:bg-red-950 dark:border-red-500'}`}>
                <span className={`font-semibold ${isCorrect ? '!text-green-800 dark:!text-green-200' : '!text-red-800 dark:!text-red-200'}`}>
                  {isCorrect ? 'Correct.' : 'Not quite.'}
                </span>{' '}
                <span className="!text-gray-700 dark:!text-gray-300">{q.explanation}</span>
              </div> : null}
          </div>;
  })}

      <button type="button" onClick={handleReset} className="mt-1 text-xs text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 underline underline-offset-2 cursor-pointer transition-colors duration-150">
        Reset quiz
      </button>
    </div>;
};

export const ProgressTracker = ({lessonNum, totalLessons, level}) => {
  const [checked, setChecked] = useState(false);
  return <div onClick={() => setChecked(prev => !prev)} className={checked ? 'flex items-center gap-3 p-4 rounded-lg border-2 border-green-600 bg-green-50 dark:bg-green-950 cursor-pointer select-none transition-all' : 'flex items-center gap-3 p-4 rounded-lg border-2 border-gray-200 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 cursor-pointer select-none transition-all'}>
      <div className={checked ? 'w-5 h-5 rounded border-2 border-green-600 bg-green-600 flex items-center justify-center shrink-0 transition-all' : 'w-5 h-5 rounded border-2 border-gray-400 dark:border-gray-500 bg-white dark:bg-gray-800 flex items-center justify-center shrink-0 transition-all'}>
        {checked ? <svg width="10" height="8" viewBox="0 0 10 8" fill="none">
            <path d="M1 4L3.5 6.5L9 1" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
          </svg> : null}
      </div>
      <div>
        <div className={checked ? 'font-semibold text-sm text-green-700 dark:text-green-300' : 'font-semibold text-sm text-gray-700 dark:text-gray-200'}>
          {checked ? 'Lesson complete' : 'Mark lesson complete'}
        </div>
        {lessonNum && totalLessons ? <div className="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
            {level ? level + ' - ' : ''}Lesson {lessonNum} of {totalLessons}
          </div> : null}
      </div>
    </div>;
};

<Info>
  **Lesson 3 of 6** – Time to teach your agent something useful. You'll create a simple topic that answers one question – like "what time do you close?" – in both Chat and Call.
</Info>

<LessonMeta level={1} difficulty="Beginner" time="10 min" />

A [Managed Topic](/managed-topics/introduction) is how your agent learns to answer questions. In this lesson, you'll create the simplest kind: one question, one answer, no follow-ups.

<img src="https://mintcdn.com/polyai-mintlify-665e356f/ITmGFFs_1FlEHv25/images/academy/simple-kb.png?fit=max&auto=format&n=ITmGFFs_1FlEHv25&q=85&s=802ef51ddfe730bc8e2d66a3c2e9e75f" alt="overview" width="1284" height="1172" data-path="images/academy/simple-kb.png" />

## What "simple" means

<Tabs>
  <Tab title="Simple topics">
    * Completes in **one turn**
    * Requires **no clarification**
    * Triggers **no actions**
    * Works the same way in **Chat and Call**
  </Tab>

  <Tab title="Not simple">
    * Combine multiple questions
    * Requires multiple intents
    * Offer SMS, handoff, or routing
    * Ask the user a follow-up question
  </Tab>
</Tabs>

<Note>
  If a topic needs any of those features, it should be implemented as a [complex topic](/learn/guides/advanced/add-complex-kb-topic) instead. These will be covered in more detail in **Level 2: Advanced** of the PolyAcademy.
</Note>

## Is your idea a simple or complex topic?

Use this decision tree before writing anything:

```mermaid theme={null}
flowchart TD
    A[New topic idea] --> B{Completes in one turn?}
    B -->|No| C[Complex topic]
    B -->|Yes| D{Needs actions?}
    D -->|Yes – SMS, handoff, routing| C
    D -->|No| E{Needs clarification?}
    E -->|Yes – asks user a question| C
    E -->|No| F[Simple topic]
    C --> G[See Level 2: Complex topics]
```

## Check your understanding

<Quiz
  questions={[
{
q: "What defines a simple Managed Topic?",
options: [
  "It uses at least one action like SMS or handoff",
  "It asks the user a follow-up question to clarify intent",
  "It completes in one turn, needs no actions, and requires no clarification",
  "It combines multiple related questions into a single response",
],
correct: 2,
explanation: "A simple Managed Topic completes in one turn, triggers no actions (no SMS, handoff, or routing), and requires no clarification from the user.",
}
]}
/>

## Before you start: define your intent

Answer these questions before creating the topic:

* What is the exact question being answered?
* Can a human answer it in one short response?
* Can users reasonably phrase this question in several different ways?

If the topic answers more than one question, split it.

**Example: single intent**

* "What time do you close?"

**Example: multiple intents**

* "What time do you close and do you have holiday hours?"

These should be two separate topics.

### Step 1: Choose a precise topic name

The topic name is the strongest signal for retrieval.

Good topic names:

* Describe one intent
* Are specific, not general
* Use `snake_case`

**Examples**

* `closing_time`
* `parking_cost`
* `pet_policy`

Avoid names that are broad or vague.

**Avoid**

* `info`
* `general`
* `shop_questions`
* `misc`

### Step 2: Write sample questions

Sample questions teach the retriever how users phrase requests. As a baseline, aim for **at least 3** sample questions per topic. If the topic is broad or users phrase it in many different ways, include more – up to 20.

Add variations that reflect real speech. Include:

* Short questions
* Polite phrasing
* Informal or incomplete phrasing
* Call-style filler words
* Different sentence lengths

**Example: `closing_time` sample questions**

* what time is closing
* closing time
* when do we have to leave
* when do you shut
* what time do we need to be out by
* uh what's the closing time

<Tip>
  Focus on **variety in language and structure**, not slight rewordings. Think about the different ways real users might describe the same issue – whether they are confused, specific, vague, or using non-standard terminology.
</Tip>

Avoid:

* Repeating the same sentence with small wording changes
* Writing marketing-style language
* Including answers

### How retrieval works

The retriever compares the user's utterance to the topic **Name**, **Sample questions**, and **Content** to find the closest semantic matches. The top matches are then sent to the LLM, which makes the final decision on which topic to use.

These signals are not weighted equally. The **topic name** is the strongest retrieval signal, followed by **sample questions**, then **content**. This means a precise topic name has more impact on retrieval than a large number of sample questions.

Because the **Name** is shown to the retriever, it should be semantically close to the intent. A vague name like `general_behavior-payment` may trigger on any payment-related utterance instead of only the specific payment issue you intended.

### Step 3: Write the Content response

The Content field is the agent's full response.

It should be:

* Short
* Direct
* Easy to understand when spoken
* Complete on its own

Use this structure:

1. State the answer clearly
2. Optionally mention one next step
3. Stop

**Example: `closing_time` content**

```text theme={null}
We open at 9am. Closing time is at 6 p.m.

If you'd like to hire the store for a late event, I can help with that.
```

This works because:

* The answer comes first
* The response ends cleanly
* No action is triggered

Avoid:

* Explanations or justification
* Policy language
* Multiple offers or options

**Avoid**

```text theme={null}
Closing time is at 6 p.m., which allows our cleaning team to prepare the store for the next day.
```

This reads well on a page but performs poorly.

### Step 4: For now, leave Actions empty

For a simple Managed Topic:

* Do not add Actions
* Do not reference functions
* Do not trigger handoff or SMS

If an action is required, the topic is no longer simple and should be rewritten as a [complex topic](/learn/guides/advanced/add-complex-kb-topic).

## Common simple Managed Topic patterns

<AccordionGroup>
  <Accordion title="Static information" icon="clock">
    **Topic name:** `opening_hours`

    **Content:**

    ```text theme={null}
    We are open daily from 7 a.m. to 6 p.m.
    ```
  </Accordion>

  <Accordion title="Policy statement" icon="shield-check">
    **Topic name:** `pet_policy`

    **Content:**

    ```text theme={null}
    Guide dogs and other service animals are allowed in the store. Other pets are not allowed.
    ```
  </Accordion>

  <Accordion title="Location information" icon="location-dot">
    **Topic name:** `parking_location`

    **Content:**

    ```text theme={null}
    Parking is available in the structure beneath the mall entrance.
    ```
  </Accordion>

  <Accordion title="Price reference" icon="dollar-sign">
    **Topic name:** `parking_cost`

    **Content:**

    ```text theme={null}
    Self-parking is free on weekdays and $5 an hour on weekends between 9am and 5pm.
    ```
  </Accordion>
</AccordionGroup>

## Check your understanding

<Quiz
  questions={[
{
q: "What is the strongest signal for Managed Topic retrieval?",
options: [
  "The sample questions list",
  "The topic content or answer text",
  "The topic name",
  "The topic description field",
],
correct: 2,
explanation: "The topic name is the strongest signal – it should describe exactly one intent, be specific rather than generic, and use snake_case. Sample questions come second, then content.",
}
]}
/>

## Verification

### Test in Chat

Ask the question using:

* Exact phrasing
* Informal phrasing
* Polite phrasing
* Abrupt phrasing

Confirm:

* The same topic triggers every time
* The response does not change
* No follow-up question is asked

Live, in the test panel, look for the **topic citations** to confirm which topic was recalled by the agent.

<img src="https://mintcdn.com/polyai-mintlify-665e356f/ITmGFFs_1FlEHv25/images/academy/live-chat-citations.png?fit=max&auto=format&n=ITmGFFs_1FlEHv25&q=85&s=847b30850df8bf24b30c5c09dfb4faf7" alt="overview" width="598" height="902" data-path="images/academy/live-chat-citations.png" />

### Test in Call

Ask the same question out loud, including hesitation or filler words.

Confirm:

* Speech is transcribed correctly
* The response sounds natural when spoken
* The agent does not over-explain

In **conversation review**, make sure topic citations are enabled:

<img src="https://mintcdn.com/polyai-mintlify-665e356f/ITmGFFs_1FlEHv25/images/academy/conv-review-citations.png?fit=max&auto=format&n=ITmGFFs_1FlEHv25&q=85&s=2c1e0d0ce80254017d31b98d031d6099" alt="overview" width="3014" height="1286" data-path="images/academy/conv-review-citations.png" />

## Final checklist

Before moving on, confirm:

* The topic answers exactly one question
* Sample questions reflect real user phrasing
* Content is short and speakable
* Actions are empty
* The topic behaves the same in Chat and Call

## Try it yourself

<Steps>
  <Step title="Challenge: Write a pet_policy topic">
    Write a complete simple topic for a store that allows only service animals.

    Include:

    1. Topic name (snake\_case)
    2. Five sample questions
    3. Content (one to two sentences)

    <Accordion title="Hint">
      The topic name should describe exactly one intent. Sample questions should reflect how real users speak – including informal and spoken phrasing. Content should be speakable and complete in one or two sentences.
    </Accordion>

    <Accordion title="Example solution">
      **Topic name:** `pet_policy`

      **Sample questions:**

      * are pets allowed
      * can I bring my dog
      * do you allow animals
      * is my cat allowed inside
      * what's your policy on pets

      **Content:**

      ```text theme={null}
      Service animals are welcome in the store. Other pets are not allowed inside.
      ```
    </Accordion>
  </Step>
</Steps>

## Check your understanding

<Quiz
  questions={[
{
q: "Should a simple Managed Topic include Actions?",
options: [
  "Yes – always add at least one action as a fallback",
  "Only if the user explicitly requests it",
  "Yes – SMS and handoff actions are always safe to include",
  "No – if an action is needed, the topic is no longer simple",
],
correct: 3,
explanation: "If an action is required (SMS, handoff, routing), the topic is no longer simple and should be rewritten as a complex topic.",
}
]}
/>

<FillBlank prompt="A Managed Topic that answers one question in one turn, requires no clarification, and triggers no actions is called a _____ topic." answer={["simple", "Simple"]} hint="The opposite of complex — it's the most straightforward kind." explanation="Simple topics complete in one turn with no follow-up questions or actions. As soon as a topic needs SMS, a handoff, routing, or clarification from the user, it becomes a complex topic — covered in Level 2." />

## Go deeper

Want the full reference for Managed Topics? These pages cover everything – including the more complex topics you'll learn in Level 2:

<CardGroup cols={2}>
  <Card title="Managed Topics" icon="book-open" href="/managed-topics/introduction">
    Complete reference for creating and managing knowledge topics
  </Card>

  <Card title="Level 2: Complex topics" icon="graduation-cap" href="/learn/guides/advanced/add-complex-kb-topic">
    Multi-turn topics with actions, SMS, and handoffs (coming in Level 2)
  </Card>
</CardGroup>

***

<CardGroup cols={2}>
  <Card title="← Previous: Edit agent behavior" icon="arrow-left" href="/learn/guides/get-started/edit-agent-behavior">
    Lesson 2 of 6
  </Card>

  <Card title="Next: Edit voice settings →" icon="arrow-right" href="/learn/guides/get-started/edit-voice-settings">
    Lesson 4 – choose how your agent sounds
  </Card>
</CardGroup>

<ProgressTracker lessonKey="l1-3-add-topic" lessonNum={3} totalLessons={6} level="Level 1" />
