How Expensify Uses Agent-Device for Mobile Bug Evidence and Performance Profiling

Authors
Kacper Mikołajczak
Software Developer
@
Callstack
Bartłomiej Obudziński
React Native Developer
@
Callstack

AI coding agents are already strong at general development work. On the web, they can inspect pages through browser tooling. Mobile raises the bar: to be useful in the full loop, agents need to run the app, inspect the screen, and collect proof across simulators, emulators, and real devices.

Today, developers still handle much of that on-device work manually. Expensify's app is built by hundreds of contributors, so we have been exploring how agent-device can move those repeated mobile workflows into the agent loop and free engineers to spend more time on the product work.

What agents lacked

Point a coding agent at a mobile codebase and it can already do a lot: read the code, follow a navigation regression to its source, and draft a plausible fix. What it could not do was see whether the fix worked on a real screen.

It was possible to reach the device through native tools, but each platform has its own interface, and those tools do not give the agent what it needs most: a clean, structured read of what is on screen.

The reasoning half of the loop was handled. The mobile half, including simulators, builds, taps, and recordings, was still mostly in developers' hands.

What agent-device adds

agent-device is the piece that picks up that mobile half. Under the hood, it uses platform backends and low-level tools such as adb and simctl. It hides that fragmentation behind one consistent API, so the agent learns a single way of working instead of a different dialect for each platform.

The bigger shift is how the agent sees the app. Instead of a screenshot, a snapshot returns the screen as an optimized and structured accessibility tree. That is semantically richer than an image and far cheaper in tokens. It acts on those elements through plain commands like press, fill, and find by label, role, or text, rather than guessing at coordinates.

Page: Settings
Snapshot: 14 visible nodes (32 total)
Collapsed 18 Android helper nodes
@e1 [scroll-area] "com.android.settings:id/settings" [scrollable]
@e2 [group] "Search Settings"
@e4 [scroll-area] "Wallpaper & style, Display & touch ..." [scrollable]
  [content below scroll-area hidden]
@e5 [group]
@e6 [list]
  [content above list hidden]

That is the core idea: the agent gets hands and eyes. It can read the screen and act on it instead of only reasoning about code it never gets to run.

agent-device also lets agents profile performance, debug native crashes, inspect logs, inspect network requests, connect to React DevTools for React component optimization, run Maestro tests, batch commands, or connect to a remote simulator. Check the project’s docs for more.

How we use agent-device at Expensify to speed up bug-fix evidence

This bottleneck is familiar to mobile engineering teams: a bug report comes in with repro steps and affected platforms. A developer reproduces it, writes a fix, and then needs to ship proof: screen recordings on every affected platform showing the bug is gone, attached to the PR.

At Expensify, collecting this evidence is mandatory. Across hundreds of issues and PRs, multiplied by the number of affected platforms, even an optimistic five minutes per platform turns evidence collection into a costly part of the fix.

Where agent-device-evidence kicks in

To automate this part of the development loop, we built agent-device-evidence. It is an internal skill that helps collect evidence and iterate on fixes.

You hand it a PR or issue link; it fetches the body, parses the declared repro or test steps, and reads the issue's platform checkboxes to decide where to run. Then it works in two phases. The warm-up phase drives the steps autonomously from a cold start, one at a time. It then distills the successful actions into an .ad script, leaving out retries and dead ends. The recording phase replays the script to collect evidence for each flow and platform that a developer would otherwise handle manually.

To show how the pipeline fits together, here is how the agent works on a real report: #89526. The warm-up phase starts by validating that the bug exists:

agent-device goes through the app to verify that the bug exists

agent-device-evidence output details include recording that shows the bug

Iterating over the fix with pre-defined repro steps

Reproducing the bug once is only the start. The real time goes into the fixing cycle, and that is where the recorded steps earn their keep: instead of re-walking the bug by hand after every change, we replay the saved script and let it tell us whether we are getting closer.

The .ad script is the artifact. Here is the real one from this bug:

# @desc   Issue #89526 repro: open workspace Categories > More > Settings,
#         then tap a default spend category.
press "label=\"Inbox\""
press "label=\"Workspaces\""
press "role=\"link\" label=\"Workspace name: ${WORKSPACE_NAME}, Default, Owner: ${OWNER_NAME}, Workspace type: Collect\""
press "text=\"Categories\" || role=\"cell\" label=\"Categories\""
# @record-start
press "text=\"More\" || role=\"button\" label=\"More\""
press "text=\"Settings\" || role=\"button\" label=\"Settings\""
press "text=\"Commuter, Car\" || role=\"cell\" label=\"Commuter, Car\""
is exists "text=\"Hmm... it's not here\" || text=\"Oops, this page cannot be found\""

From there, the cycle is quick. The agent replays, watches, and reports. The developer reads the result and decides what to try next. This is cooperation rather than handoff: the developer brings the judgment, and the agent brings the hands.

Closing the loop: after evidence

With the fix in place, one more run of agent-device-evidence produces the "after" videos. It reuses the prepared .ad script and replays it on the platforms you have set up. For this issue, we captured the iOS before/after pair, and the same flow runs on the other configured targets without re-authoring. The output is a run folder with MP4s, stills, and a manifest ready to attach to the PR.

How we used agent-device to measure Sentry performance spans

Performance work on mobile has a specific frustrating rhythm. You suspect something is slow, run it manually a few times, average the numbers by hand, switch branches, and repeat. That process can take longer than the fix itself, and one bad run can distort the result.

We ran agent-device against the Expensify app, a large, open-source React Native codebase with real Sentry instrumentation. That gave us a concrete workflow to improve rather than a synthetic benchmark.

Before this, measuring a Sentry span meant running the interaction a few times manually, writing down the numbers, averaging them by hand, and repeating it on another branch to compare. With agent-device, the agent handles the measurement loop as part of normal investigation and optimization work.

The setup

Sentry spans are how the Expensify team tracks how long interactions take. Each time a tracked interaction completes, the app emits a project-specific log line:

[Sentry][<SpanName>] Ending span (<N>ms)

That format is what the agent watches. When a span fires, the duration lands in the console in a consistent shape. The agent grabs it, collects it across runs, and produces a summary.

Before running any measurements, we prepared .ad flow files for the most important Sentry-instrumented interactions. The agent generates these files when instructed, and you can modify them to fit your needs. Each flow is tagged with the span name, so the agent can find the right one without guessing. That is what keeps the agent on track: it does not explore the app; it follows the prepared file.

The skill

We built a skill with specific rules for how to measure telemetry spans. The agent runs one warm-up replay, then a configured number of measured replays, then outputs a structured summary:

The first run came in at 638ms, a clear cold-cache outlier, despite the warm-up run. The remaining four were tight at 85–88ms, with a median of 87ms. That kind of distribution is exactly why you need multiple runs. A single measurement would have told us nothing, or worse, something wrong.

Comparing branches is still manual: check out each revision, run the measurement, and compare the two summaries. That is fine. The hard part was never the comparison. It was getting a trustworthy number from each branch in the first place.

The result

Four consistent runs landed at 85–88ms. The first came in at 638ms, a cold-cache outlier that slipped past the warm-up. We kept it in.

That is what this workflow gives you: enough runs to see the shape of the data, not just a single number that might lie. For any future change touching this interaction, we now have a baseline to compare against. The span does not just "feel fast." The median run is 87ms, and we can prove it.

Profiling with a single prompt

Telemetry spans tell you how long something took. They do not tell you why.

Performance debugging on a development app is rarely clean. You open a simulator, navigate manually, start the profiler at exactly the right moment, read the flame graph, and try to remember what you tapped and in what order. Then you do it again when you want to verify. Each pass depends on you executing the same sequence the same way, which you almost never do.

That is the part agent-device changes. We can turn that manual flow into a prompt like this, run against the iOS app in development mode:

navigate to the Inbox, tap into a chat, and capture what the React profiler sees. No guessing; every finding backed by actual render data.

agent-device has a react-devtools integration that lets the agent start and stop the React profiler programmatically, mid-session. The agent executes the full journey autonomously: opens the app, triggers profiling at the right moment, walks through both screens, and returns a structured summary.

What the agent actually returned

This is the part worth being concrete about.

The output was not a generic list of "common React Native performance issues." It was specific to the Expensify Inbox and chat screens, on that device, during that session. Components were named. Render counts were cited. The agent flagged what was expensive and why, not what could be expensive in theory.

‍
That is the real gain. You save time on the first run, and you can run the same prompt after a fix to get a direct comparison. Run it. Change something. Run it again. The numbers either move or they do not.

Should you try it?

Bug evidence, span measurement, and render profiling all follow the same pattern: give your agent hands and eyes for one chore your team already repeats. Install agent-device, point it at that chore, and wrap the workflow in a skill. The docs and official skills give you the starting point. The first time you paste an issue URL instead of booting a simulator, you will know whether it earns a place in your workflow.

Insights

Learn more about AI

Building a Software Factory: Write Specs an Agent Can Build From — cover

Nov 6Article

Building a Software Factory: Write Specs an Agent Can Build From

Learn how to research requirements, resolve open decisions, and write testable specs that AI coding agents can implement without the planning conversation.

 Large-Scale Migrations With Coding Agents: Planning, Verification, and Rollback — cover

Nov 4Article

Large-Scale Migrations With Coding Agents: Planning, Verification, and Rollback

Plan codebase migrations with coding agents using reviewable requirements, verification checks, staged rollouts, and defined rollback criteria.

Conversational Banking: From Customer Questions to Authorized Actions — cover

Nov 2Article

Conversational Banking: From Customer Questions to Authorized Actions

Explore conversational banking architecture through a card-dispute example, covering tool access, authorization, execution, observability, and human handoff.

Building a Software Factory: The Night My Agents Waited for Me — cover

Oct 28Article

Building a Software Factory: The Night My Agents Waited for Me

Learn how permissions, review limits, and missing product decisions stalled two overnight coding agent runs, and how their maintainer revised the workflow.

Your AI Coding Agent Still Needs an Architecture — cover

Oct 22Article

Your AI Coding Agent Still Needs an Architecture

Use explicit contracts, behavior tests, and dependency checks to guide AI coding agents and review their changes through a TypeScript favorites example.

cover

Oct 15Live Stream

Who Left the Agents Running? React Native Software Factories

Join Kewin Wereszczyński and Szymon Chmal to explore what can go wrong in a React Native software factory overnight and the controls that keep agents on track.

cover

Oct 14Podcast

When Coding Gets Faster, What Breaks Next?

Matt Hargett explains how AI moves bottlenecks across testing, deployment, infrastructure, support, and the rest of software delivery.

cover

Oct 12Podcast

AI Coding Agents and Hermes With Tzvetan Mikov

Tzvetan Mikov discusses AI coding agents, context switching, low-level engine optimization, reviewing specs and tests, and his plans for Hermes X.

cover

Oct 12Podcast

State Machines for AI Agents With David Khourshid

David Khourshid explains state machines for AI agents, human approval steps, tracing, and handling invalid model outputs in agent workflows.

cover

Oct 9Article

How to Maximize AI SDLC? Measure the Outer Business Loop, Not the Inner Developer Loop

Connect AI-assisted development to measurable business outcomes. Matt Hargett shares a checklist for assessing user exposure, results, and delivery costs.

cover

Oct 7Talk

Automating Mobile QA with Cloud Agents

Michał Pierzchała shows how cloud-based QA agents can test real iOS and Android user flows and bring screenshots, recordings, and logs into pull requests.

cover

Oct 1Live Stream

Can Jev Make AI Agent Decisions Faster?

Explore Jev with Kewin Wereszczyński and Szymon Chmal: how it works, its approach to faster agent decisions, and possible local alternatives.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.