← ElevenLabsCONTENT HISTORYWHAT CHANGED · RULE-BASED ANALYSIS
Update to ElevenLabs
Snapshot Sep 30, 2026 · 22:53 UTC · version 1.0.0
Collection source: not recorded for this historical snapshot.
First saved snapshot
No earlier snapshot is available to establish a change.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"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.",
"included_files": [
{
"relative_path": "reference/README.md",
"size_in_bytes": 6530
},
{
"relative_path": "reference/bland.md",
"size_in_bytes": 6670
},
{
"relative_path": "reference/example-agent.json",
"size_in_bytes": 6762
},
{
"relative_path": "reference/example-webhook-tool.json",
"size_in_bytes": 2901
},
{
"relative_path": "reference/expressions.md",
"size_in_bytes": 11427
},
{
"relative_path": "reference/retell.md",
"size_in_bytes": 13504
},
{
"relative_path": "reference/traps.md",
"size_in_bytes": 12862
},
{
"relative_path": "reference/vapi.md",
"size_in_bytes": 10578
},
{
"relative_path": "scripts/check-examples.mjs",
"size_in_bytes": 11426
}
],
"skill_md_contents": "---\nname: architect-migrate-from-competitor\ndescription: 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.\n---\n\n# Migrate a voice agent from another platform to ElevenLabs\n\n## What this skill is for\n\nThe user has an agent built on Retell, Vapi, or Bland and wants it running on ElevenLabs. This skill\nowns the parts of that job specific to *importing*: identifying the export, handling credentials\ninside it, mapping foreign constructs onto ElevenLabs ones, naming what genuinely has no equivalent,\nand sequencing the work.\n\nIt does **not** re-teach ElevenLabs authoring. This plugin has ~30 skills that own that, and they\nare more detailed and better maintained than a summary here would be. Your job is to translate and\nto route.\n\n**What counts as finished: an agent that demonstrably runs.** Not an optimal one — a conversion that\nstarts, resolves its variables, calls a tool successfully and reaches an end. Aim to get there in one\npass and one round of fixes, which means the checking in step 5 is part of the work rather than\nsomething offered afterwards. The finish line is a run, never a re-read of your own config, and \"I\nconverted everything\" is not a status report.\n\nAssume an authenticated EL workspace with `$API_KEY` set, as every skill in this plugin does.\n\n## What to delegate, and to whom\n\nRoute each step rather than reimplementing it. Most migrations touch six or seven of these; read one\nwhen you reach its step, not up front.\n\n| Step | Skill |\n|---|---|\n| Read the target agent's current state | `architect-explore-agent` |\n| Choose the model | `architect-llm-selection` |\n| Port prompt text and first message | `architect-edit-string-fields` |\n| Build the workflow graph, nodes, edges | `architect-edit-workflows` |\n| Author step-by-step logic as procedures | `architect-structured-procedures`, `architect-manage-procedures` |\n| Recreate tools | `architect-create-webhook-tool`, `architect-create-client-tool`, `code-tools` |\n| Fix a tool that misbehaves | `architect-edit-existing-tool`, `architect-troubleshoot-tool-errors` |\n| Post-call fields and evaluation criteria | `architect-post-call-data` |\n| Remaining config, turn-taking, latency | `architect-update-config-safely`, `architect-turn-taking-latency` |\n| Auth, guardrails, retention | `architect-secure-for-production` |\n| Stage and ramp the rollout | `architect-branches-versions-merge`, `architect-schedule-launch` |\n| Mock tools before any test run | `architect-mock-all-tools` |\n| The smoke run that proves it works (step 5) | `architect-create-simulation-test` |\n| Parity tests, CI | `-llm-test`, `-tool-test`, `architect-get-agent-ci-green` |\n| Score the agent — only once asked, see step 6 | `agent-review`, then `agent-simplification` |\n| Check it once real calls exist (step 6) | `architect-review-live-calls` |\n\n**Do not create the agent through `architect-generate-agent`.** Its own documentation warns that its\nflow regenerates the prompt instead of installing the one you just converted. Create the shell\ndirectly with `POST /v1/convai/agents/create`, then install the converted content.\n\n**Reading a sibling skill is not the same as routing to it, and the difference costs you at write\ntime.** The temptation is to skim the two or three you think you need and hand-roll the rest, which is\nfaster right up to the first rejected payload — a real migration reimplemented tool creation, post-call\ndata, model selection and all testing inline, and hit its schema surprises while writing to the live\nworkspace rather than while reading. Each of these skills owns field-level detail this one deliberately\ndoes not restate. Before you report, account to yourself for which skill covered each step: a step you\ncannot attribute is a step you improvised. That accounting is for you, not for the report — a customer\nhas no use for a list of internal skill names.\n\n## Step 1 — identify the platform\n\nDispatch on a field that is always present, not on one that merely often is:\n\n| Signature | Platform | Read next |\n|---|---|---|\n| `response_engine.type` | Retell | `reference/retell.md` |\n| `squadId`, `members[]`, `transportConfigurations`, `model.toolIds` | Vapi | `reference/vapi.md` |\n| `pathway`, `nodes[].type` with a `Default` node, or a Persona payload | Bland | `reference/bland.md` |\n\nAsk the user which platform it came from if the blob is ambiguous. Never guess from prose in the\nprompt text.\n\n## Step 1b — audit the export before converting it\n\nCheap, mechanical, and it routinely finds live bugs in the source agent. A migration is the first time\nanyone reads the whole graph at once, so do this before deciding what to build:\n\n- **Dangling edges** — a condition with no destination. These are silent dead ends on paths that look\n wired in the editor, and they land on happy paths as often as error paths.\n- **Orphaned nodes** — nothing routes in, or nothing routes out. A whole feature that has never run.\n- **Variables read but never assigned** — spoken in a prompt, written nowhere. The caller hears a\n literal placeholder or a blank. **Scan code-node bodies, not just template syntax.** A variable read\n as `dv.account_ref` inside a JavaScript node is invisible to a `{{...}}` search, so an export with a\n dozen code nodes will under-report badly on the exact check that matters most. Rank what you find by\n how many tools bind it: a variable bound on every tool that nothing supplies fails every tool call at\n conversation start.\n- **One identifier, two different definitions.** Check whether any two tool entries share an id or a\n name while disagreeing on url or method. This is not tidiness — one export reused a single tool id\n across a main-flow tool and a component tool pointing at *different environments*, so the agent read\n from one and wrote to the other. Report it: an agent whose write paths leave the environment its name\n claims is a finding the owner will want, and it decides how you key the id map in step 4.\n- **Response paths that cannot resolve** — an assignment reading a field the API does not return, or a\n path whose shape is wrong for the response (array indexing and object prefixes are the usual\n offenders). These carry across silently and break the same way on the new platform.\n- **Branch conditions with a null target** and no else path.\n\n### Do the audit in full. Do not report it in full.\n\nThis is the part that decides whether a migration feels like arriving somewhere or like being handed a\nbill. Run every check above at full depth — the accuracy is the point, and nothing else in this skill\nfinds these. Then write the findings to a file, and bring **two** things back to the user:\n\n1. **What their agent does** — the intents it handles, the tools it calls, the shape it is in. A dozen\n lines, and a diagram of the source graph where one helps. Name the phases using the source's own\n node names so they can check you. This is the only evidence they have that you understood their\n agent before you started rewriting it, and it is the cheapest trust available in the whole job.\n2. **Only what you cannot proceed correctly without.** The test is mechanical: *would a wrong guess\n here break the migration?* A variable that every tool binds and nothing supplies, a voice that does\n not port, an environment that two halves of the graph disagree about. On a real 123-node export that\n list was **two items**. It is normally one to three.\n\nEverything else goes in the file with a one-line pointer. Not because it does not matter — it is the\nmost valuable thing in this step — but because of *when* it arrives. A defect ledger delivered before\nanything runs reads as \"here is how much work you have bought,\" and the findings are almost all\npre-existing bugs in an agent that has been in production for months. Lead with a wall of red and a\nfirst-time user concludes the platform is the problem, closes the tab, and stays where they are.\n\nSo do not open with a count of defects. When nothing blocks the conversion, **say that first** — it is\nusually true and it is the single most useful sentence you can write.\n\n**Separate the three registers, in the file and in anything you say aloud.** They read completely\ndifferently and mixing them is what makes a short list feel long:\n\n- **Blocks the conversion** — needs an answer now. This is the only category that goes inline.\n- **Already broken in the source** — live today, reproduced faithfully. Say so explicitly. \"Your\n current agent reads a placeholder to the caller here\" is a gift; the same fact in an undifferentiated\n list is an accusation.\n- **Does not port** — the genuine gaps, from `reference/<platform>.md`.\n\nNever silently fix a defect and never silently carry one across. Logging it in the file is not silence;\nit is where the mapping log and gap list already live, from step 4.\n\n### Check the source's claims against its own endpoints\n\nEverything above reads the export. Some of the worst defects are not in it — a prompt asserts a fact,\nthe backend it calls disagrees, and no amount of reading the JSON can tell you. Say a prompt instructs\nthe agent to offer same-day slots up to 5 p.m. while the scheduling endpoint refuses anything after\n4:30. Both halves look right on their own, and the agent offers a slot its own tool then rejects.\n\nSo where the export hands you an endpoint, verify the facts the prompt states about it. This means\ncalling a third party's production API, so it is fenced:\n\n- **Ask the user first, naming the endpoints.** They may not own that backend, and it may be someone\n else's live system.\n- **Read-only endpoints only** — a lookup, an availability check, a status query. Never one that books,\n pays, cancels, sends, or writes, whatever its name suggests.\n- **Synthetic values only.** A probe is not the place for a real name, number, or booking reference.\n- **Skip it if the endpoint needs a credential you recovered from the export.** Using that credential\n to explore is a wider use than its owner authorised.\n\nWhat you learn joins the findings. A prompt fact the backend contradicts is a caller-visible bug in the\nsource agent, and it is the kind nobody has reported, because it only shows up on the calls that hit it.\n\n## Step 2 — credentials, before anything else touches the export\n\nExports embed live credentials: bearer tokens, Basic auth headers, API keys in tool headers, and\nsometimes a secret inside a webhook URL's query string. Handle these first, because every later step\ncopies fields around.\n\n1. Scan every tool for header values, URLs with embedded userinfo or query secrets, and any field\n whose name contains `auth`, `token`, `key`, `secret`, or `bearer`.\n2. For each real credential found, create an ElevenLabs secret and reference it — never paste the\n literal value into a tool definition:\n\n ```\n POST /v1/convai/secrets\n {\"type\": \"new\", \"name\": \"billing_api_token\", \"value\": \"<the value from the export>\"}\n ```\n\n The response carries a `secret_id`. Reference it in the tool's `request_headers` as\n `{\"secret_id\": \"<that id>\"}`. Rotation is `PATCH /v1/convai/secrets/{secret_id}` with a body of\n `{\"type\": \"update\", \"name\": ..., \"value\": ...}` — note the id is in the path, not the collection\n endpoint you created against.\n\n **Secrets are workspace-scoped, so check what else uses one before you touch it.**\n `GET /v1/convai/secrets/{secret_id}/dependencies/{resource_type}` lists the resources depending on\n a secret. Rotating or deleting a secret that other agents reference breaks them, and a migration is\n exactly the context where someone tidies up a credential that looked unused.\n3. Tell the user which credentials you found and that they are now live in a third system, so they\n can decide whether to rotate at the origin. A credential sitting in an export file has usually\n been shared more widely than its owner realises.\n4. A value that is already a `{{template}}` reference is not a credential — do not resolve it to a\n literal, which is how an agent ends up authenticating as the wrong principal for the rest of a call.\n But do not carry the template across as a **string** either. **ElevenLabs substitutes nothing in a\n header string** — a header given as text is sent exactly as written, so `\"Basic {{vault_token}}\"`\n transmits those literal characters and the request is simply unauthenticated. It is a credential\n reference that does nothing, and it fails on every call rather than visibly at build time. Use the\n typed forms:\n\n - a dynamic variable — `{\"variable_name\": \"vault_token\"}`\n - a workspace secret — `{\"secret_id\": \"<id>\"}`\n - an environment variable — `{\"env_var_label\": \"<label>\"}`\n\n A locator supplies the **whole** header value, so it cannot be composed with a literal prefix. That\n sounds like a problem for `Basic <token>` and is not: **put the scheme word inside the secret.**\n Create the secret with value `Basic <token>` and reference it with `{\"secret_id\": ...}`. The header\n is then complete on its own, and the credential never appears in the agent config. If you find\n yourself wanting `\"Basic {{vault_token}}\"`, that is the sign to make a secret holding the whole\n header value.\n5. **Watch for one variable that plays two roles.** A single name is often both a *static seed*\n credential used by early calls and a *per-user token* that a later tool overwrites mid-call. They\n need different treatment and it is easy to see only one of them:\n\n **The test is mechanical: does any tool write that variable during the call?** Look for a response\n assignment on any tool whose target is the variable name — not just on the tool you are holding,\n since the tool that issues a token is rarely the tool that uses it. Then:\n\n - **Nothing writes it → static.** Its value lives in the agent's variable defaults, so a\n `{\"variable_name\": ...}` locator would leave a live credential sitting in agent config in\n plaintext. Make a secret holding the complete header value and reference it with\n `{\"secret_id\": ...}`.\n - **Some tool writes it → swapped.** Use `{\"variable_name\": ...}`. A secret cannot change mid-call,\n and the locator leaks nothing because the value only ever exists at runtime.\n\n Because the locator supplies the whole header, a swapped variable has to hold the whole header value\n too. If the endpoint issuing that token returns a bare token, the assignment writing it cannot add\n the scheme word — so either have that endpoint return the complete value, or confirm the receiving\n API accepts a bare token. That is a short, concrete question for the customer, and it is worth asking\n before you build rather than after the first unauthenticated call.\n\n Handling only the swap leaves a placeholder in the agent's variable defaults, which invites someone\n to paste a live credential into agent config in plaintext. Say explicitly which of the two you have\n done.\n\nDo not write the raw export, or any redacted copy containing real values, into a repo or a log.\n\n## Step 3 — pick the shape\n\nOne decision to state and one to ask. What gets *shipped* has a sensible default and should not be put\nto the user at all; what *shape* it takes is worth their judgement, and this is the moment for it —\nafter the audit, before anything is built.\n\n### The deliverable is a default, not a question\n\n**The rebuild ships. A faithful port gets built quietly alongside it as an oracle.** State that in one\nline and move on. Do not open someone's migration by asking them to choose a migration philosophy —\nthey came to get their agent working, and the trade only means something to a person who already knows\nwhat fidelity costs.\n\n- **The rebuild is the agent.** The source is the specification, not the blueprint: pick the shape that\n fits, fix what the audit found, hand them something callable.\n- **The faithful port is built and then not mentioned again.** It mirrors the source construct for\n construct, it is never deployed, and it is one converter pass now that the audit exists. It is the\n oracle — what parity tests are written against, and what `agent-simplification` wants before it will\n touch anything. One line in the report's file list is the whole of its visibility.\n\nTwo things change this, and both come from the user rather than from your reading of the export. If\nthey ask how they can tell the new agent matches, the port stops being silent — that is its purpose and\nit is already built. If they say plainly that they are done with the source platform, skip it.\n\nFindings are simply fixed in the rebuild and reproduced in the port. There is no per-finding question\nto ask; the report says which ones you fixed.\n\n### The shape is the question\n\nThis is the one decision worth putting to the user before you build, because it is the one they can\nactually judge — they know their own call flow.\n\nFour options, in increasing order of cost. **Recommend one** — with the reason, in a sentence they can\ndisagree with — rather than presenting a menu and waiting:\n\n- **A single agent with a prompt.** Right for a source that is one big prompt with a few tools, and\n right more often than the source's node count suggests.\n- **An agent with procedures.** Right for step-by-step logic — verification sequences, intake,\n scripted disclosures — where the order matters but the graph does not branch much.\n- **An agent with a workflow.** Right when the source genuinely has distinct states with different\n tools, models, or voices per state.\n- **A mix.** Common and often correct: a prompt or a small workflow for the spine, procedures for the\n sequences that hang off it. Do not treat the three above as exclusive.\n\nDo not mirror the source's topology out of loyalty. Competitor editors encourage many small nodes, and\nevery node you carry over multiplies the configuration surface that can go wrong. Say what the shape\nbuys in terms of *their* agent — \"your three queue components run the same algorithm, so they become one\nparagraph and three procedures\" — not in terms of node counts, which mean nothing to them. Every real\nreduction comes from that kind of semantic merge; a mechanical conversion is roughly\nnode-count-neutral, which is exactly why the port is not the thing that ships.\n\n## Step 3b — get the shapes right before anything is watching\n\nDiscovering the API's shape rules by being rejected by it is slow, and it burns the patience of whoever\nis watching — a customer on a call, or someone who just uploaded their own export and is waiting. It is\navoidable: nearly every rejection below is a known shape rule, and the rest can be learned somewhere\nharmless.\n\nThree habits, in order of how much they save:\n\n1. **Build every payload locally first, then write.** Do not alternate authoring and API calls\n node-by-node. Compose the whole set — tools, nodes, edges — check it against the rules in this\n skill and `reference/expressions.md`, and only then start writing. A shape mistake made once in a\n local draft is one fix; the same mistake discovered on call forty is forty.\n2. **Do any genuine schema discovery somewhere disposable.** If you truly do not know whether a field\n is accepted, find out on a scratch agent rather than on the one being migrated. Ask which workspace\n to use for it — a workspace holding production agents is not disposable, and tools and knowledge-base\n documents are workspace-global, so a probe that creates one is not confined to your scratch agent.\n Delete only what you created in this session, by id you captured at create time. Never learn on the\n customer's agent, and never learn on a branch they are reading.\n3. **Re-read a rejection before changing anything.** The messages below name a *symptom*, not the\n field at fault, so the instinct to bisect the payload is wrong and expensive. One of these cost a\n real migration fourteen identical failures.\n\n| Rejection | What it actually means |\n|---|---|\n| `Input should be a valid dictionary or object to extract fields from` | An `expression` was passed as a **string**. Conditions are structured objects — see `reference/expressions.md`. |\n| `Input should be a valid dictionary` | A `path_params_schema` was passed as an **array**. It is an object keyed by parameter name. |\n| `Can only set one of: description, dynamic_variable, is_system_provided, constant_value, or is_omitted` | A property declares **two or more** value sources. Exactly one. |\n| `Must set one of: description, dynamic_variable, ...` | A property declares **none**. Most often an array's `items` schema, which needs its own source. |\n| `Invalid URL format` | A `{{...}}` source interpolation survived in a tool URL. Static host, single-brace path placeholder, `path_params_schema`. |\n| `Duplicate edge found between X and Y` | An edge already exists for that node **pair**. Edges are keyed on the unordered pair, so the reverse transition becomes that edge's `backward_condition`. |\n| `Input should be 'generate_immediately', 'wait_for_user' or 'auto'` | `entry_behavior` got something outside its enum. |\n| `Input should be 'auto', 'force' or 'off'` | `pre_tool_speech` was given filler text. It is an enum, and it cannot carry a line — use `forced_tool_name` on an `override_agent` node instead. |\n\nAnd one that never rejects at all, so no message will tell you: **`expression: {}`** is accepted and\nsilently becomes `boolean_literal` false, so the edge never fires.\n\nAnd two on the **test** payloads, which is where people are still guessing after everything else has\ngone in clean: `success_conditions` is a list of strings, and `simulation_scenario` is a plain string —\nnot the nested `simulated_user_config` object its name suggests. Both reject, and both only bite at\nstep 5, so they read as a broken smoke run rather than as two schema rules.\n\nIf something does go wrong in front of the customer, say what you are checking rather than narrating\nsurprise. \"That field takes an object, fixing\" is fine. A string of \"oh, that didn't work\" is what\nmakes a routine schema rule look like a broken migration.\n\n## Step 4 — build in dependency order, and keep the id map\n\n**Read `reference/traps.md` before you author anything here, and `reference/expressions.md` before the\nfirst condition.** They are short and they are the failure list for exactly this step: every item in\nthem is a thing that is accepted by the API and wrong at runtime, so nothing later in this skill will\ncatch it for you. Read them now rather than after a rejection, because these do not reject.\n\n**For the payload shapes themselves, read `reference/` — not a sample config you found in the\nworking repo.** A complete agent and a complete webhook tool live there, every field populated. The\nsample you would otherwise grep for may be stale, may be someone's scratch file, and outside\nElevenLabs does not exist at all, which is how a skill passes internally and fails in the field.\n\nMost of the order here is forced by what references what. Getting it wrong does not produce a clear\nerror; it produces a graph with references to things that do not exist yet.\n\n1. **Create the agent shell.** Everything else needs its id. Do not use\n `architect-generate-agent` (see above).\n2. **Create every tool, and record the id each one comes back with.** This is the step people skip\n the bookkeeping on, and it is unrecoverable later: a workflow's `tool` node references a tool by\n **id**, not by name, and so does `additional_tool_ids` on an `override_agent` node. You cannot build\n any node that calls a tool until its id exists.\n3. **Create procedures through the branch-scoped flow** that `architect-manage-procedures` owns.\n Passing them in the agent-create payload returns 200 and silently drops them.\n4. **Build nodes, then edges.** Edges reference node ids, so the nodes have to exist. Order only the\n conditional edges — the unconditional one is moved last for you.\n5. **Then the rest of the config** — model, privacy, turn-taking — and only then step 5.\n\n**Key the id map by something that is genuinely unique, which is neither the name nor the id alone.**\nExports break both keys, and each failure is silent:\n\n- **Same name, different tools.** Routine. Once you have renamed one to satisfy uniqueness, a\n name-keyed map cannot tell them apart and quietly points several nodes at one tool.\n- **Same id, different definitions.** Worse, and the case step 1b tells you to look for. An id-keyed map\n collapses the two, and half the graph then calls the wrong host — which nothing rejects, because both\n hosts answer.\n\nSo key on the **definition**: the source id together with the fields that decide where a call lands,\nurl and method at minimum. Two entries are the same tool only when those agree. When they do not,\ncreate two ElevenLabs tools and record in the mapping log why one source tool became two — that split\nis exactly the kind of thing a reader will otherwise assume was a mistake.\n\n### Leave a coverage proof, for the rebuild and the port alike\n\nTwo local files. They are the difference between output someone can audit and output they have to take\non trust, and they cost almost nothing because you are making each of these decisions anyway:\n\n- **A mapping log** — one entry per source behaviour: what it was, where it landed, and why. In a\n faithful port this is the fix log. In a rebuild it is the *only* thing that answers \"did you drop\n something?\", which is the rebuild's one genuine weakness against a port.\n- **A gap list** — every construct you could not map and what you did instead. This is also what the\n user needs in order to sign off on the approximations, rather than discovering them later.\n\nWrite both to a scratch directory, never into a repo, and never let either hold a credential value or a\nverbatim copy of the export.\n\n### Then check your own output, not your intentions\n\nRun a mechanical pass over what you actually emitted. Re-derive the things that are cheap to re-derive:\nevery edge's endpoints exist, every tool id referenced was really created, every node is reachable,\nevery variable a tool binds is assigned somewhere.\n\nThis is not ceremony. On one real migration that pass caught a bug in the converter its own author had\njust written: where a node pair carried an edge in only one direction, the edge was emitted reversed and\nunconditional, silently orphaning five nodes and one whole lookup path. Re-reading the code would not\nhave found it, and every validator in the chain accepted the payload. Report the check's result\nalongside the payload; a clean result you can point at is worth more than an assurance.\n\n## Model choice\n\nDo not carry the source model across by name, and do not use any static table of compliant models.\n`GET /v1/convai/llm/list` returns the models available to *that* workspace, filtered by the\ndeployment's data residency and the workspace's compliance entitlements. Call it, then hand the\nchoice to `architect-llm-selection`, which owns the region, HIPAA/PCI/ZRM and latency tradeoffs.\n\n## Step 5 — prove it runs, before you tell anyone it is done\n\nThis is a step, not a formality at the end, and it is the difference between a migration that *looks*\nfinished and one that works. Every failure mode in `reference/traps.md` is silent: nothing rejects your\npayload, the agent exists, the graph renders, and it is broken on the first real call. A migration\nreported as complete without this has not been checked — it has been *hoped about*.\n\n**The bar for \"working\" is five things, and all of them are observable:**\n\n1. Every tool was created — no payload was rejected and quietly skipped.\n2. The agent starts and delivers its first message.\n3. Every dynamic variable a tool binds is actually supplied at conversation start.\n4. At least one tool call executes and comes back successful.\n5. At least one path reaches a terminal node.\n\nNothing there is about quality. A first migration should be judged on whether it runs at all; tuning\ncomes after, and confusing the two is how a broken agent gets shipped with a confident summary.\n\n### Mock the tools for the first run, or you will transact against production\n\n**Tool mocking defaults to off.** A simulation with default settings calls the customer's real\nendpoints — so on the agent you just migrated, a smoke test books the appointment, charges the card,\nand mutates the cart, for real. Nothing warns you.\n\nSo run it in two passes:\n\n- **Pass 1 — everything mocked.** This is the cheap, side-effect-free run that proves the *graph* is\n alive: first message fires, variables resolve, edges route, a terminal node is reached. It catches\n the conversation-start binding trap outright — a variable a tool needs but nothing supplies fails the\n run with a `missing_dynamic_variables` error naming it, which is exactly the check that is\n impossible to pass by reading.\n- **Pass 2 — read-only tools live, everything that mutates still mocked.** One real lookup is what\n proves auth actually works, and auth is the thing most likely to be silently wrong after a\n migration. Do not unmask a tool that books, pays, cancels, sends, or writes.\n\n**Mocking everything is not the same as defining mocks, and the difference will waste your first run.**\nWhen no mock matches a call the default is to raise, not to fall through to the real tool. That default\nis the right one — an unmatched call is visible instead of quietly hitting production — but it means\n\"mock all\" with no mock bodies makes every tool call fail, and you learn nothing about the graph. Give\neach tool one mock with no parameter conditions, which makes it always match, and a `mock_result`\nholding the minimal successful response shape that tool's consumers read. Where a response assignment\npulls a field out, the mock has to contain that field or the variable silently stays unset and you are\ndebugging the mock instead of the migration. A mock can also be marked as an error deliberately, which\nis how you exercise a failure path once the happy one works.\n\n`architect-mock-all-tools` owns the mocking setup and `architect-create-simulation-test` owns building\nthe run; route to them rather than hand-rolling. The current endpoints are\n`POST /v1/convai/agent-testing/create` and `POST /v1/convai/agents/{agent_id}/run-tests` —\n`simulate-conversation` still exists but is deprecated.\n\n### Report what the run showed, not that you ran one\n\nGive the five items above with their actual outcome, and name anything you could not verify. \"Smoke\ntest passed\" is not a result; \"first message fired, three edges traversed, `fetch_profile` returned 200,\ncheckout still mocked and untested\" is.\n\n**Two rounds is the budget for fixing.** If a third would be needed, the failure is probably not a\nconversion defect, and grinding on it is how a one-hour job becomes four. Stop fixing and work out\nwhich of these it is: a success condition that does not match what the agent was asked to do, a mock\nmissing a field its consumer reads, or a real platform behaviour. Then say which, with the measurement,\nand stop. **A behavioural gate that holds two runs in three is a finding, not a bug** — non-determinism\ndoes not converge under repetition, so report the rate and what would make it absolute rather than\nre-running until it looks better. Between rounds, re-run only what failed; run the whole suite once at\nthe end.\n\n### The closing report\n\nThis is the artifact the migration is judged on, and its job is to leave someone confident enough to\nmove. Order matters more than content here — the same facts in the wrong order read as a warning.\n\n1. **What it does, and what it became.** The phases and tools, and a before/after diagram of the source\n graph against the new shape. This goes first because it is the only section that says *I understood\n your agent*.\n2. **That it runs.** The five checks with their outcomes.\n3. **Mapped directly · Handled natively by ElevenLabs · Needs you.** Three groups, in that order. The\n middle one matters more than it looks: a construct the source built by hand that this platform does\n for you is a *win*, and filed as \"difference\" it reads as a loss. The third group is the only one\n that asks anything of them, and it should be short.\n4. **Decisions and why.** Every judgement call you made that they might have made differently — one\n line each, with the reason and the alternative. A voice chosen as a placeholder, a model held at the\n source's for comparability, an environment resolved one way. This is what makes the migration\n reviewable rather than a black box, and it is the section people actually read.\n5. **Not migrated, on purpose.** Everything net dropped: orphaned nodes nothing routed to, edges that\n led nowhere, tools referenced by nothing, constructs with no equivalent. Say why for each, in a\n clause. An unexplained absence is the thing that erodes trust later, when they go looking for a\n feature and cannot find it — and most of these were already dead in the source, which is worth\n stating plainly.\n6. **The files, in one line each** — the audit findings, the mapping log, the gap list, the port.\n7. **What to do next, and it is not a review.** If they have transcripts or recordings from the source\n platform, this is the moment they are worth the most: real scenarios replayed against the new agent\n find in an afternoon what invented ones will not find at all. Invite that. It is a better next step\n than any score, and it is the one that turns a migration into their agent.\n\nDo not lead with a defect count, do not grade the agent, and do not attach a scorecard. The step 1b\nfindings live in a file with a pointer, and `agent-review` is step 6's business and gated there.\n\n## Before you call it done\n\n**These five can only be answered by running something.** Do not substitute a re-read of your own\nconfig for any of them.\n\n- **A mocked smoke run passed** — first message delivered, variables resolved, a terminal node reached.\n- **One read-only tool executed live and succeeded**, so auth is proven rather than assumed.\n- **No `missing_dynamic_variables` error**, the only real proof that every bound variable is supplied\n at conversation start.\n- **Every tool was created**, counted against the source's tool list. A payload rejected and skipped\n leaves a graph routing to nothing.\n- **The self-check over your emitted output ran and you are reporting its result** (step 4) — not the\n validator's, which passes payloads that are structurally wrong.\n\n**Then the report, which is what the migration is actually judged on.**\n\n- It leads with what the agent does and that it runs — not a defect count, not a score. The step 1b\n findings are in a file with a pointer, and `agent-review` was not handed over unasked.\n- It says what was dropped and why, and what you decided and why.\n- Only what genuinely blocked the conversion was put to the user as a question. One to three is normal;\n eleven means you moved the audit into the conversation.\n- The mapping log and gap list exist and the user has them.\n- Every credential you found is a secret reference and you said so — including any variable carrying\n both a static seed and a swapped token. You **ran** the scan rather than reasoning over the tool list.\n\n**Then the silent failures — and check them against the files, not against a summary.** Re-open\n`reference/traps.md` and `reference/expressions.md` and read what you emitted against them: headers,\ntool URLs, conditions, transfer nodes, node-scoped attachments. This file used to restate that list,\nwhich meant two copies to keep in sync, and they drifted. One instruction is safer than a duplicate.\n\nParity tests are the one item that is genuinely follow-up rather than part of a working migration. Say\nso plainly instead of implying the agent is test-covered when it has had one smoke run, and point at the\ncustomer's own transcripts, which is where real coverage comes from.\n\n## Step 6 — hand off, because \"runs\" is not \"good\"\n\nThis skill's finish line is deliberately low: a conversion that runs. Say that plainly and name who\nanswers the next question, rather than leaving the user thinking a smoke test was a quality review.\n\n**But do not hand a scorecard to someone who has just arrived.** `agent-review` is an operator's tool.\nPointed at a fresh migration it returns a page of structural findings that read as a verdict on the\ndecision to switch — and it lands right after the audit and the test results, which is three documents\nin a row telling a new user their agent is broken. That sequence is how a migration gets abandoned at\nthe last step. So gate it: run it when the user asks, or once they have confirmed the agent does what\nthey need. Not by default, and not in the same breath as the report.\n\n1. **`agent-review`** — scores prompt quality and workflow structure, which is the axis this skill\n defers on purpose. Right for an operator tuning an agent they already trust.\n2. **`agent-simplification`** — acts on the structural findings. A faithful one-node-per-source-state\n port is exactly its input, and it proves behaviour is preserved with a ground-truth suite rather\n than asserting it.\n3. **`architect-review-live-calls`** — once real traffic exists, finds what is actually failing in\n production. Nothing before this point can tell you that.\n\n**When the review does run, it is scoring the rebuild, so a bad score is a real finding.** You already\nmade the structural calls `agent-simplification` would have made, which means a pile of structural\nfindings says the shape was wrong — not that the review is being unfair to a migration. Do not reach\nfor \"it's a migration\" to excuse a score you earned.\n\nThe exception is if someone points a review at the **port**. That is supposed to score badly: it\nmirrors the source's topology because that is what fidelity means, and the findings are a backlog\nrather than defects. Refactoring them away before anyone has confirmed the new agent matches the old\none destroys the only thing the port was for.\n\n## Reference files, and when each one is due\n\nFour files matter to any one migration, and three of them are not optional reading. The point of the\nsplit is that each is short enough to actually read at the moment it applies, rather than skimmed as a\nwall up front.\n\n| File | Read it | What it holds |\n|---|---|---|\n| `reference/<platform>.md` | at step 1, once you know the platform | construct-by-construct mapping, the genuine gaps, export shape. Read only yours — you do not need the other two. |\n| `reference/traps.md` | **before step 4**, and again while authoring each surface | every way a migrated agent breaks silently, grouped by whether you are authoring tools, nodes, or config |\n| `reference/expressions.md` | **before you write any condition** | what can be expressed at all, why a carried-over condition inverts, and the guard builders to emit |\n| `reference/` | **before you write the first payload** | a complete agent and a complete webhook tool, every field populated, plus the jq recipes for anything they do not show |\n\nThat last one is the plugin's shared example directory, not this skill's. Go there rather than\nlooking for a sample config in the working repo: the one you find may be stale, may be a scratch file\nfrom another session, and in an install outside ElevenLabs does not exist at all. Read it as a field\ncatalogue — it shows where each field lives and what shape it takes. Do not start a migration by\ncopying it, or you ship a maximalist config whose settings nobody chose.\n\n`traps.md` and `expressions.md` are ElevenLabs-side, so they apply whichever platform you came from.\nIf you are about to author a condition or a tool and have not read them, you are about to make one of\nthe mistakes in them — they exist because a real migration hit these with the mapping table open.\n"
}SHA-256: 24c9eed55dd18cb72903f9f864db12d418a602c7cb132bb08e24cbba48e3d8cd