> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usefini.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from a legacy chatbot or IVR to an AI agent

> A phased playbook for replacing a scripted chatbot or phone menu with an AI agent: inventory and classify old flows, keep what works, run in parallel, cut over by segment and plan the rollback.

export const StepExplorer = ({title, steps = [], hint = "Click a step, or use Next."}) => {
  const FV = {
    lime: "#C3EE5E",
    ink: "#131415",
    line: "rgba(127,127,127,0.28)",
    soft: "rgba(127,127,127,0.07)",
    softer: "rgba(127,127,127,0.04)",
    muted: "rgba(127,127,127,0.95)",
    pass: "#C3EE5E",
    warn: "#FFB020",
    fail: "#FF4D4D",
    radius: 14
  };
  const fvCard = {
    border: `1px solid ${FV.line}`,
    borderRadius: FV.radius,
    padding: 18,
    margin: "20px 0",
    background: FV.softer
  };
  const fvChip = active => ({
    border: `1px solid ${active ? FV.lime : FV.line}`,
    background: active ? FV.lime : "transparent",
    color: active ? FV.ink : "inherit",
    borderRadius: 999,
    padding: "6px 12px",
    fontSize: 13,
    fontWeight: 600,
    cursor: "pointer",
    lineHeight: 1.2
  });
  const fvBtn = primary => ({
    border: `1px solid ${primary ? FV.lime : FV.line}`,
    background: primary ? FV.lime : "transparent",
    color: primary ? FV.ink : "inherit",
    borderRadius: 10,
    padding: "7px 14px",
    fontSize: 13,
    fontWeight: 600,
    cursor: "pointer"
  });
  const fvLabel = {
    fontSize: 11,
    fontWeight: 700,
    letterSpacing: "0.08em",
    textTransform: "uppercase",
    opacity: 0.6,
    marginBottom: 8
  };
  const [i, setI] = useState(0);
  const s = steps[i] || ({});
  return <div style={fvCard}>
      {title && <div style={fvLabel}>{title}</div>}
      <div style={{
    display: "flex",
    alignItems: "center",
    gap: 0,
    overflowX: "auto",
    paddingBottom: 6
  }}>
        {steps.map((st, k) => <div key={k} style={{
    display: "flex",
    alignItems: "center",
    flex: k === steps.length - 1 ? "0 0 auto" : "1 0 auto"
  }}>
            <button onClick={() => setI(k)} title={st.title} style={{
    width: 34,
    height: 34,
    borderRadius: 999,
    flex: "0 0 auto",
    cursor: "pointer",
    fontWeight: 700,
    fontSize: 13,
    border: `2px solid ${k <= i ? FV.lime : FV.line}`,
    background: k === i ? FV.lime : k < i ? "rgba(195,238,94,0.25)" : "transparent",
    color: k === i ? FV.ink : "inherit"
  }}>{k + 1}</button>
            {k < steps.length - 1 && <div style={{
    height: 2,
    minWidth: 18,
    flex: 1,
    background: k < i ? FV.lime : FV.line,
    margin: "0 4px"
  }} />}
          </div>)}
      </div>
      <div style={{
    display: "flex",
    gap: 6,
    flexWrap: "wrap",
    margin: "6px 0 14px"
  }}>
        {steps.map((st, k) => <span key={k} onClick={() => setI(k)} style={{
    fontSize: 12,
    cursor: "pointer",
    opacity: k === i ? 1 : 0.55,
    fontWeight: k === i ? 700 : 500,
    marginRight: 8
  }}>{st.label}</span>)}
      </div>
      <div style={{
    border: `1px solid ${FV.line}`,
    borderRadius: 12,
    padding: 16,
    background: FV.soft,
    minHeight: 120
  }}>
        {s.meta && <div style={{
    ...fvLabel,
    opacity: 0.75
  }}>{s.meta}</div>}
        <div style={{
    fontSize: 17,
    fontWeight: 700,
    marginBottom: 6
  }}>{s.title}</div>
        {s.body && <div style={{
    fontSize: 14.5,
    lineHeight: 1.6,
    opacity: 0.9
  }}>{s.body}</div>}
        {s.points && <ul style={{
    margin: "10px 0 0",
    paddingLeft: 18,
    fontSize: 14,
    lineHeight: 1.6
  }}>
            {s.points.map((p, k) => <li key={k}>{p}</li>)}
          </ul>}
      </div>
      <div style={{
    display: "flex",
    justifyContent: "space-between",
    alignItems: "center",
    marginTop: 12,
    gap: 8
  }}>
        <span style={{
    fontSize: 12,
    opacity: 0.6
  }}>{hint}</span>
        <div style={{
    display: "flex",
    gap: 8
  }}>
          <button style={fvBtn(false)} disabled={i === 0} onClick={() => setI(v => Math.max(0, v - 1))}>Back</button>
          <button style={fvBtn(true)} onClick={() => setI(v => (v + 1) % Math.max(1, steps.length))}>{i === steps.length - 1 ? "Start over" : "Next"}</button>
        </div>
      </div>
    </div>;
};

To migrate from a legacy chatbot or IVR to an AI agent, inventory every flow and menu option, classify each one as a knowledge answer, a workflow with actions, a route to a human, or something to retire, and rebuild only what earns its place. Then run the AI agent in parallel without customer-facing replies, cut over one channel or customer segment at a time, and keep a tested rollback for every slice until the old system is switched off.

This playbook works for any AI support agent and for both chat and phone. The biggest risk in these projects is not the new agent: it is losing something the old system did quietly, such as an authentication step, a routing rule or a tag a report depends on.

## Why migrations go wrong

Scripted bots and phone menus encode years of decisions. Some are real policy ("callers about fraud go straight to the fraud team"), some are workarounds for the old system's limits ("press 4 to repeat the menu"), and some nobody remembers the reason for. Teams that rebuild everything copy the workarounds into the new agent. Teams that rebuild nothing lose the policy. The work is telling them apart.

## The phases

<StepExplorer
  title="Click through the migration"
  steps={[
{ label: "Inventory", meta: "Phase 1", title: "List every flow, menu option and integration", body: "Capture what each flow does, which systems it touches, how it ends and how much traffic it carries.", points: ["Export flows and menu trees from the old system", "Include after-hours and holiday variants", "Note every report, tag or routing rule that depends on the old system"] },
{ label: "Classify", meta: "Phase 2", title: "Give each flow a destination", body: "Each flow becomes a knowledge answer, a workflow with actions, a route to a human, or is retired.", points: ["Use the mapping table and flowchart below", "Mark flows that encode policy, even if they look trivial"] },
{ label: "Rebuild", meta: "Phase 3", title: "Build knowledge, workflows and routing", body: "Write the knowledge first, then rebuild workflows that call your systems, then the routing rules.", points: ["Keep authentication and routing that work", "Point write actions at a sandbox until cutover"] },
{ label: "Parallel run", meta: "Phase 4", title: "Run the AI agent without customer-facing replies", body: "The agent drafts what it would have said while the old system keeps serving customers. Compare and fix.", points: ["Review drafted answers against what actually happened", "Turn real conversations into regression test cases"] },
{ label: "Phased cutover", meta: "Phase 5", title: "Switch one channel or segment at a time", body: "Turn off the old system for one slice, turn on the AI agent there, and watch closely before expanding.", points: ["Only one system answers on any slice", "Each slice has a written rollback"] },
{ label: "Retire", meta: "Phase 6", title: "Switch off the old system", body: "Once every slice is stable, turn off the old bot or menu and clean up what depended on it.", points: ["Repoint reports, tags and triggers", "Archive the old flows for reference"] }
]}
/>

## Phase 1: Inventory old flows and menus

Export or document every chatbot flow and every IVR menu path. For each one, record:

| Field | Example |
| - | - |
| Flow or menu path | Example: Main menu → 2 Billing → 1 Refund status |
| What it does | Asks for order number, looks up refund status, reads it back |
| Systems touched | Order system (read) |
| How it ends | Answer given, or transfer to billing queue |
| Monthly volume | From your bot or telephony reports |
| Containment or completion | How often it ends without a transfer, if your reports show it |
| Depends on it | Billing queue routing, "refund-status" tag used in a weekly report |

Sort by volume. A small number of flows usually carries most of the traffic, and those deserve the most care. Do not skip flows that transfer immediately: an option that sends callers straight to a specialist team often encodes a policy decision.

## Phase 2: Classify each flow

Every flow gets exactly one destination.

| Destination | Use it when | Example |
| - | - | - |
| **Knowledge answer** | The flow only answers a question from fixed content. | "What are your opening hours?", "How do I reset my password?" |
| **Workflow with actions** | The flow collects details and reads from or writes to a system, with steps that must run the same way every time. | Refund status lookup, address change, card freeze |
| **Route to human** | Policy says a person handles it, or the stakes are too high for automation today. | Fraud reports, bereavement, complaints, legal threats |
| **Retire** | The flow exists only because of the old system's limits, or nobody uses it. | "Press 9 to repeat", topic menus, "type MENU to start over" |

```mermaid theme={null}
---
title: Where each old flow goes
---
flowchart TD
    F["Old flow or<br/>menu option"] --> U{"Still used and<br/>still needed?"}
    U -->|"No"| RET["Retire"]
    U -->|"Yes"| H{"Policy requires<br/>a human?"}
    H -->|"Yes"| HUM["Route to human"]
    H -->|"No"| S{"Reads or writes<br/>a system?"}
    S -->|"No"| KN["Knowledge answer"]
    S -->|"Yes"| WF["Workflow with actions"]
    WF --> AU{"Needs identity<br/>verification?"}
    AU -->|"Yes"| KEEP["Keep existing<br/>authentication step"]

    classDef source fill:#F7F7F7,color:#131415,stroke:#E8E8E8
    classDef agent fill:#131415,color:#FFFFFF,stroke:#131415,stroke-width:3px
    classDef surface fill:#FFFFFF,color:#131415,stroke:#131415
    classDef human fill:#C3EE5E,color:#131415,stroke:#131415,stroke-width:2px

    class F,RET source
    class U,H,S,AU surface
    class KN,WF,KEEP agent
    class HUM human
```

Two notes on classification:

* **Menus mostly disappear.** A topic menu exists because the old system could not understand free text or speech. An AI agent can, so the menu itself is usually retired. What survives is whatever happens after the customer picks an option.
* **Policy-driven routes stay explicit.** "Fraud goes to the fraud team" should be a written rule in the new agent, not something you hope it infers.

## Phase 3: Keep what works

A migration is not a rewrite of everything. Keep the parts of the old setup that are doing their job:

* **Authentication.** If callers already verify identity through a PIN, one-time code or account lookup, reuse the same check in the new workflows rather than designing a new one. Changing authentication and the support agent at the same time doubles the risk. Check your regulator's and your security team's requirements before changing any verification step.
* **Routing to specialist teams.** Queue names, skills and hours that work today should map one to one onto the new agent's escalation rules.
* **Tags and reporting fields.** If dashboards or triggers depend on tags the old bot set, make the new agent set equivalent tags, or update the reports before cutover.
* **Approved wording.** Disclosures, legal notices and recorded-line announcements keep their approved text.

## Phase 4: Data and integrations

List every system the old flows read from or write to, and decide how the new agent will reach each one.

| System | Old access | New access | Read or write | Owner | Sandbox available? |
| - | - | - | - | - | - |
| Order system | Bot vendor connector | Your API | Read | Engineering | Example: Yes |
| Payments | IVR plugin | Your API | Write | Payments team | Example: No, use a test account |
| Helpdesk | Native | Native | Read and write | Support ops | n/a |

Write actions need the most care. Until cutover, point them at a sandbox or test account, or keep the workflows that use them switched off during the parallel run. A parallel run that issues real refunds is not a parallel run.

Also plan for transcripts and history: where conversations from the old system are stored, how long you keep them, and whether the new agent or your team needs access to them. Check retention requirements for your industry.

## Phase 5: Run in parallel

Before the AI agent answers a single customer, run it alongside the old system:

* The AI agent sees real conversations and drafts the reply it would have sent, visible only to your team (for example, as an internal note in your helpdesk).
* The old system, or your team, keeps serving customers as today.
* Reviewers compare the AI agent's drafts with what actually happened, mark the wrong ones and fix the knowledge or workflow behind them.
* Good and bad examples become regression test cases that you rerun after every change.

For phone, the equivalent is testing calls end to end with internal callers and scripted scenarios before routing any real customer calls.

Exit the parallel run when the drafts on your top flows are consistently correct and your regression cases pass, not when a date arrives.

## Phase 6: Cut over in phases

Pick slices that are easy to switch and easy to watch:

* **By channel:** chat first, where feedback is fast, then email, then phone.
* **By segment:** one brand, region, product line or customer tier at a time.
* **By hours:** after-hours traffic first, when the alternative is often no service at all.

For each slice: turn off the old system there, turn on direct replies from the AI agent, send test conversations, and assign someone to watch the first days of real traffic. Only one system should answer on any slice. Two bots answering the same customer produces two answers and metrics nobody can trust.

### Rollback plan

Write the rollback for each slice before you cut it over:

| Item | Example |
| - | - |
| Trigger | Wrong answers on a policy topic, a broken action, escalation rate well above the parallel-run baseline |
| Who decides | Named support lead on duty |
| How to roll back | Switch the slice back to internal notes only, re-enable the old flow or menu for that slice |
| Time to roll back | Measured in a dry run before cutover |
| Afterward | Fix, rerun the regression cases, repeat the parallel run for that slice |

Keep the old system available, but switched off, until every slice has been stable for a period you agree on in advance.

## Communicating with the team

The support team will notice the change before customers do. Tell them:

* What the AI agent handles, what it routes to them, and what changes in their queues.
* How escalated conversations arrive, and what context comes with them.
* How to flag a wrong answer, and who fixes it.
* The cutover schedule and the rollback trigger, so nobody is surprised.

Involve team leads in reviewing parallel-run drafts. They know which old flows matter, and they will catch problems faster than anyone.

## Common mistakes

* Rebuilding every menu option as a workflow instead of retiring what the AI agent no longer needs.
* Changing authentication at the same time as the support agent.
* Running write actions against production during the parallel run.
* Letting the old bot and the new agent reply on the same conversations.
* Forgetting reports, tags and triggers that depended on the old system.
* Switching off the old system before a rollback has been tested.

## Doing this in Fini

In Fini (usefini.com), the migration phases above map to these features:

* [Migrate from Zendesk bots to Fini](/en/how-to/migrate-from-zendesk-bots) walks through this process on one helpdesk end to end, including **Bot Routing** for cutting over brand by brand.
* Rebuild workflows with actions as behavior trees in **Rulebook → Intent Rules**, with `Read`, `Form` and `Tool` nodes. See [Intent Rules](/en/automations/rulebook).
* Run the parallel phase with an **Internal Comment** rule in **Rulebook → Reply Rules**, add a **No Reply** rule for human-owned conversations, and roll a slice back by returning it to **Internal Comment**. See [Reply Rules](/en/automations/reply-behavior).
* For phone, configure the agent from **Voice** in the agent sidebar, then click **Start test** to run a **Voice test call** in the browser before any real calls: follow the live transcript and call status, and click **End call** to finish and review the transcript before closing the dialog (closing it ends the call and clears the transcript). Coordinate phone numbers and call routing with your Fini contact. See [Fini on voice](/en/deploy/voice).
* Review drafted replies and calls in [Inbox](/en/testing/inbox), and use the flask icon to turn the conversations that matter into [Test Suite](/en/testing/test-suite) test cases. Group them into a collection for the migration and run it after every change. Test Suite uses recorded or mock Action responses, so check rebuilt write Actions end to end in a sandbox before cutover.

## Related

<CardGroup cols={2}>
  <Card title="Migrate from Zendesk bots" icon="right-left" href="/en/how-to/migrate-from-zendesk-bots">
    The Fini-specific migration guide for Zendesk.
  </Card>

  <Card title="Intent Rules" icon="diagram-project" href="/en/automations/rulebook">
    Deterministic behavior trees for workflows with actions.
  </Card>

  <Card title="Reply Rules" icon="turn-down-right" href="/en/automations/reply-behavior">
    Direct reply, internal note or no reply, per conversation.
  </Card>

  <Card title="Fini on voice" icon="phone" href="/en/deploy/voice">
    Run the agent on inbound phone support.
  </Card>
</CardGroup>


## Related topics

- [Changelog](/en/changelog.md)
- [What is Fini](/en/what-is-fini.md)
- [Create a test set](/en/api-reference/create-test-set.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.