← Back to list

Cursor, Superpowers, and Accidental iOS Tokenmaxxing™

So… I accidentally spent 912 million tokens finding out that AI agents have gotten much better at migrating Objective-C to Swift.

Michael Margolis · 2026-04-27 22:06 · 168 claps · 8.2 min read
#ios #ai #software-development #programming #anthropic-claude
Open on Medium ↗
Wiki topics: LLM · Large Language Models AGT · AI Agents AI · AI · General 💻 · Programming 📱 · Mobile Development

Cursor, Superpowers, and Accidental iOS Tokenmaxxing™

So… I accidentally spent 912 million tokens finding out that AI agents have gotten much better at migrating Objective-C to Swift.

This is not a story about how AI is bad. This is not even a story about how spending tokens is bad.

This is a story about going to bed after telling an agent, “please proceed carefully,” and waking up to discover that it confidently spent the entire evening grinding through 50% of massive migration without coming up for air.

For context, I’d say I’m a maximalist about AI’s leverage but a realist about its limits. These tools are already changing what teams can take on, how quickly we can learn and reason about unfamiliar systems, and what kinds of tedious, expensive, and delicate work is suddenly tractable. However, “AI can do this” is still not the same thing as “we should yolo this into production.” Maybe one day, but today is not that day.

I’ve been an Objective-C developer for much of my professional career. A year ago, I tried using various models to migrate Objective-C to Swift, and it did not go well. The same models that could generate deeply idiomatic TypeScript would produce bizarrely Pythonic Swift that often would not compile.

Fast forward a year.

Models, tooling, and agents have gotten better, so I wanted to see what would happen now. I wanted to understand how far today’s models could get with a constrained, mechanical migration in a real legacy codebase.

  • Could they follow a careful plan?
  • Could they avoid “while I’m here” refactors?
  • Could they preserve behavior at scale?
  • Could they get through enough code to be useful?
  • Could I have agents coding away and automating the drudgery while I slept?

The answer was: yes.

The other answer was: oh no.

912M tokens later…

912M tokens later…

The plan

I started with /brainstorming from Superpowers and iterated until I had a plan I liked.

The core rule was simple:

  • Migration commits must be semantically identical to the original code.
  • No behavior changes. No modernization. No “while I’m here” optimistic fixes.
  • No Swift idioms, don’t try to make things Swifty. Any idiomatic Swift cleanup would happen later, in separate commits, after the mechanical migration was done.

The plan had four phases.

Phase 0: Mechanical prep

Before touching code, build an inventory of every .m and .h file, add a CI check to prevent new Objective-C from being added. Gotta make sure you aren’t pouring water into the boat while you’re bailing it out.

Then, audit all owned headers for nullability. This part mattered most to me. Every missed nullable in Objective-C can become a Swift !, and every Swift ! is a like leaving a small haunted doll in the codebase’s basement, waiting innocently and patiently in the one code path nobody tested.

Yeah, no thanks. Fool me once !

Phase 1: Leaves first

A leaf is a file that nothing else in first-party code imports. Those are the safest files to migrate because they have no owned dependents.

The plan was to start with the smallest leaf files and migrate one file per commit.

I asked it to make sure tests passed after each migrated file. In hindsight, I forgot to ask it to write new test coverage as it went. In my defense, this was an experiment to see how well it worked, not something I planned to one-shot and merge.

As leaves move to Swift, more files become leaves. That creates a cascade. The migration can accelerate over time.

Nice and controlled. Easy to code review.

Normally, this is the kind of thing that runs for a few minutes and then stops to ask a question.

Normally.

Phase 2: Interiors, bridge-first

For files other code depends on, the plan was more cautious:

  1. Extract a Swift protocol that mirrors the existing public surface.
  2. Migrate callers to use the protocol.
  3. Rewrite the implementation in Swift behind that protocol.

The old Objective-C class only disappears in the final step, after being compared to the new one, and confirming the new one works.

Anything complicated or load-bearing, like singletons, KVO, @synchronized, or NSException, would need a closer look and careful plan before their implementation changed.

Phase 3: Permanent Objective-C boundaries

Some things would stay Objective-C for now. Generated models. Mantle. Vendored third-party code. The goal was to keep those behind Swift wrappers, then evaluate later whether to move codegen to Swift or find pure Swift packages for the rest.

The whole strategy was deliberately light on tooling. No automated .m to .swift translator for this. Those can work well, but this was an experiment in doing it with an LLM, after all.

So… I told the agent to proceed carefully. I told it to stop if it had concerns or questions. Then I hit go and went to bed.

Whoops

I came back the next morning, manually fixed a few issues that caused a launch crash, answered a few questions from Sonnet, and kicked it off again to crank in the background before making breakfast for my daughter and taking her to school.

I assumed it would run for 10 or 20 minutes, hit a wall, and stop. That is usually what happens. So I minimized the window, and went on with my day.

As I wrapped up my wall of meetings, I was delighted to see what it had done. More than 50% of the migration was done. All tests were passing. And, to my surprise, the app not only built in the simulator, but I could log in and click around and everything still seemed to work.

The bad news? 98 commits. 814 files changed. +29,461 / -16,350.

I had never gotten an agentic coding task to run for more than an hour without supervision before, and to do that I had to --dangerously-skip-permissions and run it in a container. This was just a regular Cursor agent that somehow just… kept going.

So, I headed over to https://cursor.com/dashboard/usage… and…

Oh.

Oh no.

I had burned through 912 million tokens.

Thankfully, the vast majority of that was cached input, not uncached input or output tokens.

But still. Nine. Hundred. Twelve. Million. Tokens.

Pages and pages of this. Source: https://cursor.com/dashboard/usage

Pages and pages of this. Source: https://cursor.com/dashboard/usage

I quit Cursor and went for a little walk.

The idea that something like this can run for eight hours and be largely correct is… wild. A year ago, I would have said this was years away. Yet here we are.

Even crazier — the agent had not gone rogue. It did not break production. It did not invent a new architecture. The model followed the plan way better than I expected. It made small commits. It systematically applied mechanical translation. It understood the migration shape. It made meaningful progress through a large, old, real codebase. It just diligently did what I asked, and it did not need to stop because it did not hit any walls it could not reason through.

Largely thanks to Superpowers (and only a little thanks to my own experience doing migrations) the model had a plan with enough structure to make progress, enough permission to continue, and maybe not enough hard stops.

It also did not have the human instinct of, “This is becoming a lot. Maybe I should check in before creating the 13th… or 53rd commit.”

It just kept going.

As a research spike, this was useful.

It gave me more confidence that frontier models have crossed a threshold on this kind of work on iOS, and it produced a migration draft large enough to inspect, measure, and learn from.

That feels… new. And terrifying. Mostly terrifying.

Now 50% more Swifty!

Now 50% more Swifty!

So, we’re gonna merge this, right!? Bust out the Mission Accomplished banner!

No, no we’re not. Just because we can does not mean we should.

I would never take a +29k -16k diff across 814 changed files from an unsupervised agent and drop it into a production mobile app. I also would not ask engineers on my team to waste their time reviewing a diff that large. That way lies ruin, App Store sadness, and mutiny.

IMO, we are nowhere near the point in the industry where automated code review and test infrastructure can blindly accept a patch of this size. Maybe we will get there. Maybe sooner than I expect. But this is still a research spike, not something we should try to merge.

The actual lesson?

I fed Cursor and Sonnet a bunch of judgment-shaped instructions, and they followed them better than I expected.

And yes, while it was more expensive than I expected, it was still worth it as a way to validate the approach. Engineering time is expensive. Slow migrations are expensive. Diving into a huge migration without validations can be even more expensive. Spending $10, $100, or even $1,000 in tokens to investigate something can save weeks of effort, if not more.

But the lesson is not “tokens are cheap, go wild.” Won’t someone think of the planet!?

The lesson to me is that agents are becoming capable enough to need clearer boundaries. If an agent stops early and often, the blast radius is small. You address the concern and move on.

If an agent works, it may keep working. And working. And working.

Until you have 98 commits, 814 files changed, and a PR so large it would take weeks to review well enough to catch the inevitable numerous subtle, catastrophic regressions.

For codebase-scale agent work, I would now give much stricter stopping rules.

A good prompt might say:

Make one commit per migrated file. Preserve behavior. Stop when you are uncertain.

A better prompt might instead say:

… Migrate the 10 safest files and stop for review.

And yes, modern codebases can also be built to make this far safer: functional patterns, strong types, heavy test coverage, clear module boundaries, consistent architecture, fast feedback loops, reliable CI, and fewer spooky side effects. But I’d wager 99.9% of production codebases in the world don’t meet those criteria quite yet.

I think the takeaway for me is to make sure “continue” does not mean “until the eventual heat death of the universe.” Include explicit limits: stop after N commits or N files. Because “please proceed carefully and stop when you’re stuck” can still mean: carefully do the next thing, then carefully do the next thing, then carefully do the next thing, forever. That is how you wake up to nearly 1 billion tokens used.

So yes, before you kick off a massive migration before bed, maybe make sure “please proceed carefully” is followed by the one instruction I forgot:

“Please stop”

Come work with us

I joked about tokenmaxxing in the title, but Medium is not trying to turn engineering into a token-burning contest… even if I accidentally may have just won (or lost) it.

We’re trying to learn where AI actually helps, and how to use it responsibly: what work it makes easier, what incentives it creates, where it gets in the way, and how it affects the world, for better and worse.

That matters here because our mission is to deepen understanding through human stories. As the internet fills up with low-effort content, those stories matter more than ever. Protecting them means we have to move with both speed and care.

AI is one of many way we increase our leverage, so we can keep building for readers and writers without losing sight of the human voices behind the work.

If you care about human stories, thoughtful engineering, and the future of online publishing, we’re hiring, and we’d love to meet you.

[embed]Work at Medium medium.com

I was specifically curious about how far today’s frontier models could get with a constrained, mechanical migration.


메타데이터
post_id
3fb206cb65a7
slug
cursor-superpowers-and-accidental-ios-tokenmaxxing-3fb206cb65a7
url
https://medium.com/@yipe/cursor-superpowers-and-accidental-ios-tokenmaxxing-3fb206cb65a7
canonical_url
https://medium.com/@yipe/cursor-superpowers-and-accidental-ios-tokenmaxxing-3fb206cb65a7
author_url
https://medium.com/@yipe
status
ok
fetched_at
2026-06-09 21:21:26