Thirty engineers just rebuilt the Shopify app from React Native to Swift and Kotlin in 12 weeks. The app has more than 300 screens, home and lockscreen widgets, an Apple Watch app, complications, and Siri Shortcuts.
Agents wrote most of the code. If you have read our other posts in this series, that won’t be a surprise. The real surprising part is how much we had to change around the code.
Once agents started producing large amounts of code quickly, everything surrounding that code became more important: the architecture, tests, code review process, machines running emulators, and coordination between people.
We changed the way the app is built so agents could work inside a workflow with fast feedback, clear patterns, and checks that kept them on the rails.
The posts we’ve shared over the last few weeks covered the main pieces of that system:
- Native is now the future of mobile at Shopify
- Helix, the internal tool powering our migration
- Our remote development environment for agents
- Reflex: an architecture for building mobile apps with agents
This is about how those pieces fit together, and what we learned about setting up a workflow where agents can move fast while humans still validate, review, and approve what ships.
How we prepared the app for agents
Before we migrated screens, a small group of engineers prepared the system around the agents: the architecture, tooling, and the feedback loop.
At first glance, that can look backwards. If an agent can generate thousands of lines of code in an afternoon, why spend weeks beforehand writing the screens?
Because speed on its own gets you a lot of code, not a good app. The system around the agent has to tell it when the output is wrong, and the architecture has to steer it toward code that is clean, consistent, and performant. Left alone, agents won’t do that.
The work was not just architecture in the usual sense. We were completely rethinking how mobile apps should be built in the age of AI agents:
- Business logic that runs without a device for fast feedback loops
- The Reflex architecture and a CLI that can load any screen in milliseconds
- One architecture shared by iOS and Android
- Clear examples and .md files for every new screen to follow
- Repository guardrails that fail when the code drifts
For humans, architecture can live partly in people's heads. For agents, it cannot. It has to be visible in file structure, architecture.md files, skills, naming, state shapes, method signatures, CLI output, lint rules, test fixtures, and CI failures.
That changed how we built the app. We were still designing for engineers, but we were also designing for agents that need repeatable patterns, fast feedback, and very clear error messages.
Before this work, an agent had the same feedback loop as a human: build the app, start the emulator or simulator, navigate to the screen, look at the result by taking screenshots, analyze it, make a change, and try again. That loop takes several minutes per iteration, and agents are bad at every step that depends on visual navigation and context that lives outside the code.
After the architecture work, an agent could run most of the screen without starting a device. It could load a ViewModel from the terminal, trigger actions, inspect state, and run deterministic checks in milliseconds. That fast feedback loop is what made the migration possible at the speed we reached later.
We didn’t get faster by asking agents to take bigger swings. We got faster by making the work smaller and the feedback loop tighter.
How we organized the actual rewrite
About 30 engineers worked on the rewrite, organized in pods of two, with each pair owning an area of the app across both platforms. The two engineers in a pod paired most of the day, each one steering multiple agents at the same time. Agents kept working overnight and on weekends, so every morning started with a queue of PRs to review. We kept the pods small on purpose. Agents gave each pair the implementation capacity to cover a whole product area. What each pod needed was clear ownership, fast decisions, and shared context.
We didn’t organize the migration as one iOS team and one Android team. Each pair owned a product area end to end. Agents did much of the implementation, but humans still owned the architecture, product behavior, and final judgment about what was good to ship.
Consistency over platform-specific architecture
Our first principle was consistency. Consistency of architecture, so agents could create PRs that followed the same shape. Consistency between Android and iOS, so the same screen exposed the same state, actions, and behavior on both platforms. An iOS ViewModel for the orders screen looks like the Android ViewModel, whether you’re reading the Swift or Kotlin version.
We deliberately prioritized consistency over platform-specific architecture. Some of the code is less idiomatic than what an iOS or Android purist might write. We are comfortable with that tradeoff because of what we get in return:
- Every migrated screen becomes an example for the next one
- One PR contains the changes for both platforms
- An engineer who knows one platform can review a PR on the other
- Architecture guidelines are written once for both platforms
- Tests can run the same merchant journey across iOS and Android
This is also what makes the argument from our first post work in practice. Building native apps for two platforms doesn’t have to cost twice as much if the second platform follows the same architecture. That only works if both sides keep the same shape.
In an agentic workflow, consistency is not aesthetic. It’s infrastructure. It’s what lets humans compare intent across platforms, agents to reuse examples, and CI to detect drift before a reviewer has to find it by hand.
We enforce that consistency. CI fails when iOS and Android diverge in ways the architecture doesn’t allow.
Keeping agents on the rails
TL;DR: No vibe coding.
In our first post, we wrote that asking an LLM to one-shot a rewrite produces a huge amount of code that can’t be shipped. Guardrails are our answer to that problem.
With a strong architecture and the right checks, agents have clear directions and less room to improvise in places where improvisation creates maintenance cost. Without those constraints, an agent can produce code that compiles, passes a narrow test, and still leaves humans with a codebase nobody wants to own.
The system has to answer three questions for every agent-written change:
- Does it follow the architecture?
- Does it behave like the existing app?
- Can a human review the intent without reading a giant diff line by line?
Different parts of the system answer those questions.
The architecture guidelines, examples, lint rules, and divergence checks answer the first one. Helix and the legacy React Native app answer the second one. Small checkpoints, cross-platform PRs, and agent-first review answer the third.
The important thing is that the guardrails live in the workflow, not only in a document. A document explains why the rule exists; CI decides whether the rule was followed.
Agents need more than instructions. They need a way to discover when they are wrong.
The existing app was the spec
We tried spec-driven development first: write a spec for a screen, give it to the agent, review the result. It didn’t work.
The specs went out of date almost immediately. By the time the agent was working on the third checkpoint of a screen, the spec written for the first checkpoint was already incomplete or wrong. Nobody wanted to maintain a parallel description of behavior that already existed in the product.
For this migration, the existing app was a better spec than a document. The legacy React Native screen already described the states, actions, edge cases, empty states, loading behavior, and navigation. Helix reads that reference, splits the migration into small checkpoints, and verifies each checkpoint against the original app instead of against a long-lived written spec.
We asked agents to converge on an existing product instead of inventing a new one from scratch. For that kind of work, a live reference beats a written spec, hands down.
New features are different. When there is no existing screen to compare against, the rules may be foreign… That is our next challenge.
The lesson from the migration is narrower but very useful: agents don’t need a perfect first attempt. They need a reference, a small next step, and a fast way to check whether they moved closer to the target.
What are the new bottlenecks?
Once writing code was no longer the slowest part of the system, the bottleneck moved.
This is the part of agentic development the industry is still absorbing. A lot of the conversation is still about how fast agents can generate code. But great teams don’t ship generated code. They ship reviewed, tested, integrated, secure, maintainable software.
When code generation speeds up, every downstream constraint becomes more visible.
This looks a lot like Jevons paradox. When something becomes more efficient, we often consume more of it. That’s what happened to us here. Agents made implementation faster, but that didn’t mean the team had less work to do. It meant we could take on more work with the same team size: more checkpoints, fixes, parallel migration streams, review, CI, decisions…
Written code is no longer a scarce resource. The scarcity has shifted to the decision-making around which code should exist, and then safely getting it into the product.
With put these productivity gains back into the roadmap.
Laptops became a bottleneck
Agents still need machines. They need emulators for UI tests and visual checks. They need compilers. They need build caches and disk space. A laptop can’t run many emulators before compilers and emulators start competing for CPU, memory, and disk.
One engineer running three agents can saturate a laptop, so we built a remote development environment for agents. The agentic workflow only works if the compute model can keep up with it. Otherwise the agent is fast, but the machine becomes the queue.
Code reviews became a bottleneck
Agents write faster than humans can approve. Code that takes minutes to write can still take days to merge. It doesn't matter how fast agents are if humans can’t responsibly understand, review, and land the changes.
We still require human code review on every PR, so the reviewer judges intent, architecture fit, product. behavior, and whether this change belongs on a codebase that millions of merchant depend on.
The question we get the most is how pods of two people can review 100 times more code. The honest answer is that they cannot, not line by line. So we changed the order of review:
- Agents are the first reviewer. Every PR gets an adversarial review against our architecture guidelines before a human looks at it. Obvious issues should be fixed before the reviewer spends attention on the change.
- We changed the size and shape of the work. A PR is a small checkpoint, not a full screen. The size of a diff a human can read is a constraint we give to the agent. One PR contains both platforms, so the reviewer can compare iOS and Android side by side instead of reviewing the same behavior twice, days apart.
- The human is the last step. Instead of scrutinizing every line of code, their job is to judge intent, architecture fit, product behavior, and whether this change belongs in a codebase that millions of users depend on.
At the end of each day during this migration build, an agent went through every review comment left that day and used them to update our skills and Architecture.md. A comment made once on one PR became a rule for every PR after it. Each day the system got a bit better, and the number of changes requested per PR kept going down.
Merging became a bottleneck
We work through the queue with agents now. Agents rebase branches, fix merge conflicts, investigate CI failures, fix flaky tests, and help get PRs through the last mile. That work used to be annoying but manageable when each engineer had a smaller number of branches in flight. With agents producing more checkpoints, the merge queue itself became part of the product development system.
If agents increase the number of PRs, you also need to use agents to improve the machinery around PRs to move items on the merge queue. Otherwise the team just moves waiting time from implementation to integration.
Coordination became a bottleneck
Tracking work also got harder. Tickets stopped being the source of truth for everything. A bug gets reported in a Slack thread, someone points an agent at it, and the fix is up for review before anyone has had time to create a ticket.
Crashes don’t always need a human to start the loop anymore. We added a new self-healing loop that runs every 15 minutes and looks for new crashes. When it finds one, our Slack agent River, reproduces it in the remote development environment and opens a PR with the fix. The first time a human hears about the crash may be when the PR shows up for review.
Stories, tasks, and bug tickets were designed for a world where writing the fix was the slow part. That is not true anymore. The real problem now is multiple engineers, each with their agents, fixing the same bug at the same time without knowing it.
That’s one reason we split the team into small pods of twos that own main areas of the app. If an issue is in orders, the orders pod picks it up. If it’s in products, the products pod takes it. It’s not a perfect answer, and we haven’t found a tool that fully replaces the ticket board, but ownership by area keeps us from duplicating work today.
As you can see, every time we removed a bottleneck, the next one showed up right behind it. The agent itself is rarely the only problem anymore. The problems moved to machines, CI, review, merging, and coordination.
What changed for software engineers?
A software engineer's job is to solve problems. There are still plenty of problems to solve. The problems are just different than before, and a single engineer is now solving 10 of them at the same time.
Before, a large share of the work was manually turning intent into code. In this migration, agents took on much more of that mechanical implementation work. But the responsibility didn’t disappear, it just moved. Engineers spent more time shaping architecture, writing the patterns agents would copy, designing feedback loops, reviewing intent, debugging systems around the code, and holding a high quality bar.
The Reflex architecture, Helix, remote development environment, review process, the ownership model were all part of the same answer: build a system where agents can do useful work without turning the codebase into a mess.
This is a different engineering job compared to a few months ago, but the scope remains massive and exciting.
What changed for our users?
The migration was not only about how we build the app. It also changed what merchants get.
We moved Shopify Mobile from a React Native app running on the New Architecture to fully native Swift and Kotlin apps, without asking our users to do anything. Our users stayed logged in and preferences carried over. From their point of view, the app updated and kept working.
But under the hood, the app changed a lot:
Launch time dropped by about 50% on Android (P50 from 1.71s to 0.87s) and 14% on iOS (P50 from 0.70s to 0.60s).
The Android binary became 40% smaller (from 80.4MB to 47.6MB)

Android Memory usage is roughly half of what it was in React Native (P50 from 470MB to 200MB)

iOS peak memory dropped by more than 70% (P50 from 730MB to 190MB)

The app now feels fully native. We can use platform transitions, smoother animations, and native page behavior without fighting the abstraction layer.
When new platform capabilities arrive, we can support them directly. For example, we were able to support iPhone Duo on day one.
Stability improved too. On Android, crash-free sessions moved from 99.66% on React Native to 99.98% on native. iOS stayed above our 99.95% target. We also added a self-healing process so agents can detect and fix crashes automatically.
And the app is now agent-ready: every screen has state and actions exposed through an introspectable ViewModel. Using the Reflex architecture and the CLI, Agents can test much of the app without launching a simulator, and anyone at Shopify can use River to build and validate a mobile change.
That is the result we wanted: a faster app for merchants, and a codebase that is easier for humans and agents to keep improving and working with.
What we’ve learned
A few things we would tell a team starting down this path:
- Spend the time upfront to give agents a fast way to test your app. Business logic that runs without a device and the Reflex architecture with the CLI to drive it were foundational for us.
- Make architecture executable. If the rule matters, make it visible in examples, Architecture.md, tests, lint rules, or CI.
- Prefer small checkpoints over big agent-generated PRs. Reliable convergence beats a perfect first attempt.
- Use the same architecture across platforms when the product behavior is shared. Consistency is what makes agent output reviewable. This is true for both business logic and UI patterns.
- Let agents review first, but keep humans as the final decision makers.
- Treat review capacity as a real system constraint. The diff size a human can understand should shape the work you give to agents.
- Expect the bottleneck to move. When code gets faster, machines, CI, review, merging, and coordination become the next constraints.
- Be explicit about ownership. When work moves faster than tickets, unclear ownership turns into duplicate work.
Conclusion
The clearest lesson from us is this: Now that agents can write a lot of the code, engineering judgment moves to the pieces around those agents: the architecture they follow, the feedback loops they use, the checks that fail when they drift, the review process that decides what ships, and the ownership model that keeps people from stepping on each other.
That is what we rebuilt. Not just the mobile app itself, but the way we built it.
So, is our app vibe coded? No. In fact, our code has never been more consistent with our desired patterns. We spent a lot of time crafting the right architecture and putting guardrails in place so agents generate code with the shape we want.
Agents do not magically produce good or bad software. They amplify the environment you put them in. The product is still the result of the patterns you choose, the architecture you design, the checks you enforce, and the judgment you apply before anything ships.
Everything here might look outdated in a few months. When it does, we will share what changed.
