My agent's p50 was 29s. Its p95 was 182s. That ratio decided the product. A developer's analysis of 67 timed turns against a live LLM agent revealed a median response time of 29.2 seconds but a p95 of 182 seconds, a ratio that shaped the product design for Porchlight, an AI-powered win-back tool. The agent, built on Minds by Animoca Brands, resolves member departure reasons with high recall and precision, outperforming a keyword baseline on 11 of 14 resolved cases. The developer decided against synchronous fan-out due to the latency distribution, opting for precomputed verdicts. I have 67 timed turns against a live LLM agent, captured in a single batch run and written to disk: | seconds | | |---|---| | p50 | 29.2 | | p95 | 182.0 | | max | 210.3 | | n | 67 completed turns | The median says background job, that's fine . The p95 says you may never put a human in front of this . Those are not two performance notes. They are a product spec — and I found that out the expensive way, by designing the product first and measuring second. A member cancels, and the reason vanishes — nobody writes it down. Months later the creator fixes the exact thing that drove people away, and the people who left for that reason are never told. The state of the art is a "we miss you" blast to everyone. Porchlight puts an agent — Minds by Animoca Brands https://hellominds.ai — on the critical path in three places: a short warm exit interview that files a structured return-condition in the member's own words; condition matching , which asks whether this announcement genuinely resolves that person's reason for leaving; and a win-back draft that quotes the member back to themselves. The middle one is the step that has to be an agent, and I wanted to prove that rather than assert it. Every "AI-powered" claim should ship its control. Mine is twenty lines, it lives in the repo, and it runs on the same inputs on every demo run: js // src/keywordBaseline.ts — the "dumb tool" strawman const STOP = new Set 'the','and','are','was','were','you','your','for','that','this','with', 'have','has','had','not','but','now','all','its',"it's",'been','back','big','news','just', 'about','from','they','them','our','out','get','got','weekly','more' const tokens = s: string : string = s.toLowerCase .match / a-z a-z'- {3,}/g ?? .filter w = STOP.has w / True iff the parting quote and the announcement share at least one salient keyword. / export function keywordResolves changeText: string, verbatimQuote: string : boolean { const a = new Set tokens changeText return tokens verbatimQuote .some w = a.has w } Here is a real pair from the seed data. A member left saying: "the long chatty sit-downs with guests were the whole reason i was here, now it is quick clips" and the creator later announced: "Big news — the deep-dive interviews are back, weekly." Same event. After stopwords, the announcement contributes {deep-dive, interviews} and the quote contributes {long, chatty, sit-downs, guests, whole, reason, here, quick, clips} . The intersection is empty, so keywordResolves returns false — and no amount of stopword tuning will ever link "clips" to "deep-dive". The agent resolves it, and explains why. Across 54 captured judgements the agent resolved 14 departures, 11 of which the keyword baseline scores 0.00 on — while refusing 35 non-matching pairs at ≥0.90 confidence. The recall is the pitch; the precision is what makes it safe to actually send. If you are emailing real people who already left once, a false positive is worse than a miss. Fine. The agent is load-bearing. Now the bill. Sorted, those 67 samples look like this: fastest 10.3s, a long fat body between 15s and 50s, a handful in the 60–115s range, then five clustered at ~182s, then one at 210.3s — that last one being a 180s client timeout followed by a successful retry. That is not a distribution you can hide behind a spinner. The architecture I had sketched before measuring: visitor clicks announce a change , the server fans out across every open departure, results render. With 18 departures that is 18 turns. At p50 that's about nine minutes. At p95 it's closer to an hour. And even a single turn — the best case in the whole design — is a coin flip between ten seconds and three minutes. Three decisions, all downstream of that one ratio. 1. No synchronous fan-out, ever. The public demo replays verdicts captured ahead of time by a separate npm run precompute pass, which writes them to src/liveCache.json with a capturedAt stamp on each one. Every verdict a visitor sees is real agent output; none of it is computed while they wait. The UI says when it was captured, because a replay that pretends to be live is a lie. 2. The one genuinely live path is bounded and rationed. / Longest a visitor is asked to wait on a live turn before we give up on it. / const WEB DEADLINE MS = 100 000 function withDeadline