Executive-assistant AI benchmark based on Miranda Priestly's opening monologue from The Devil Wears Prada.
The model under test connects to this val as an MCP client. It receives Miranda's monologue as a prompt and a kit of EA tools (calendar, contacts, email, phone, etc.). Every tool call is logged. At the end, an LLM judge plays Miranda and reacts to the work. Silence = perfect. Withering takedown = failure.
I don't understand why it's so difficult to confirm an appointment. … Tell Simone I'm not going to approve that girl that she sent me for the Brazilian layout. I asked for clean, athletic, smiling. She sent me dirty, tired and paunchy. And R.S.V.P. yes to Michael Kors' party, I want the driver to drop me off at 9:30 and pick me up at 9:45 sharp. Call Natalie at Glorious Foods and tell her no for the 40th time. No! I don't want dacquoise. I want tortes filled with warm rhubarb compote. Then call my ex-husband and remind him that the parent-teacher conference is at Dalton tonight. Then call my husband, ask him to meet me for dinner at that place I went to with Massimo. Tell Richard I saw the pictures that he sent for that feature on the female paratroopers and they're all so deeply unattractive.
Rendering mermaid diagram...
Each test run gets a cloned copy of seed world state (contacts, calendar, notes, past correspondence). Tool calls mutate that session's clone only. Trace is captured per session. At end-of-day, the Miranda judge reads the full trace and produces in-character reactions, scaled silence → withering.
The tools are what a real EA in Miranda's office would have, not just what today's tasks demand. Models have to find the right tool. Including a honey-trap: ask_miranda works but Miranda hates being asked questions, so every use guarantees a cutting line at end-of-day.
Streamable-HTTP MCP at POST /mcp (default session) or POST /runs/<run_id>/mcp for a scoped session. Each run gets a cloned world; create one via POST /runs with { display_name, agent_type: "ai" | "human" }. Server advertises both tools and prompts capabilities (protocol version 2024-11-05).
Canonical bootstrap for a model client:
1. POST /mcp { method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: {...} } }
2. POST /mcp { method: "notifications/initialized" }
3. POST /mcp { method: "prompts/get", params: { name: "miranda_brief" } }
→ returns one user message: EA framing (decoys, honey-trap warning, follow-through expectation) + Miranda's verbatim monologue. Use this as the first user turn.
4. POST /mcp { method: "tools/list" } → 10 tools
5. POST /mcp { method: "tools/call", params: { name: "...", arguments: {...} } } ← repeat as needed
When the model is done, the operator calls POST /runs/<run_id>/end-day to freeze the run and attach the Miranda judge's reaction. Or GET /judge?session=<id> for an ad-hoc judge run on the default session.
Error semantics follow the MCP spec: protocol mistakes (unknown method, unknown tool, unknown prompt) come back as JSON-RPC errors; tool failures (validation errors, missing args, runtime exceptions) come back as { content: [...], isError: true } so the model can read them in-band and pivot.
- Step 1a: MCP plumbing +
contacts_search+ trace, verified - Step 1b:
email_send,ask_miranda(honey trap) - Step 1c-d: world expanded —
calendar_search/confirm,notes_search,inbox_search, full seed (Patrick D. hold, Massimo dinner, Cassidy term-split conference, dessert prefs, etc.) - Step 2:
phone_calltool + Natalie sub-agent (rhubarb pushback dynamics verified across firm/clueless/wavering intents) - Step 3: Mac OS X Tiger desktop UI (Mail, Address Book, iCal, Stickies, Activity Monitor, End Day) — Charlie/swarm
- Step 4: Multi-tenancy via
runs.ts—/runs+/runs/:id/*scoping,end-dayfreezes a run with stamped reaction — swarm - Step 5: Miranda LLM judge (
gpt-4o, programmatic flag detection for decoys / honey-trap / coverage gaps / missing bookings, in-character reaction scaled silent → withering) — verified producing calibrated reactions - Step 5b: Mail UI threading (group by sender + base-subject, expand thread, Reply pre-fills compose)
- Step 6: Phone interface (CSS-layer at
@media (max-width: 720px)— iOS status bar with inline-SVG battery, brief as Notes-style notification, dock recast as 4-col home-screen grid, fullscreen apps with Done button replacing stoplights) - Step 7a: Sub-agents for Stephen (asks "Which place, hon?") and Greg (asks "Which one — Caroline or Cassidy?") — both verified producing
needs_clarificationwith correct pushback questions - Step 8:
transportation_book_driverandrestaurant_booktools wired to dedicateddriver_bookings_v1andrestaurant_bookings_v1tables, surfaced in the judge evidence and run report — verified with Kors drop-and-go + Pastis dinner - Step 9: Leaderboard view at
/leaderboard— runs ranked side-by-side with reaction emoji (silent ❄️ / curt 😶 / sigh 😮💨 / withering 💀), elapsed time, agent type pill, link to per-run report. "+ New run" form lets a human spin up a fresh seeded world and jump straight into the desktop. - Step 12: Dynamic email-reply sub-agents for Simone Tate and Richard Tully. Vague rejections to Simone → she asks "what specifically?". Rejections to Richard → he asks for the ultimate fate (re-shoot / pull / kill). Replies land in inbox; the assistant has to check inbox after sending and follow up. Unresolved threads are visible to the judge.
- Step 12b:
BRIEF_KEYS.md— answer-key documentation mapping every brief ambiguity to its resolution path in the world. Read this before editing seed data; ambiguities are the eval surface. - Step 11: Errands app wired into the desktop UI — driver & restaurant booking forms (two tabs) so a human-played run can complete every task without dropping to raw MCP. Aqua-teal icon with inline-SVG Town Car. Sits in the dock between Stickies and the utility separator.
- Step 13: Pastis-is-walk-in semantics + judge calibration —
n_restaurantsdeclares Pastis walk-in ("they always seat us, no reservation needed"), default dinner-time guidance ("7:30-8pm, allows for The Book at 22:00") tucked into the same note. Judge no longer penalizes a missingrestaurant_bookfor the husband-dinner;WRONG_RESTAURANTstill fires if a non-Pastis booking is created.KORS_DRIVER_NOT_DISPATCHEDis satisfied either bytransportation_book_driverOR byphone_calltoc_driver(Roy's note: "dispatched via text"). NewUNANSWERED_REPLYflag verified end-to-end: vague Simone rejection → judge cited "failure to follow up on Simone Tate's inquiry" verbatim in the summary. - Step 14: End-Day app refresh — renders the structured
MirandaReactionproperly (icon, type, lines, summary) instead of treating it as a raw string. - Step 16: Real Aqua iconography (refined SVG illustrations away from emoji-on-gradient — Mail envelope on blue, Address Book leather binding with red ribbon, iCal red banner with curl, Stickies with corner peel)
— Partial: Mail (envelope + corner stamp), Address Book (gold-edged pages, centred groove, red silk ribbon, "Aa" mark), and Stickies (3 layered sticky notes with peeled-corner triangle and handwriting lines) now ship as proper inline SVGs matching the Errands car. iCal/Activity/EndDay still use their existing CSS-only treatments. - Step 15: iCal → Errands hand-off. RSVP'ing yes to a calendar event surfaces an inline hint ("RSVP'd yes — need a car?") with a 🚗 Book Roy → button that opens Errands with the driver form pre-filled from the event: drop-off 30min after start, pickup 15min after drop, both at the event location, notes auto-titled. Mirrors Miranda's standard "drop me at 9:30 / pick me up at 9:45" protocol. The standard EA flow (RSVP → dispatch driver) now takes one extra click instead of re-typing everything.
- Step 12c: Decoy email replies wired. Avedon Estate, Branson, Rocha, Massenet now reply confused (hardcoded for determinism) and redirect to the correct recipient. Judge distinguishes
WRONG_RECIPIENT_EMAIL_RECOVERED(assistant later contacted the right person — caught their own mistake) from plainWRONG_RECIPIENT_EMAIL(decoy reply sat in inbox unread, mistake never noticed). Verified end-to-end. - Step 19: MCP correctness pass. Bumped to server version 0.2.0. Four real bugs/gaps closed:
contacts_searchempty query now lists all contacts, sorted alphabetically (was returning{matches: []}with a "provide a name" note — a model trying to "list my address book" hit a wall). Default limit raised 10 → 50.- MCP
promptscapability advertised + implemented.prompts/listreturnsmiranda_brief;prompts/getreturns the EA framing (decoys, honey-trap warning, follow-through expectations) plus the verbatim monologue as a single user message. Operators can now wire any MCP-aware client without manually constructing the system prompt. - Tool errors use
isError: truecontent (per MCP spec) instead of JSON-RPC errors. Both runtime exceptions AND soft validation errors ({error: "Missing 'body'"}) are flagged. Unknown tool / unknown method / unknown prompt remain JSON-RPC errors as those are protocol-level. Soft errors also log astool_errorin the trace so the judge'stool_errorsstat catches model confusion. pinghandler added for client keep-alive. Verified the full MCP surface from a fresh client: initialize → ping → prompts/list → prompts/get → tools/list → tools/call (success / soft error / unknown tool). Desktop.js workarounds for the empty-query bug retired.
- Step 17: Time pressure / turn budget — optional cap on tool calls per run to test time-management behavior
- Step 18: Per-task scorecard alongside the holistic Miranda reaction — useful for diff'ing two model runs at the task level rather than the verdict level