--- name: architect-migrate-from-competitor description: Use when the user wants to move a voice agent onto ElevenLabs Conversational AI from another platform — Retell AI, Vapi, or Bland — or hands over an export from one of them to work from. Fires on "migrate my Retell agent", "convert this Vapi assistant", "move from Bland to ElevenLabs", "port my agent to ElevenLabs", "here is my agent JSON", or when the user pastes a JSON blob containing response_engine, squadId, transportConfigurations, or pathway. Not for unrelated database, codebase, or framework moves. --- # Migrate a voice agent from another platform to ElevenLabs ## What this skill is for The user has an agent built on Retell, Vapi, or Bland and wants it running on ElevenLabs. This skill owns the parts of that job specific to *importing*: identifying the export, handling credentials inside it, mapping foreign constructs onto ElevenLabs ones, naming what genuinely has no equivalent, and sequencing the work. It does **not** re-teach ElevenLabs authoring. This plugin has ~30 skills that own that, and they are more detailed and better maintained than a summary here would be. Your job is to translate and to route. **What counts as finished: an agent that demonstrably runs.** Not an optimal one — a conversion that starts, resolves its variables, calls a tool successfully and reaches an end. Aim to get there in one pass and one round of fixes, which means the checking in step 5 is part of the work rather than something offered afterwards. The finish line is a run, never a re-read of your own config, and "I converted everything" is not a status report. Assume an authenticated EL workspace with `$API_KEY` set, as every skill in this plugin does. ## What to delegate, and to whom Route each step rather than reimplementing it. Most migrations touch six or seven of these; read one when you reach its step, not up front. | Step | Skill | |---|---| | Read the target agent's current state | `architect-explore-agent` | | Choose the model | `architect-llm-selection` | | Port prompt text and first message | `architect-edit-string-fields` | | Build the workflow graph, nodes, edges | `architect-edit-workflows` | | Author step-by-step logic as procedures | `architect-structured-procedures`, `architect-manage-procedures` | | Recreate tools | `architect-create-webhook-tool`, `architect-create-client-tool`, `code-tools` | | Fix a tool that misbehaves | `architect-edit-existing-tool`, `architect-troubleshoot-tool-errors` | | Post-call fields and evaluation criteria | `architect-post-call-data` | | Remaining config, turn-taking, latency | `architect-update-config-safely`, `architect-turn-taking-latency` | | Auth, guardrails, retention | `architect-secure-for-production` | | Stage and ramp the rollout | `architect-branches-versions-merge`, `architect-schedule-launch` | | Mock tools before any test run | `architect-mock-all-tools` | | The smoke run that proves it works (step 5) | `architect-create-simulation-test` | | Parity tests, CI | `-llm-test`, `-tool-test`, `architect-get-agent-ci-green` | | Score the agent — only once asked, see step 6 | `agent-review`, then `agent-simplification` | | Check it once real calls exist (step 6) | `architect-review-live-calls` | **Do not create the agent through `architect-generate-agent`.** Its own documentation warns that its flow regenerates the prompt instead of installing the one you just converted. Create the shell directly with `POST /v1/convai/agents/create`, then install the converted content. **Reading a sibling skill is not the same as routing to it, and the difference costs you at write time.** The temptation is to skim the two or three you think you need and hand-roll the rest, which is faster right up to the first rejected payload — a real migration reimplemented tool creation, post-call data, model selection and all testing inline, and hit its schema surprises while writing to the live workspace rather than while reading. Each of these skills owns field-level detail this one deliberately does not restate. Before you report, account to yourself for which skill covered each step: a step you cannot attribute is a step you improvised. That accounting is for you, not for the report — a customer has no use for a list of internal skill names. ## Step 1 — identify the platform Dispatch on a field that is always present, not on one that merely often is: | Signature | Platform | Read next | |---|---|---| | `response_engine.type` | Retell | `reference/retell.md` | | `squadId`, `members[]`, `transportConfigurations`, `model.toolIds` | Vapi | `reference/vapi.md` | | `pathway`, `nodes[].type` with a `Default` node, or a Persona payload | Bland | `reference/bland.md` | Ask the user which platform it came from if the blob is ambiguous. Never guess from prose in the prompt text. ## Step 1b — audit the export before converting it Cheap, mechanical, and it routinely finds live bugs in the source agent. A migration is the first time anyone reads the whole graph at once, so do this before deciding what to build: - **Dangling edges** — a condition with no destination. These are silent dead ends on paths that look wired in the editor, and they land on happy paths as often as error paths. - **Orphaned nodes** — nothing routes in, or nothing routes out. A whole feature that has never run. - **Variables read but never assigned** — spoken in a prompt, written nowhere. The caller hears a literal placeholder or a blank. **Scan code-node bodies, not just template syntax.** A variable read as `dv.account_ref` inside a JavaScript node is invisible to a `{{...}}` search, so an export with a dozen code nodes will under-report badly on the exact check that matters most. Rank what you find by how many tools bind it: a variable bound on every tool that nothing supplies fails every tool call at conversation start. - **One identifier, two different definitions.** Check whether any two tool entries share an id or a name while disagreeing on url or method. This is not tidiness — one export reused a single tool id across a main-flow tool and a component tool pointing at *different environments*, so the agent read from one and wrote to the other. Report it: an agent whose write paths leave the environment its name claims is a finding the owner will want, and it decides how you key the id map in step 4. - **Response paths that cannot resolve** — an assignment reading a field the API does not return, or a path whose shape is wrong for the response (array indexing and object prefixes are the usual offenders). These carry across silently and break the same way on the new platform. - **Branch conditions with a null target** and no else path. ### Do the audit in full. Do not report it in full. This is the part that decides whether a migration feels like arriving somewhere or like being handed a bill. Run every check above at full depth — the accuracy is the point, and nothing else in this skill finds these. Then write the findings to a file, and bring **two** things back to the user: 1. **What their agent does** — the intents it handles, the tools it calls, the shape it is in. A dozen lines, and a diagram of the source graph where one helps. Name the phases using the source's own node names so they can check you. This is the only evidence they have that you understood their agent before you started rewriting it, and it is the cheapest trust available in the whole job. 2. **Only what you cannot proceed correctly without.** The test is mechanical: *would a wrong guess here break the migration?* A variable that every tool binds and nothing supplies, a voice that does not port, an environment that two halves of the graph disagree about. On a real 123-node export that list was **two items**. It is normally one to three. Everything else goes in the file with a one-line pointer. Not because it does not matter — it is the most valuable thing in this step — but because of *when* it arrives. A defect ledger delivered before anything runs reads as "here is how much work you have bought," and the findings are almost all pre-existing bugs in an agent that has been in production for months. Lead with a wall of red and a first-time user concludes the platform is the problem, closes the tab, and stays where they are. So do not open with a count of defects. When nothing blocks the conversion, **say that first** — it is usually true and it is the single most useful sentence you can write. **Separate the three registers, in the file and in anything you say aloud.** They read completely differently and mixing them is what makes a short list feel long: - **Blocks the conversion** — needs an answer now. This is the only category that goes inline. - **Already broken in the source** — live today, reproduced faithfully. Say so explicitly. "Your current agent reads a placeholder to the caller here" is a gift; the same fact in an undifferentiated list is an accusation. - **Does not port** — the genuine gaps, from `reference/.md`. Never silently fix a defect and never silently carry one across. Logging it in the file is not silence; it is where the mapping log and gap list already live, from step 4. ### Check the source's claims against its own endpoints Everything above reads the export. Some of the worst defects are not in it — a prompt asserts a fact, the backend it calls disagrees, and no amount of reading the JSON can tell you. Say a prompt instructs the agent to offer same-day slots up to 5 p.m. while the scheduling endpoint refuses anything after 4:30. Both halves look right on their own, and the agent offers a slot its own tool then rejects. So where the export hands you an endpoint, verify the facts the prompt states about it. This means calling a third party's production API, so it is fenced: - **Ask the user first, naming the endpoints.** They may not own that backend, and it may be someone else's live system. - **Read-only endpoints only** — a lookup, an availability check, a status query. Never one that books, pays, cancels, sends, or writes, whatever its name suggests. - **Synthetic values only.** A probe is not the place for a real name, number, or booking reference. - **Skip it if the endpoint needs a credential you recovered from the export.** Using that credential to explore is a wider use than its owner authorised. What you learn joins the findings. A prompt fact the backend contradicts is a caller-visible bug in the source agent, and it is the kind nobody has reported, because it only shows up on the calls that hit it. ## Step 2 — credentials, before anything else touches the export Exports embed live credentials: bearer tokens, Basic auth headers, API keys in tool headers, and sometimes a secret inside a webhook URL's query string. Handle these first, because every later step copies fields around. 1. Scan every tool for header values, URLs with embedded userinfo or query secrets, and any field whose name contains `auth`, `token`, `key`, `secret`, or `bearer`. 2. For each real credential found, create an ElevenLabs secret and reference it — never paste the literal value into a tool definition: ``` POST /v1/convai/secrets {"type": "new", "name": "billing_api_token", "value": ""} ``` The response carries a `secret_id`. Reference it in the tool's `request_headers` as `{"secret_id": ""}`. Rotation is `PATCH /v1/convai/secrets/{secret_id}` with a body of `{"type": "update", "name": ..., "value": ...}` — note the id is in the path, not the collection endpoint you created against. **Secrets are workspace-scoped, so check what else uses one before you touch it.** `GET /v1/convai/secrets/{secret_id}/dependencies/{resource_type}` lists the resources depending on a secret. Rotating or deleting a secret that other agents reference breaks them, and a migration is exactly the context where someone tidies up a credential that looked unused. 3. Tell the user which credentials you found and that they are now live in a third system, so they can decide whether to rotate at the origin. A credential sitting in an export file has usually been shared more widely than its owner realises. 4. A value that is already a `{{template}}` reference is not a credential — do not resolve it to a literal, which is how an agent ends up authenticating as the wrong principal for the rest of a call. But do not carry the template across as a **string** either. **ElevenLabs substitutes nothing in a header string** — a header given as text is sent exactly as written, so `"Basic {{vault_token}}"` transmits those literal characters and the request is simply unauthenticated. It is a credential reference that does nothing, and it fails on every call rather than visibly at build time. Use the typed forms: - a dynamic variable — `{"variable_name": "vault_token"}` - a workspace secret — `{"secret_id": ""}` - an environment variable — `{"env_var_label": "