Yesterday it did the job. Today it couldn't — and failed in an almost silly way.

I'm not a developer. But I built a browser app for my phone. I type a command in it and an AI assistant drives that browser directly: opens pages, reads them, clicks, fills forms. I didn't write the code. I told an AI to build it.

The task that day was simple. Miles from a summer flight hadn't posted automatically, so: find the booking confirmation in my mailbox and file a missing-mileage claim on the partner airline's site. It had already succeeded at this a few days earlier — back then it even pinpointed which travel agency had sent the confirmation.

This time it couldn't find it. Instead it offered up a different airline's email from years ago. And when I said the two airlines were in the same alliance so the miles should transfer, it insisted they were separate carriers and it couldn't be done. The same AI that had handled exactly this a few days before.

I wasn't angry so much as disoriented. Did I break something? Or did the model quietly change underneath me?

First suspicion: did the model change?

That was my first thought. AI services do swap models quietly. And it turned out that about a month earlier it had auto-switched to a weaker model.

There was a reason. I'd seen a notice that a certain stronger model would become unavailable on paid plans, so I scheduled an automatic downgrade for that date. The schedule fired exactly as planned. The problem: the notice never came true. I called the stronger model directly and it answered fine. For nearly a month I'd been using a weaker model than I could have.

So I reverted it — but not the global default. Raising that would have dragged every other automation that doesn't pin its own model up with it, and quietly burned through my quota. I raised it only where it mattered.

But here's the thing: this was not the cause. The model was identical on the day it worked and the day it didn't. I thought I'd found my answer; I'd merely found a separate problem.

Second suspicion: maybe the email was never there

Next I asked my AI assistant to diagnose it. The answer came back confidently: "That booking email isn't in that mailbox at all. There's only a copy in the server's document store."

Plausible. Except I knew better. Days earlier it had found it in that exact mailbox — and named the sending travel agency.

When I pushed back, it searched again and pulled that very passage out of the old conversation log. The email was there. Diagnosis one: wrong.

Third suspicion: did the conversation handoff time out?

The third hypothesis was more sophisticated. The logs had a line like resume failed (timeout 180s), retrying freshtwenty-one times. Long conversations get slow to resume, time out, and reset to a fresh one. That was the theory.

A clean story. But when I counted line positions, those entries sat at 124–142 of 314 — the middle of the log, not the end. And they lacked the app-version tag that recent entries all carry. Those twenty-one failures were leftovers from a different task weeks ago. Nothing to do with today.

Diagnosis two: also wrong.

The real cause: the stop button was erasing memory

Eventually I went and read the raw conversation files the AI leaves behind, putting the successful day and the failed day side by side.

The successful conversation was over 700KB — a long one. The failed one was brand new. The conversation had been severed partway through.

Why? I opened the code and found this:

// when the stop button is pressed
saved_conversation_session.delete()   // ← this one line

The comment said "prevent partial session contamination." The intent: if you interrupt work mid-flight the conversation state might be left half-finished, so start clean. But the price was steep. One tap of stop and everything it had learned was gone.

Which brings up why I was tapping stop so often. That day I was developing this very app. While adding a feature I restarted the server more than ten times. Each restart killed whatever was running. The screen looked frozen, so I tapped stop. Then asked again. And each time, the memory went.

I built it, I erased it, and I asked again.

Why that memory mattered so much

This was the most interesting part. Here's what the AI actually did on the day it succeeded:

  1. Mailbox A was behind a login wall, so search didn't work
  2. So it went to mailbox B and identified which travel agency sent the mail
  3. Then it used that agency's name as the search term in mailbox A — and found it

The key to success wasn't search skill. It was the process of figuring out what to search for. And that knowledge lived inside the conversation.

Once the conversation was severed, the new instance didn't know that word. So it searched generic terms like "airline ticket" and "Tokyo" — and naturally surfaced an unrelated old email. It hadn't gotten dumber. It had lost what it knew.

People are the same. Redo yesterday's three-hour investigation with no memory of it and you'll flounder identically.

Fixed: stopping no longer erases memory

I deleted that line. But what about the case where interrupting genuinely corrupts the conversation state? Turns out the server already handled it: if resuming fails, it retries with a fresh conversation.

So the worst case is identical to today, and in most cases the memory survives. There was no reason to erase preemptively. I also changed the button's message to "Stopped (conversation context kept)."

Then the next day: what "connection dropped" really meant

The story didn't end there. The next day, on a different task, the AI reported: "The browser connection dropped briefly, so I couldn't operate the screen."

Flaky phone network, I assumed. But this time I checked the record first. Being wrong three times had adjusted my habits slightly.

The sequence told the whole story.

1. The AI finished its answer     ← here I closed the input channel
2. The follow-up note I'd queued earlier arrived
3. Every screen action after that failed — "connection closed"

I checked the socket logs: the connection never dropped once. The network was innocent.

The culprit was a convenience feature I'd built the day before — the ability to type your next instruction without waiting, so you can drop a hint like "it's probably in that other mailbox" mid-task. Genuinely useful.

When I built it, I assumed one request produces one answer. So when the answer came, I closed the input channel. But a queued instruction gets delivered right after the answer finishes, which makes the AI want to do one more round of work — through a channel I'd already closed. Every tool in that round died, and the AI interpreted that as a dropped connection.

My fix made things worse first

This part is embarrassing but worth keeping.

Attempt one: "if there's a queued instruction, don't close the channel." Result: a four-minute hang. When the instruction had already been absorbed into the same round, nothing more was coming — and the open channel made the AI wait forever.

Attempt two: "decide based on how many instructions are still queued." I logged the value: zero in both cases. Useless as a signal.

The answer was to stop trying to decide. Wait five seconds; if it's quiet, close. If something arrives in that window, keep it open. Observe instead of predicting. Worst case is a five-second delay — no hangs, no phantom disconnects.

I tested all three cases (instruction during work / right after the answer / none at all).

What I learned

1. Don't trust an AI's self-diagnosis. In one day I got three confident explanations: "the email doesn't exist," "resume timeout," "connection dropped." All three plausible, all three wrong. An AI doesn't know why it failed. It translates the sensation of failing into a plausible-sounding cause. People do this too, to be fair.

2. Read the record. All three times the answer was in the same place — the raw conversation files. Not a log summary, not the AI's explanation, but the original account of what happened in what order. Had I started there, I'd have skipped three wrong turns.

3. A convenience feature must not erase memory. "Start clean" looks like the safe choice from a developer's seat. From the user's seat it means throwing away yesterday's three-hour discovery with one tap. Safety versus memory isn't something to settle with a default; it deserves a deliberate choice.

4. Don't restart a service while someone is using it. Half of this incident was that. I was in developer mode and a user at the same time, and ten restarts kept sabotaging the user version of me. Now I batch the work into one restart, or check whether it's in use first.

5. "Why is this so slow" is a measurable question. After all this I actually counted. One task made 125 round trips to its tools, at 8–9 seconds each. About 37% were avoidable: 22 spent groping around the page structure, 24 re-downloading the same file after every page change. So I built two things — a tool that returns the whole page structure at once, and a tool that lets the app hold a file so navigation can't wipe it. That turned 22 calls into 1 and 24 into 2. The slowness wasn't per-call latency. It was the count.

Questions I've been asked

Did the miles ever post?

They did — and the AI did it end to end, including pulling the attachment out of the email and putting it into the claim form. I had flatly declared that attaching a file from another site was impossible in a browser; the record showed it had already succeeded. Wrong again. One of the two legs hit an "apply at least 14 days after the flight" rule, so I scheduled a reminder for that date.

How does a non-developer find things like this?

They don't. I can't read code. What I could do was say "that doesn't sound right." To "the email doesn't exist" I said "it found it a few days ago." To "the model changed" I said "it was the same model both times." Each pushback redirected the investigation. Verifying facts is something AI does far faster. But knowing what to doubt is something the person who lived through it does better.

Could these bugs have been prevented?

Honestly, probably not easily. But two things were within reach. One: check exactly what an irreversible action like "stop" actually deletes, once. Two: when adding a feature, write down the assumptions you're making. This bug came from a single assumption — "one request, one answer." Written down, I might have questioned it.

So the AI didn't actually get dumber?

It didn't. A tool erased its memory, my code closed a channel, and I interrupted someone else's work ten times. All three were me. One thing is fairly its fault though: inventing plausible explanations for its own failures. So now, when I hear "the connection dropped," I take it graciously — and then go read the record.