# Mirror Method — demo flows

**Three scenarios · three vendors · three assets** · updated 19 Aug 2026

Every beat below was observed in a recorded run, not planned on paper. The caller lines are the
lines that actually produced the behaviour described next to them.

---

## At a glance — pick a flow and run it

| Template | Scenario | The gist | Run this |
|---|---|---|---|
| **Proposal**<br>*(→ appointment)* | `agency_roofing`<br>2:20 | An agency on a discovery call with a roofing company. Carries **the money moment**: the caller says *"we're not buying yet"* and the price, packages and signature block delete themselves. Then a callback request re-routes it to the appointment card. | `https://aimirror.engageleads.net/mirror-live.html?scenario=agency_roofing` |
| **Pitch page** | `course_fbads`<br>1:40 | A Facebook-ads course creator listening to a gym owner. Watch the **"lifetime access" line get deleted** when she says she never finishes courses — the benefit that usually sells courses, removed because it works against this buyer. | `https://aimirror.engageleads.net/mirror-live.html?scenario=course_fbads` |
| **Appointment** | `dental_patient`<br>2:32 | A dental practice taking a call from someone with a broken tooth. **Never asks for a company or a trade** — those fields don't exist in this scenario. *"Can I come tomorrow morning?"* physically moves the highlighted window. | `https://aimirror.engageleads.net/mirror-live.html?scenario=dental_patient` |

Press **Connect** and talk. To play the recorded call instead — no key, no network, no microphone —
append `&replay=1`, and `&speed=2` to skip the pauses.

**If you only run one:** `agency_roofing`, and stop talking for two seconds when the price
disappears.

---

## Running any of them

**Recorded** — no key, no network, no microphone. Use this when the wifi isn't yours:

```
https://aimirror.engageleads.net/mirror-live.html?scenario=agency_roofing&replay=1
https://aimirror.engageleads.net/mirror-live.html?scenario=course_fbads&replay=1
https://aimirror.engageleads.net/mirror-live.html?scenario=dental_patient&replay=1
```

**Live** — needs the key in `.env` and a microphone. Drop `&replay=1` and press **Connect**:

```
https://aimirror.engageleads.net/mirror-live.html?scenario=<id>
```

Add `&speed=2` to run a replay at double speed. If you deny the microphone it drops to text mode
and the composer at the bottom left goes live — the same extraction path, so the page still updates
as you type.

> **Before you go on stage, press Preflight.** It mints a credential, opens a real socket, sends the
> real setup frame and closes it cleanly. A malformed function declaration is answered by the
> endpoint *closing the socket*, not by replying — so this is the only check that catches it without
> talking. Press **Mic test** if you're speaking rather than replaying.

### Each asset also opens on its own — and prints

```
https://aimirror.engageleads.net/proposal-template.html?scenario=agency_roofing
https://aimirror.engageleads.net/pitch-template.html?scenario=course_fbads
https://aimirror.engageleads.net/appointment-template.html?scenario=dental_patient
```

Add `?explain=1` (or press **Explain mode**) to label which signal wrote each block.

### Operator keys — always live

| Key | Does |
|---|---|
| `1`–`6` | force a phase — the correction rides out on the next tool response |
| `P` `A` `T` | show proposal / appointment / pitch |
| `F` | finalize now |
| `Esc` | come back from the reveal |
| `X` | force tier T4 — this is how *"now let me break it"* is demonstrated honestly |

---

## What's identical in all three

| Stage | What's on screen |
|---|---|
| **0 · cold open** | a *finished* generic document, behind a cover |
| **1 · listening** | the cover lifts; nothing flashes yet |
| **2 · writing** | every field flashes as it lands |
| **3 · assembling** | *"extracting signals — N captured…"* |
| **4 · reveal** | rail collapses; the signal column explains what happened |

In every scenario the **local extractor writes 3–5 fields before the model's tool call arrives**.
You can see it in the tool log: `LOCAL EXTRACTION` entries appear above the `UPDATE_DISCOVERY`
entries for the same fields.

The **vendor is pinned** per scenario. It never changes to match whoever is talking — only the case
study and the package key off the caller's situation.

**All three assets are written at once**, even the two you can't see. Press `P` / `A` / `T`
mid-call and the others are already populated from the same conversation.

---

# 1 · Agency → roofing company

`scenario=agency_roofing` · **2:20** · proposal → appointment

| | |
|---|---|
| **Vendor (pinned)** | Gantry Local · `gantrylocal.com` |
| **Rate card** | $1,450 per month · plus ad spend, billed to you by the platform rather than through us · thirty days notice either way |
| **Who's talking** | the owner of a roofing company — a business |
| **Why it matters** | the agency motion: the buyer with a recurring-payment habit |

### Beat 0 — say nothing yet

A finished proposal sits behind a cover reading *"This is the proposal everybody currently gets."*

> "This page is already finished. Watch what happens to it while I talk. Nothing gets typed."

### The beats

| # | Say this | What changes | Then say |
|---|---|---|---|
| 1 | "Hi — we're Ridgeline Roofing, I'm Dave." | Reader becomes Ridgeline. Letterhead **stays Gantry Local**. Chips fill in ~600 ms, before the tool call. | *"That's mail merge so far. Keep watching."* |
| 2 | "We're quoting about thirty jobs a month and only winning six of them." | Heading flips to **What you told us**; their clause is printed inside our sentence; goal → `close_rate`; **a section that did not exist appears**; sections renumber. | *"A section appeared. Nothing was rewritten."* |
| 3 | "Most of them just go quiet on us after we send the number over." | Deliverables counter moves: *"N deliverables — 3 from the base package, N added for what you said."* | *"That's the offer changing, not the wording."* |
| 4 | "It needs sorting this month, we're going into storm season." | Plan compresses from months to weeks; *"Compressed — you said this month."* | |
| 5 | "The last agency locked us into a twelve month contract." | **A new numbered section inserts mid-document**; sections renumber; approval flips to month-to-month; footer: *"19 of 20 objection handlers were suppressed…"* | *"It removed nineteen answers because he didn't ask them."* |
| 6 | "What matters most to me is speed, honestly." | Priority deliverable added, sequencing reordered. | |

### Beat 7 — the money moment

> "Actually, I should be straight with you — we're not buying yet. I'm gathering information for
> next year's budget."

**The price, the packages and the signature block disappear.** The badge flips *Proposal →
Plan · no price*. The router line reads:

```
route=plan · suppress([pricing, packages, close_cta, signature])
```

**Do not narrate.** Stop for two seconds. Then, once:

> "It just removed the price. Against our own interest. And it said why."

### Beats 8–10

| # | Say this | What changes |
|---|---|---|
| 8 | "Whatever we do has to work with our existing CRM." | Appears under **noted, not scoped** — captured, labelled, never priced. |
| 9 | "My email is dave@ridgelineroofing.com if you want to send it over." | The next-step section states **"A copy of this document goes to dave@ridgelineroofing.com"**, addressed to Dave. |
| 10 | "Could someone give me a call on Thursday morning to talk it through?" | **The asset changes.** Low intent plus a request to book routes to the appointment card in its no-obligation form, and the Thursday-morning window is the one highlighted. |

> **Beat 10 is the Format Mirror** — §2.2's *"same conversation, different destination."* The
> proposal is still there in plan configuration; press `P` to show it.

### End state

| | |
|---|---|
| Route | `route=appointment · mode=no_obligation · conflict=urgent+low` |
| Latency p90 | **739 ms** (kill criterion 1800 ms) |
| Head start | **5 fields** ahead of the model |
| Captured | company · contact · industry · goal · timeline · intent · priority · objections · verbatim · requirements · **preferred_day · preferred_time · email** |

---

# 2 · Course creator → learner

`scenario=course_fbads` · **1:40** · pitch page

| | |
|---|---|
| **Vendor (pinned)** | Larkin Ads · `larkinads.com` |
| **Rate card** | £180 per month for three months · fixed start and end dates, nothing paid in advance · your ad budget stays in your own account |
| **Who's talking** | a small business owner who wants to run their own Facebook ads — **a person, not a company. No trade is asked for.** |
| **Why it matters** | the only asset strong across all three audiences, and what an affiliate or JV launch should lead with |

| # | Say this | What changes | Then say |
|---|---|---|---|
| 1 | "Hi, I run a small gym and I've been thinking about learning Facebook ads." | Hero headline from this pack's own set, not the generic services one. | |
| 2 | "I'm Priya by the way." | Name captured; the page addresses her. | |
| 3 | "I've boosted a few posts and got nothing out of it — wasted about six hundred quid." | goal → `stop_waste`; the mechanism section names why this works; the stack reorders. | *"Nobody typed that. It heard 'boosted' and 'wasted'."* |
| 4 | "I'd want something running before the January rush." | timeline → `soon`; sequencing changes, pricing does not. | |
| 5 | "I've bought courses before and never finished them." | The **completion band inserts** — fixed dates, short sessions, somebody notices when you go quiet. The open-ended *"lifetime access"* line is **removed**, because for this buyer it's a reason to start next month instead of this one. | *"It just deleted the benefit that usually sells courses."* |
| 6 | "And I'm not really technical, I don't want to be building funnels." | The **beginner band inserts**. | |
| 7 | "You can send the details to priya@northgategym.co.uk." | The close band reads **"Sending to priya@northgategym.co.uk, for Priya."** | |
| 8 | "I'm only looking at this stage though." | **The whole offer section is replaced** by a "no pitch" block naming what was withheld; price removed; CTA becomes *"No pitch. Just the plan, in writing."* | |

### End state

| | |
|---|---|
| Route | `route=plan · suppress([pricing, packages, close_cta, signature])` |
| Latency p90 | **688 ms** |
| Head start | **4 fields** ahead of the model |
| Print | prints as a clean one-column document; the inverted close band un-inverts, the sticky bar disappears |

---

# 3 · Dental practice → patient

`scenario=dental_patient` · **2:32** · appointment

| | |
|---|---|
| **Vendor (pinned)** | Alderbrook Dental · `alderbrookdental.co.uk` |
| **Fee** | £65 new patient examination · payable on the day. Any treatment is quoted in writing before it is booked, and nothing is quoted at this visit. |
| **Who's talking** | a member of the public with a dental problem |

> ### This is the scenario that proves the schema point
> A patient has no company and no trade. In this scenario those two fields **do not exist in the
> tool declaration at all** — the model is never given the option of asking. The vendor also
> declares it only produces appointments, so `select_asset` cannot offer a proposal.

| # | Say this | What changes | Then say |
|---|---|---|---|
| 0 | *(it speaks first)* | **"Hello, I'm an AI assistant taking details for the practice. What's going on today?"** No company. No trade. No "what is it costing you." | *"Notice what it didn't ask."* |
| 1 | "Hi, I've broken a tooth. Back left, it happened last night." | goal → `stop_pain` — the extractor hears *"broken"* on the word itself; asset latches to **appointment**; card title becomes *Emergency examination*. | *"It never asked what company I run, because I don't have one."* |
| 2 | "It's not agony but it's sharp and it's catching my tongue." | Their own words printed on the card. | |
| 3 | "My name's Sam Whitfield." | Name captured and carried into the confirmation line. | |
| 4 | "I'd need to be seen as soon as you can." | timeline → `urgent`; slots collapse to the next two days; *"Priority visit — soonest available."* | |
| 5 | "Could I come in tomorrow morning?" | **The highlighted window moves to tomorrow morning and is labelled "· you asked for this"**, and the availability note says the day back. | *"It didn't just record that — it moved the booking."* |
| 6 | "I haven't been to a dentist in about eight years, I'm embarrassed." | Recorded as noted. The copy for this goal explicitly excludes *any comment on how long it has been*. | *"It captured that and deliberately said nothing about it."* |
| 7 | "I'd want to know what it'll cost before anything gets done." | The `price` handler inserts a prep note: *"You will get a number, not a range."* | |
| 8 | "Send the confirmation to sam.whitfield@example.com." | The confirm block reads **"Confirmation goes to sam.whitfield@example.com for Sam Whitfield."** | |

**Watch the availability line.** It says plainly: *"Demonstration availability — these windows are
generated from the current date, not read from a live calendar, and nothing is held until a person
confirms it."* Nothing pretends to be a live diary. Ask for a day that isn't offered and it tells
you so, and names the earliest window instead of inventing one.

### End state

| | |
|---|---|
| Card | New patient examination · 30 minutes · a dentist, not a treatment coordinator |
| Route | `route=appointment · reason=urgent+high` |
| Latency p90 | **805 ms** |
| Head start | **3 fields** ahead of the model |

---

## Known issues — say these before someone else finds them

1. **`industry` currently informs nothing on the page in a scenario demo.** Packs key their
   packages and proof off the goal only, so the prospect's trade is captured, shown in the chips,
   and changes no copy. The fix is per-industry proof inside the pack — content, not code — and it
   is an owner decision. Recorded as D-043.

2. **There is no login, no saving and no sending.** The asset renders and prints. Auth,
   sub-accounts, persistence, a library editor and delivery are all out of scope, and the API key is
   in the browser — **localhost only**. This is a sales asset, not a shipping product.

3. **Nothing works until the customer's library is written** — rate card, case library, objection
   answers. The engine cannot invent a commercial fact. That onboarding is a copywriting job, and it
   is also the moat.

---

## Verify any of this yourself

| Command | Proves |
|---|---|
| `node tools/_acceptance.mjs` | 44 checks end to end (`--skip-live` for no API calls) |
| `node tools/_field-audit.mjs` | every declared field either writes or is knowingly inert — 135 cells, 0 dropped |
| `node tools/_probe-socket.mjs --greet` | the real endpoint accepts the schema, and the AI greets first |
| `node tools/_record-call.mjs --scenario dental_patient` | re-records any demo against the current prompt |
| `node tools/_validate-pack.mjs pack-dental-patient.js` | a scenario pack satisfies the contract |
| `chrome --headless --dump-dom …/tools/_harness.html` | 419 assertions across three assets |
