<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sharmin Sirajudeen</title>
    <description>The latest articles on DEV Community by Sharmin Sirajudeen (@sharminsirajudeen).</description>
    <link>https://dev.to/sharminsirajudeen</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3861545%2F75306999-70b8-4b5f-abd0-beebccc6e0c5.png</url>
      <title>DEV Community: Sharmin Sirajudeen</title>
      <link>https://dev.to/sharminsirajudeen</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sharminsirajudeen"/>
    <language>en</language>
    <item>
      <title>You Have Amplitude and Still Don't Know What to Do</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Thu, 23 Jul 2026 06:26:46 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/you-have-amplitude-and-still-dont-know-what-to-do-bjp</link>
      <guid>https://dev.to/sharminsirajudeen/you-have-amplitude-and-still-dont-know-what-to-do-bjp</guid>
      <description>&lt;h1&gt;
  
  
  You Have Amplitude and Still Don't Know What to Do
&lt;/h1&gt;

&lt;p&gt;There's a specific kind of quiet panic that doesn't get talked about enough.&lt;/p&gt;

&lt;p&gt;You did everything right. You wired up analytics early. You've got Amplitude — or Mixpanel, or Firebase, or all three. You have events, funnels, retention curves, a dashboard with forty charts. You are, by every definition anyone gave you, &lt;em&gt;data-driven.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And you're staring at it at 1am, watching day-1 retention slide, and you genuinely &lt;strong&gt;don't know what to do.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That feeling isn't a skill gap. It's the tool being honest about its own shape — and most people never notice the shape, so they blame themselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  More data didn't reduce the fog. It &lt;em&gt;is&lt;/em&gt; the fog.
&lt;/h2&gt;

&lt;p&gt;The instinct, when the dashboard doesn't answer the question, is to add more tracking. More events. A new funnel. A cohort split. Surely the answer is in there somewhere and you just haven't sliced it right.&lt;/p&gt;

&lt;p&gt;So you add a 90th event. And the fog gets &lt;em&gt;thicker&lt;/em&gt;, because you didn't have a data-quantity problem. You had a data-&lt;em&gt;legibility&lt;/em&gt; problem, and you just made the pile you have to read taller.&lt;/p&gt;

&lt;p&gt;Here's the thing nobody says: &lt;strong&gt;a wall of charts is not understanding.&lt;/strong&gt; It's raw material for understanding that somebody — usually you, at 1am — still has to turn into a story. The dashboard stops exactly where the hard part begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two different questions
&lt;/h2&gt;

&lt;p&gt;Every event-analytics tool ever built is extremely good at one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What happened?&lt;/strong&gt; Which screens got viewed, which events fired, where the funnel leaked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And structurally blind to the other one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What did the user &lt;em&gt;experience&lt;/em&gt;?&lt;/strong&gt; Where they hesitated. What they tapped that didn't respond. Where they scrolled hunting for something and gave up. The moment it stopped feeling worth it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Look at your funnel. &lt;code&gt;onboarding_complete = 41%&lt;/code&gt;. That number tells you &lt;em&gt;the hole is at onboarding.&lt;/em&gt; It does not tell you that a chunk of the other 59% tapped a "Grant permission" button that lagged for two seconds, tapped it three more times because nothing happened, and closed the app in frustration. &lt;strong&gt;Same 41%. Completely different fix.&lt;/strong&gt; One is a copy problem. One is a dead button. The funnel cannot tell them apart, and the fix depends entirely on which one it is.&lt;/p&gt;

&lt;p&gt;Day-1 ghosting — the thing quietly killing most apps — is almost never a "value proposition" problem you can reason your way to from a chart. It's a &lt;em&gt;first-session friction&lt;/em&gt; problem. And friction is exactly the thing event counts are built to not see.&lt;/p&gt;

&lt;h2&gt;
  
  
  A session is a story, not a row in a table
&lt;/h2&gt;

&lt;p&gt;The reason a good product person can watch ten session recordings and suddenly &lt;em&gt;know&lt;/em&gt; what's wrong — while the same person can stare at the dashboard for an hour and know nothing — is that a session is a &lt;strong&gt;narrative&lt;/strong&gt;, and narratives carry causation. "Opened app → landed on a wall of options → scrolled up and down twice → backed out" is a &lt;em&gt;sentence&lt;/em&gt;. It has a subject, a verb, and an ending you can feel. &lt;code&gt;screen_view: 1, scroll: 2, app_background: 1&lt;/code&gt; is the same facts with the meaning boiled off.&lt;/p&gt;

&lt;p&gt;The job of analytics was never to count. It was to &lt;strong&gt;hand you the story back&lt;/strong&gt; — legibly, so the fix is obvious instead of buried.&lt;/p&gt;

&lt;p&gt;That means capturing the things that carry the plot: not just &lt;em&gt;that&lt;/em&gt; a screen was viewed, but how long it actually held attention (with the app in the foreground, not counting the time the phone was in a pocket). Not just &lt;em&gt;that&lt;/em&gt; a session ended, but whether it ended in a conversion, a decision to leave, or was simply cut off. The hesitations. The rage-taps. The dead scrolls. The moment of abandonment — and what was on screen when it happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honesty is the feature
&lt;/h2&gt;

&lt;p&gt;There's a trap on the other side, though, and it's worth naming because most "insight" tools fall straight into it: the confident narration that's secretly a guess. A dashboard that says &lt;em&gt;"users churned because of pricing"&lt;/em&gt; when all it actually saw was that they left the pricing screen is lying to you with a straight face — and it's more dangerous than the fog, because now you're confidently wrong.&lt;/p&gt;

&lt;p&gt;So the rule we hold ourselves to in &lt;strong&gt;Drengr&lt;/strong&gt; is: &lt;strong&gt;say exactly how much we know, and no more.&lt;/strong&gt; When we counted something, we say we counted it. When we're inferring — "this &lt;em&gt;looks like&lt;/em&gt; frustration" — we label it an inference and show you the signal underneath, so &lt;em&gt;possible rage&lt;/em&gt; never gets quietly promoted to &lt;em&gt;rage&lt;/em&gt;. An honest "we don't know why yet, but here's exactly where it broke" is worth more than a fluent story you can't check. The whole point is to end the not-knowing — and you can't do that by trading it for confidently-wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "knowing what to do" actually feels like
&lt;/h2&gt;

&lt;p&gt;It's not a bigger dashboard. It's smaller and sharper: &lt;em&gt;"On the plan-picker screen, 30% of first-time users scroll the full list, hesitate, and leave without tapping anything — and it's been getting worse since the redesign."&lt;/em&gt; That's not a number. That's a &lt;strong&gt;to-do item.&lt;/strong&gt; You know what to build tomorrow.&lt;/p&gt;

&lt;p&gt;You were never missing data. You were missing the story your data was always trying to tell — and a tool honest enough to tell it straight.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>product</category>
      <category>startup</category>
      <category>ux</category>
    </item>
    <item>
      <title>Is Your A/B Test Actually Significant?</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Thu, 23 Jul 2026 06:25:40 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/is-your-ab-test-actually-significant-io5</link>
      <guid>https://dev.to/sharminsirajudeen/is-your-ab-test-actually-significant-io5</guid>
      <description>&lt;h1&gt;
  
  
  Is Your A/B Test Actually Significant?
&lt;/h1&gt;

&lt;p&gt;You ran the test. Variant B is up 8%, the tool says &lt;code&gt;p = 0.03&lt;/code&gt;, and there's a little green checkmark next to the word &lt;strong&gt;Significant&lt;/strong&gt;. You ship it.&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable part: that green checkmark almost certainly doesn't mean what you think it means. Not because the math is wrong — because of &lt;em&gt;when you looked&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The habit that breaks almost every test
&lt;/h2&gt;

&lt;p&gt;You didn't run this test the way the statistics assume you did.&lt;/p&gt;

&lt;p&gt;A classic significance test — the &lt;code&gt;p &amp;lt; 0.05&lt;/code&gt;, fixed-horizon kind every dashboard shows you — makes one quiet assumption: &lt;strong&gt;you decided the sample size in advance, you looked exactly once, at the end, and then you stopped.&lt;/strong&gt; That's the deal that makes &lt;code&gt;p &amp;lt; 0.05&lt;/code&gt; mean "5% chance this is a fluke."&lt;/p&gt;

&lt;p&gt;Nobody runs tests that way. You watched it. On day two it was flat, day three B pulled ahead, you refreshed it on your phone over coffee, and the moment it crossed the line you called it. That's the honest, human way to run a test — and it silently voids the guarantee.&lt;/p&gt;

&lt;p&gt;The name for this is &lt;strong&gt;peeking&lt;/strong&gt;, and it's not a rounding error. When you monitor a fixed-horizon test continuously and stop the first time it hits &lt;code&gt;p &amp;lt; 0.05&lt;/code&gt;, your real false-positive rate isn't 5%. Depending on how often you look, it climbs toward &lt;strong&gt;20–40%+&lt;/strong&gt;. (This isn't folklore — it's the finding behind Optimizely's own move to sequential testing, and the Johari et al. paper literally titled &lt;em&gt;"Peeking at A/B Tests."&lt;/em&gt;) Roughly: given enough looks, a coin-flip difference will &lt;em&gt;eventually&lt;/em&gt; wander across the &lt;code&gt;0.05&lt;/code&gt; line by pure chance, and you'll be standing there ready to declare victory when it does.&lt;/p&gt;

&lt;p&gt;So the failure isn't that your tools lie. It's that they answer a question — "is this significant &lt;em&gt;if you looked once, at a pre-set sample size&lt;/em&gt;?" — that has nothing to do with how you actually used them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "just wait for significance" is bad advice
&lt;/h2&gt;

&lt;p&gt;The usual fix you'll hear is: &lt;em&gt;stop peeking — pick your sample size up front and don't look until you hit it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It's technically correct and practically useless. It means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Committing to a fixed run length &lt;em&gt;before&lt;/em&gt; you know the effect size (the thing the test exists to measure).&lt;/li&gt;
&lt;li&gt;Sitting on a variant that's obviously harming users because "the test isn't done."&lt;/li&gt;
&lt;li&gt;Ignoring the single most useful thing about running online — that you &lt;em&gt;can&lt;/em&gt; watch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Telling a builder to not look at their own live experiment is like telling a pilot to not look at the altimeter until landing. The real answer isn't to look less. It's to use a test that's &lt;strong&gt;valid under looking.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests you're allowed to watch
&lt;/h2&gt;

&lt;p&gt;There's a whole family of methods — &lt;strong&gt;anytime-valid&lt;/strong&gt; or &lt;strong&gt;always-valid&lt;/strong&gt; inference — built for exactly this. The short version: instead of a p-value that's only honest at one pre-chosen moment, you compute a quantity that stays honest &lt;em&gt;no matter when you stop.&lt;/em&gt; You can check it every hour, stop the instant you're convinced, and still hold your error rate.&lt;/p&gt;

&lt;p&gt;The mechanics have a few names — &lt;strong&gt;e-values&lt;/strong&gt;, &lt;strong&gt;sequential probability ratio tests&lt;/strong&gt;, &lt;strong&gt;always-valid confidence sequences&lt;/strong&gt; — but the property that matters is one sentence: &lt;strong&gt;you may stop whenever you like, and the guarantee still holds.&lt;/strong&gt; No pre-registered sample size, no peeking penalty, no pretending you didn't look.&lt;/p&gt;

&lt;p&gt;The trade-off is honest and worth stating: to buy that freedom, these tests are a touch more &lt;em&gt;conservative&lt;/em&gt; at any single moment than a fixed-horizon test would be if you'd genuinely only looked once. You pay a small tax in exchange for permission to do the thing you were always going to do anyway. That's a good trade. A slightly slower "yes" you can trust beats a fast "yes" that's wrong a third of the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five-minute integrity checklist
&lt;/h2&gt;

&lt;p&gt;Before you believe any test — sequential or not — run these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sample Ratio Mismatch (SRM).&lt;/strong&gt; You split 50/50, but the arms have 12,000 vs. 13,400 users. That gap should be near-impossible by chance. If your split is off, &lt;em&gt;stop&lt;/em&gt; — something in the assignment or logging is broken, and every downstream number is contaminated. SRM is the smoke alarm; check it first, every time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Did you slice after the fact?&lt;/strong&gt; "It's not significant overall, but it's significant for Android users in Canada on Tuesdays." Every extra segment you test is another lottery ticket for a false positive. If you're going to slice, your significance bar has to get &lt;em&gt;stricter&lt;/em&gt; as you add comparisons (this is what false-discovery-rate correction is for) — not stay at &lt;code&gt;0.05&lt;/code&gt; per slice.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;What's the smallest effect you'd actually act on?&lt;/strong&gt; If a 0.5% lift wouldn't change a single decision, don't run a test powered to detect it — you'll just wait forever or fool yourself. Decide the effect that matters &lt;em&gt;before&lt;/em&gt; you look at the result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Is this a novelty spike?&lt;/strong&gt; New things get clicked because they're new. A week-one win can be pure curiosity that decays to nothing. Let it run past the novelty, or your "winner" is a fad.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Can you even see &lt;em&gt;why&lt;/em&gt; it moved?&lt;/strong&gt; A test tells you &lt;em&gt;that&lt;/em&gt; B beat A. It rarely tells you &lt;em&gt;why&lt;/em&gt; — and "why" is what you actually need to not get fooled next time. If B won because it accidentally hid a confusing button, you learned something. If you can't tell, you're one layer short.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The honest version of "data-driven"
&lt;/h2&gt;

&lt;p&gt;Being data-driven was never about producing a number. It's about producing a number you're &lt;em&gt;allowed to believe&lt;/em&gt; — one whose guarantee survives contact with how you actually work.&lt;/p&gt;

&lt;p&gt;That's the whole design principle behind how we build &lt;strong&gt;Drengr&lt;/strong&gt;: the test math should be honest about the thing humans really do — watch, and stop when convinced — instead of quietly assuming you didn't. An answer you can check beats a confident answer you can't, every time.&lt;/p&gt;

&lt;p&gt;Your last "significant" result might have been real. The point is that, run the usual way, &lt;strong&gt;you have no way to know&lt;/strong&gt; — and that not-knowing is the exact thing a real test is supposed to remove.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>statistics</category>
      <category>abtesting</category>
      <category>product</category>
    </item>
    <item>
      <title>The HiPPO is always right</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Tue, 30 Jun 2026 21:00:12 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/the-hippo-is-always-right-138p</link>
      <guid>https://dev.to/sharminsirajudeen/the-hippo-is-always-right-138p</guid>
      <description>&lt;h1&gt;
  
  
  The HiPPO is always right
&lt;/h1&gt;

&lt;p&gt;I've spent years inside companies serving millions of people. Different industries, different teams — and the same quiet thing in every one, the thing nobody says out loud:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The people running the company can't actually see it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not the founder. Not the CTO. Not the investor. Not the VP, not the director. Every one of them gets the same thing — &lt;strong&gt;the version from the data team.&lt;/strong&gt; A report, written for them, by someone else, about what's happening inside their own company. The entire leadership's picture of reality, capped by one team in the middle. A sharp founder with an average data team gets an average, maybe-wrong picture of their own company — and no way to know it's wrong.&lt;/p&gt;

&lt;p&gt;And sometimes there isn't even a report. I've watched this exact thing play out more than once: someone senior declares that a screen "has a problem." No data — a feeling, delivered with authority. The room agrees instantly, because when the most senior person names the cause, agreement isn't evidence, it's &lt;em&gt;gravity&lt;/em&gt;. There's even a name for it: the &lt;strong&gt;HiPPO&lt;/strong&gt; — the &lt;em&gt;Highest Paid Person's Opinion&lt;/em&gt;. And that's where it ends. No one ever establishes whether the screen was actually the problem. The claim is never proven true or false — it's just &lt;em&gt;said&lt;/em&gt;, and then it's real, because of who said it. Years later, you still don't know if it ever was.&lt;/p&gt;

&lt;p&gt;That's the &lt;em&gt;fast&lt;/em&gt; way a wrong conclusion enters a company. The slow way wears the costume of rigor.&lt;/p&gt;

&lt;p&gt;Watch how a "data-driven" decision actually gets made. A PM has a hunch. It becomes a PRD. Engineering builds it, ships it, everyone waits. Weeks pass. The PM comes back with a list of things to track — events they &lt;em&gt;guessed&lt;/em&gt; would matter, from a discovery that may have been wrong from the start. The data team wires it up. BI builds the dashboard. A number appears.&lt;/p&gt;

&lt;p&gt;So you decide. On a number, produced by a hundred-person chain, measuring a hypothesis someone &lt;em&gt;guessed&lt;/em&gt;, instrumented in a way you can't check, interpreted by a team whose ceiling you can't exceed. Sometimes you find out you were wrong. More often — the part that should keep you up at night — you &lt;em&gt;don't.&lt;/em&gt; Nobody verified it.&lt;/p&gt;

&lt;p&gt;Here's the most rigorous costume of all: the A/B test. The "gold standard." The thing we point to and say &lt;em&gt;we're data-driven.&lt;/em&gt; Ask four honest questions about your last one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Did the tracking even fire correctly for both variants?&lt;/li&gt;
&lt;li&gt;Did your metric measure what you &lt;em&gt;care about&lt;/em&gt; — or a proxy you &lt;em&gt;hoped&lt;/em&gt; correlated?&lt;/li&gt;
&lt;li&gt;Did the winning variant quietly &lt;em&gt;hurt&lt;/em&gt; three things you never thought to measure?&lt;/li&gt;
&lt;li&gt;Was the statistics even valid — no peeking, no sample mismatch, no confound?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You shipped the winner. You verified &lt;em&gt;none of it.&lt;/em&gt; And that's the &lt;em&gt;rigorous&lt;/em&gt; end of how companies decide.&lt;/p&gt;

&lt;p&gt;The better-funded the company, the better it hides all this. The biggest players can afford world-class PMs and data scientists who guess better and rebuild the apparatus faster. So clarity becomes a luxury good — rationed by how much talent and money you can throw at the middle layer. Everyone else flies blind and calls it data-driven.&lt;/p&gt;

&lt;p&gt;The problem isn't the people. Every PM and analyst I've worked alongside was sharp and trying hard. The problem is the &lt;em&gt;method&lt;/em&gt; — a whole discipline built on a quiet lie: that you can understand what's happening by having a human &lt;em&gt;guess&lt;/em&gt; what to measure, instrument the guess, wait, and trust the interpretation. Whether the conclusion falls from a HiPPO's mouth or crawls out of a hundred-person pipeline, it arrives the same way: &lt;strong&gt;unverified.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So here's the question I can't put down: &lt;strong&gt;what if you never had to guess?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What if you could just &lt;em&gt;see&lt;/em&gt; what actually happened — and check it yourself — instead of trusting a chain of people to hand you a version of it? The teams that get there first won't win because they hired better analysts. They'll win because they &lt;strong&gt;stopped being blind.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That world is closer than it looks. I'm building toward it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;— Sharmin Sirajudeen. I'm building toward a world where you can just see what's real. &lt;a href="https://drengr.dev" rel="noopener noreferrer"&gt;drengr.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>product</category>
      <category>startup</category>
      <category>data</category>
    </item>
    <item>
      <title>Claude + Mobile via MCP: Giving the Model Hands on a Real Phone</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Sun, 03 May 2026 19:24:44 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/claude-mobile-via-mcp-giving-the-model-hands-on-a-real-phone-45c0</link>
      <guid>https://dev.to/sharminsirajudeen/claude-mobile-via-mcp-giving-the-model-hands-on-a-real-phone-45c0</guid>
      <description>&lt;h1&gt;
  
  
  Claude + Mobile via MCP: Giving the Model Hands on a Real Phone
&lt;/h1&gt;

&lt;p&gt;I plugged in a Pixel two months ago, ran one command in Claude Desktop, and watched it open Maps and start navigation to my home address from a single sentence prompt. It was the first time I'd ever seen a language model physically operate a phone. Latency was about two seconds per action; the part that surprised me was the third step, where Claude noticed it had landed on the wrong screen and self-corrected without being asked.&lt;/p&gt;

&lt;p&gt;That experience is what this post is about — what "Claude on mobile via MCP" actually means today, what's required underneath to make it work, and why the bottleneck for AI agents has stopped being model size and started being whether the model has hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "Claude mobile MCP" actually refers to
&lt;/h2&gt;

&lt;p&gt;There isn't a product called Claude Mobile MCP. What people are searching for is a way to let Claude — running in Claude Desktop, Claude Code, Cursor, or any other &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;MCP-aware client&lt;/a&gt; — control a real Android phone or iOS simulator. MCP (Model Context Protocol) is Anthropic's open standard for exposing tools and data to language models. It's the cleanest way to plug a new capability into Claude without rebuilding the client.&lt;/p&gt;

&lt;p&gt;The missing piece has been a server on the MCP side that knows how to drive a phone. That's what &lt;a href="https://drengr.dev" rel="noopener noreferrer"&gt;Drengr&lt;/a&gt; does. It's a single Rust binary that exposes three tools to any MCP client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drengr_look      observe the current screen + UI tree
drengr_do        execute a tap / type / swipe / draw / key event
drengr_query     read structured data (devices, activity, crashes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three verbs. No XPath, no Appium daemon, no fragile selectors. Claude calls &lt;code&gt;drengr_look&lt;/code&gt;, gets back a compact text description of what's on screen with numbered elements, decides what to do, and calls &lt;code&gt;drengr_do&lt;/code&gt; with the action and target element. Drengr executes against the device through its native channel (ADB on Android, WDA on iOS simulators) and returns a &lt;em&gt;situation report&lt;/em&gt; — what changed, what appeared, whether the app crashed — so the next decision starts grounded.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bottleneck stopped being the model
&lt;/h2&gt;

&lt;p&gt;A common assumption is that mobile AI assistants are blocked on model capability. They aren't. Current Claude models reason about a screenshot better than most humans can describe one. Open Photos, find a specific picture, attach it to a WhatsApp message — Claude has the visual reasoning to do all of that. What it doesn't have, by default, is a way to actually touch the screen.&lt;/p&gt;

&lt;p&gt;That gap is purely transport. iOS sandboxing prevents one app from reaching into another. Android Accessibility Services exist but are heavy to set up, scary to permission, and limited in what they can synthesize. Cloud-only assistants are dead for anything physical because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; A two-second cloud round trip per tap feels broken when you're holding the phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; Banking apps, health apps, messages — none of that should leave the device for a UI inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network independence.&lt;/strong&gt; Subway, airplane, bad hotel wifi.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once Gemini Nano on Android and Apple Intelligence on iOS are widespread, the model is local. The control plane has to be local too. Drengr is a single static binary; that's not a coincidence. (&lt;a href="https://dev.to/blog/control-plane-local-ai-agents"&gt;More on why local matters in &lt;em&gt;The Missing Control Plane for Local AI Agents&lt;/em&gt;.&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  How to actually try it
&lt;/h2&gt;

&lt;p&gt;Two commands, assuming you have Claude Code installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add drengr &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; drengr mcp
drengr doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first registers Drengr as an MCP server in Claude Code. The second checks that ADB and Xcode are reachable on your machine and prints any device it can see. Then plug in an Android phone (with USB debugging enabled) or boot an iOS simulator, and ask Claude to do something on it.&lt;/p&gt;

&lt;p&gt;A few prompts that have worked for me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;"Open YouTube and search for &lt;code&gt;m3 ultra benchmarks&lt;/code&gt;."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Open Slack, find the channel called engineering, post the message &lt;code&gt;merging at 3pm&lt;/code&gt;."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;"Open Maps and start navigation to home."&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one becomes an observe-decide-act loop under the hood. Claude calls &lt;code&gt;drengr_look&lt;/code&gt;, reasons over the text scene Drengr returns (~300 tokens vs ~100KB for a raw screenshot — see &lt;a href="https://dev.to/blog/field-notes-and-related-research"&gt;why text-first matters here&lt;/a&gt;), decides what to do, calls &lt;code&gt;drengr_do&lt;/code&gt;, gets a situation diff back, and repeats until the task is done or it gets stuck.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond mobile QA
&lt;/h2&gt;

&lt;p&gt;The obvious early audience for a mobile MCP server is QA — automate the test flows that break every sprint. That market is real but small. The much bigger one is everything &lt;em&gt;else&lt;/em&gt; you can build once Claude can touch a phone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RPA on mobile.&lt;/strong&gt; UiPath / Automation Anywhere shops have spent a decade automating desktop workflows. Mobile has been a gap because the existing tooling assumed a developer sat behind it. With an LLM in the loop, the tooling assumption changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accessibility tools.&lt;/strong&gt; A low-vision user can ask Claude to operate a banking app on their behalf, with the app running on their own device.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-device personal assistants.&lt;/strong&gt; "Find pictures from last weekend in Photos and attach them to a WhatsApp message to my mom" is a sentence Claude can already plan. The hands-and-eyes layer was missing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those need new model capability. They need a working transport. (&lt;a href="https://dev.to/blog/ai-can-browse-the-web-but-cant-tap-a-phone"&gt;That argument in full: &lt;em&gt;AI Can Browse the Web. Why Can't It Tap a Phone?&lt;/em&gt;&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;If you want Claude to control a phone, install Drengr and ask it to do something. The control plane is the part you don't want to build yourself — WDA, ADB, the screen-capture pipeline, the situation diffing, the cross-platform abstraction, the MCP wiring — they're all unglamorous infrastructure that's already done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add drengr &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; drengr mcp
drengr doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point Claude at any Android device or iOS simulator and watch what happens when the model has hands.&lt;/p&gt;

</description>
      <category>appium</category>
      <category>mobiledev</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
    <item>
      <title>AI-Native Mobile Testing: What It Actually Means in 2026</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Sun, 03 May 2026 19:24:01 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/ai-native-mobile-testing-what-it-actually-means-in-2026-3nfj</link>
      <guid>https://dev.to/sharminsirajudeen/ai-native-mobile-testing-what-it-actually-means-in-2026-3nfj</guid>
      <description>&lt;h1&gt;
  
  
  AI-Native Mobile Testing: What It Actually Means in 2026
&lt;/h1&gt;

&lt;p&gt;The phrase "AI-native" has been thrown around in the testing space since 2019. Almost every tool calling itself that just bolts a language model on top of Appium and ships the same brittle XPath selectors with a new label.&lt;/p&gt;

&lt;p&gt;That's not AI-native testing. That's Appium with a chatbot.&lt;/p&gt;

&lt;p&gt;This post is about what AI-native actually has to mean to be worth anything — and what it changes about how mobile teams ship.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern that broke testing for the last decade
&lt;/h2&gt;

&lt;p&gt;Traditional mobile UI testing has a well-known failure mode. You write hundreds of tests with hardcoded element IDs or XPath selectors. Then half of them break each release for reasons that have nothing to do with bugs — a designer moved a button into a &lt;code&gt;BottomSheet&lt;/code&gt;, a resource ID got renamed, an animation timing changed. Forty tests turn red overnight.&lt;/p&gt;

&lt;p&gt;Engineering teams burn entire sprint days fixing tests for code that works fine. The economics are absurd: you spend more on test maintenance than the tests prevent in production bugs. (&lt;a href="https://dev.to/blog/evolution-of-mobile-automation"&gt;I covered the deeper history of why this happens in &lt;em&gt;Evolution of Mobile Automation&lt;/em&gt;.&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The dominant fix from the last decade was "better selectors" — accessibility IDs, more stable resource paths, page-object patterns. None of it fixed the fundamental problem: a test that says &lt;em&gt;"tap the element with id &lt;code&gt;btn_login&lt;/code&gt;"&lt;/em&gt; couples the test to an implementation detail that has no business being part of the test contract.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI-native actually means
&lt;/h2&gt;

&lt;p&gt;A real AI-native testing tool does one structural thing differently: &lt;strong&gt;the AI is the orchestrator, not a polish layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Concretely, that means the test framework hands the AI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the current screen (screenshot + parsed UI tree, in plain text)&lt;/li&gt;
&lt;li&gt;a description of what the test is trying to accomplish ("log in with these credentials")&lt;/li&gt;
&lt;li&gt;the list of available actions (tap, type, swipe, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…and the AI decides what to do, step by step. No selectors. No hardcoded IDs. No XPath. The AI looks at the screen, notices a button labeled "Login" near a username field, taps it. If next release the button gets a new background and moves down 80 pixels, the AI doesn't care. It still sees a button labeled "Login," it still taps it. The test passes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://drengr.dev" rel="noopener noreferrer"&gt;Drengr&lt;/a&gt; is one implementation of this pattern. It's a single Rust binary that exposes three tools to any MCP-aware AI client (Claude Desktop, Cursor, Windsurf today):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drengr_look      observe the current screen + UI tree
drengr_do        execute a tap / type / swipe / draw / key event
drengr_query     read structured data (devices, activity, crashes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole surface. Three verbs, no XPath, no Appium daemon to keep alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it changes economically
&lt;/h2&gt;

&lt;p&gt;Once the AI is the orchestrator, two things shift:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Test maintenance approaches zero.&lt;/strong&gt; The test prompt ("log in with &lt;code&gt;test@example.com&lt;/code&gt; / &lt;code&gt;pw123&lt;/code&gt; and verify you land on the dashboard") rarely changes between releases. The implementation underneath (button labels, layout, animations) can change freely without breaking the test. Maintenance cost was the entire reason mid-size companies stopped writing UI tests at all. Removing it puts UI testing back on the table for teams that wrote it off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The audience widens.&lt;/strong&gt; Indie developers and small teams who could never afford a dedicated QA engineer are now in scope. The cost of one full-time Espresso maintainer was what kept many small apps shipping untested. An AI-native testing layer effectively replaces that role for the cost of a Claude or Cursor subscription. (&lt;a href="https://dev.to/blog/field-notes-and-related-research"&gt;How drengr's architecture lines up with what academic researchers have found, in &lt;em&gt;Field Notes&lt;/em&gt;.&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond testing: the same primitives, different audience
&lt;/h2&gt;

&lt;p&gt;The interesting thing once you have an AI controlling a real device is that the testing use case stops being the most important one.&lt;/p&gt;

&lt;p&gt;The same &lt;code&gt;drengr_look&lt;/code&gt; / &lt;code&gt;drengr_do&lt;/code&gt; / &lt;code&gt;drengr_query&lt;/code&gt; primitives let an AI agent on the user's machine:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open Maps and start navigation when the user says "drive me home"&lt;/li&gt;
&lt;li&gt;Scroll through Instagram and report sponsored posts&lt;/li&gt;
&lt;li&gt;Pay a bill in a banking app on behalf of someone with a motor disability&lt;/li&gt;
&lt;li&gt;Run any of the long-tail "things you'd ask a human assistant to do on your phone" tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mobile QA was the first audience because the pain is most acute there. The AI-agent-builder audience is much larger. If you're building anything that wants an AI to control a real mobile device — testing, accessibility, personal assistance, something I haven't thought of — the control plane is the part you don't want to write from scratch. (&lt;a href="https://dev.to/blog/ai-can-browse-the-web-but-cant-tap-a-phone"&gt;That argument in full: &lt;em&gt;AI Can Browse the Web. Why Can't It Tap a Phone?&lt;/em&gt;&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Drengr is free. Install through Claude Code with one command, verify with a second:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add drengr &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; drengr mcp
drengr doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point your agent at any Android device or iOS simulator and watch what happens when the model has hands. No XPath. No selectors. No &lt;code&gt;find_element(By.ID, "btn_login")&lt;/code&gt; to maintain across forty test files for the rest of your career.&lt;/p&gt;

&lt;p&gt;That's what AI-native is supposed to mean.&lt;/p&gt;

</description>
      <category>appium</category>
      <category>mobiledev</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
    <item>
      <title>The Missing Control Plane for Local AI Agents</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Sun, 03 May 2026 19:23:55 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/the-missing-control-plane-for-local-ai-agents-232d</link>
      <guid>https://dev.to/sharminsirajudeen/the-missing-control-plane-for-local-ai-agents-232d</guid>
      <description>&lt;h1&gt;
  
  
  The Missing Control Plane for Local AI Agents
&lt;/h1&gt;

&lt;p&gt;I sat with my Pixel for 20 minutes trying to get Claude Desktop to dictate a Slack message via accessibility. It was miserable. The model was capable. The transport wasn't.&lt;/p&gt;

&lt;p&gt;That gap — between an AI that can reason and an AI that can actually &lt;em&gt;do&lt;/em&gt; — is what I've been working on with &lt;a href="https://drengr.dev" rel="noopener noreferrer"&gt;Drengr&lt;/a&gt;. This post is the version of the argument I'd give to anyone building local AI agents today.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a control plane actually means here
&lt;/h2&gt;

&lt;p&gt;When people talk about "AI agents," they usually focus on the model: which one, how big, how cheap to run, what context window. Those are real questions, but they all assume the agent has a way to &lt;em&gt;act&lt;/em&gt; on the world. On mobile, it mostly doesn't. iOS sandboxing prevents one app from touching another. Android Accessibility Services exist but are heavy to set up, scary to permission, and limited in what they can synthesize.&lt;/p&gt;

&lt;p&gt;The result: you can ship a brilliant Gemini Nano running on a Pixel, and it still can't open Maps and start navigation for you. The model has no hands.&lt;/p&gt;

&lt;p&gt;A control plane fills that gap. It's not the model. It's the layer underneath that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;observes&lt;/strong&gt; the device (screen state, UI tree, current activity, foreground app)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;executes&lt;/strong&gt; discrete actions (tap, type, swipe, draw, key event, app launch)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reports&lt;/strong&gt; what changed after each action so the agent can adjust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drengr is one implementation of this control plane. It exposes three MCP tools to any AI client that supports the protocol — Claude Desktop, Cursor, Windsurf today; more soon:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drengr_look      observe the current screen + UI tree
drengr_do        execute a tap / type / swipe / etc
drengr_query     read structured data (devices, activity, crashes)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole surface. Three verbs, no XPath, no fragile selectors, no Appium daemon to keep alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observe → decide → act, on real devices
&lt;/h2&gt;

&lt;p&gt;Drengr's runtime is a single Rust binary that drives the device through its native channels (ADB on Android, WDA on iOS simulators). The agent loop looks like this each step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The model calls &lt;code&gt;drengr_look&lt;/code&gt;. Drengr captures a screenshot, dumps the UI tree, builds a compact text description (~300 tokens vs ~100KB for an image — see &lt;a href="https://dev.to/blog/field-notes-and-related-research"&gt;why text-first matters in the field-notes post&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;The model decides what to do, returns a JSON envelope with the action.&lt;/li&gt;
&lt;li&gt;The model calls &lt;code&gt;drengr_do&lt;/code&gt;. Drengr executes against the device, then runs a &lt;em&gt;situation report&lt;/em&gt; — diffed against the previous state — and feeds it back so the next decision starts grounded.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The situation report is the part most agent frameworks miss. Without it, the model is blind between observations and tends to over-act (tapping the same dead button five times because nothing visibly changed). With it, the loop becomes self-correcting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this needs to be local
&lt;/h2&gt;

&lt;p&gt;Cloud-only AI assistants are dead for anything physical. The moment a model has to decide whether to tap "Confirm" on your banking app, three things matter that round-trips can't deliver:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; A two-second cloud round trip feels broken when you're holding the phone in your hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy.&lt;/strong&gt; Banking apps, health apps, messages — none of that should leave the device for a UI inference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network independence.&lt;/strong&gt; Subway, airplane, bad hotel wifi.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once Gemini Nano (Android) and Apple Intelligence (iOS) are widespread, the bottleneck shifts entirely. The model is local. The control plane has to be local too. Drengr's runtime is a single static binary; that's not a coincidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond mobile QA: where this actually goes
&lt;/h2&gt;

&lt;p&gt;The obvious early audience for a mobile control plane is QA — automate the tedious test flows that break every sprint. That market is real but small. The much bigger one is AI-agent builders making on-device personal assistants.&lt;/p&gt;

&lt;p&gt;Concretely, with the same three tools shown above, an agent on the user's machine can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the Photos app, find pictures from last weekend, attach them to a message in WhatsApp&lt;/li&gt;
&lt;li&gt;Watch a flight app for a price drop and rebook automatically&lt;/li&gt;
&lt;li&gt;Operate a banking app inside a screen-sharing session for a low-vision user&lt;/li&gt;
&lt;li&gt;Run the long-tail of "things you'd ask a human assistant to do on your phone if you had one"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those need new model capability. They need a working hands-and-eyes layer that the model can call. &lt;a href="https://dev.to/blog/ai-can-browse-the-web-but-cant-tap-a-phone"&gt;That's exactly the gap I wrote about in &lt;em&gt;AI Can Browse the Web. Why Can't It Tap a Phone?&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to start
&lt;/h2&gt;

&lt;p&gt;If you're building anything that wants an AI to control a real mobile device — whether your goal is QA, an on-device assistant, an accessibility tool, or something I haven't thought of — the control plane is the part you don't want to build from scratch. WDA, ADB, the screen-capture pipeline, the situation diffing, the cross-platform abstraction — they're all unglamorous infrastructure that's already done.&lt;/p&gt;

&lt;p&gt;Drengr is free to use. One command to install it via Claude Code, one to verify it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add drengr &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; drengr mcp
drengr doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then point your agent at it and see what your model can actually do when it has hands. (&lt;a href="https://dev.to/blog/why-rust"&gt;The Rust choice was deliberate too — that's a separate post.&lt;/a&gt;)&lt;/p&gt;

</description>
      <category>appium</category>
      <category>mobiledev</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
    <item>
      <title>How a Web Worker Fixed My Dying-Battery Audio (And What I Learned About PWAs the Hard Way)</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Mon, 13 Apr 2026 11:54:40 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/how-a-web-worker-fixed-my-dying-battery-audio-and-what-i-learned-about-pwas-the-hard-way-3e0n</link>
      <guid>https://dev.to/sharminsirajudeen/how-a-web-worker-fixed-my-dying-battery-audio-and-what-i-learned-about-pwas-the-hard-way-3e0n</guid>
      <description>&lt;p&gt;I spent the last week modifying an open-source NES emulator to run in the browser as a PWA. I'm an Android developer by trade — Kotlin, Jetpack Compose, Flutter when the project calls for it. This was my first real dive into Web Workers, SharedArrayBuffer, and turning a browser tab into something that feels like a native app.&lt;/p&gt;

&lt;p&gt;Here's what I learned. Some of it was obvious in hindsight. Most of it wasn't.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem That Started Everything
&lt;/h2&gt;

&lt;p&gt;I wanted to add real-time game modification sliders to a browser-based NES emulator. Speed multiplier, firepower boost, infinite lives — the kind of thing that's trivial if you have access to the game's memory. The emulator (JSNES, open-source) gives you direct access to the NES CPU's RAM via JavaScript. Writing a slider that tweaks &lt;code&gt;cpu.mem[0x0487]&lt;/code&gt; every frame is maybe 10 lines of code.&lt;/p&gt;

&lt;p&gt;I set up a GitHub Codespace, got the emulator running, and tested it in the browser. Everything worked beautifully. Then I opened the same URL on an older Android phone sitting on my desk.&lt;/p&gt;

&lt;p&gt;The game visuals were smooth enough. But the audio — the iconic 8-bit music — sounded like a toy running out of battery. Slow, dragging, painful. Like someone was holding the NES's APU underwater.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Single-Threaded Was the Root Cause
&lt;/h2&gt;

&lt;p&gt;Here's what was happening. The NES generates audio samples at 44,100 Hz, tied directly to CPU emulation. Each frame of emulation produces ~735 audio samples. The browser's Web Audio API expects those samples delivered at a consistent rate.&lt;/p&gt;

&lt;p&gt;On a decent machine, the main thread easily ran the emulator at 60fps + rendered the canvas + fed audio samples. No contention. On the slow Android phone, canvas rendering was choking the main thread. Frames dropped to 30fps. Half the audio samples were generated per second. The Web Audio API played them at the expected rate but ran out halfway — producing that dying-battery sound.&lt;/p&gt;

&lt;p&gt;I tried every hack I could think of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive sample dropping&lt;/strong&gt; — monitored FPS and dropped audio samples when the device struggled. Result: choppy audio instead of slow audio. Not better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Rate Control&lt;/strong&gt; — stretched available samples via interpolation (the algorithm RetroArch uses). Result: alien communication sounds. The pitch was wrong because you can't stretch 22K samples to fill 44K slots without changing the fundamental frequency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-frame catch-up&lt;/strong&gt; — ran 2 NES frames per &lt;code&gt;requestAnimationFrame&lt;/code&gt; when the device fell behind. Result: even slower, because the device couldn't handle 2 frames if it was already struggling with 1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of it worked because I was treating the symptom, not the disease. The disease was: &lt;strong&gt;audio generation and canvas rendering were fighting for the same thread.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Web Workers
&lt;/h2&gt;

&lt;p&gt;The solution was architecturally simple. Move the NES emulation (CPU + audio generation) to a &lt;strong&gt;Web Worker&lt;/strong&gt;. The main thread only handles canvas rendering, user input, and UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Worker Thread (setInterval @ 60fps)
├── JSNES emulation (CPU, PPU, APU)
├── Audio sample generation → SharedArrayBuffer
├── Game mod logic (speed, firepower, lives)
└── Frame pixel conversion → postMessage (Transferable)

Main Thread (requestAnimationFrame)
├── Canvas rendering (receives pixels from Worker)
├── Audio playback (reads from SharedArrayBuffer)
├── Keyboard/touch input → postMessage to Worker
└── UI (sliders, toggles, save/load, fullscreen)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight: &lt;code&gt;setInterval&lt;/code&gt; in a Web Worker is &lt;strong&gt;not throttled&lt;/strong&gt; when the tab is backgrounded. &lt;code&gt;requestAnimationFrame&lt;/code&gt; on the main thread is. This means the Worker keeps generating audio at a consistent rate regardless of what the renderer is doing. The audio buffer never starves.&lt;/p&gt;

&lt;h2&gt;
  
  
  SharedArrayBuffer: The Zero-Copy Audio Bridge
&lt;/h2&gt;

&lt;p&gt;This was the part I found most interesting, coming from a mobile background where inter-thread communication usually means &lt;code&gt;Handler.post()&lt;/code&gt; or Kotlin coroutine channels.&lt;/p&gt;

&lt;p&gt;The Worker generates ~735 audio samples per frame. Those samples need to reach the main thread's &lt;code&gt;ScriptProcessorNode&lt;/code&gt; with minimal latency. &lt;code&gt;postMessage&lt;/code&gt; adds serialization overhead and scheduling jitter — fine for input events, not great for 44,100 samples per second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SharedArrayBuffer&lt;/strong&gt; gives both threads access to the same memory. The Worker writes audio samples into a ring buffer. The main thread's audio processor reads from the same buffer. Zero copy, zero serialization, microsecond access.&lt;/p&gt;

&lt;p&gt;The layout is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SharedArrayBuffer:
[0-3]   Int32: write index (Worker writes via Atomics.store)
[4-7]   Int32: read index (Main reads via Atomics.store)
[8+]    Float32[]: interleaved L/R audio samples
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Worker writes samples after each &lt;code&gt;nes.frame()&lt;/code&gt; call. The &lt;code&gt;ScriptProcessorNode&lt;/code&gt; on the main thread reads them in its &lt;code&gt;onaudioprocess&lt;/code&gt; callback. The &lt;code&gt;Atomics&lt;/code&gt; operations provide memory ordering guarantees — no locks needed for a single-producer, single-consumer ring buffer.&lt;/p&gt;

&lt;p&gt;One gotcha that cost me an hour: &lt;strong&gt;interleaved audio samples must always be written in pairs&lt;/strong&gt; (left + right). If the available buffer space is odd, you write one L sample without its R, and every subsequent read is shifted by one channel. The fix is one line: &lt;code&gt;samplesToWrite = available &amp;amp; ~1&lt;/code&gt; — force even.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SharedArrayBuffer requires specific HTTP headers:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without these, &lt;code&gt;typeof SharedArrayBuffer === 'undefined'&lt;/code&gt; in every browser. I built a fallback path using &lt;code&gt;postMessage&lt;/code&gt; with Transferable &lt;code&gt;Float32Array&lt;/code&gt; for environments where the headers can't be set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frame Transfer: Transferable Objects
&lt;/h2&gt;

&lt;p&gt;The NES outputs 256×240 pixels per frame. That's ~245KB of pixel data at 60fps. Copying it via &lt;code&gt;postMessage&lt;/code&gt; would be expensive. &lt;strong&gt;Transferable objects&lt;/strong&gt; solve this — the &lt;code&gt;ArrayBuffer&lt;/code&gt; is moved between threads, not copied. The sending thread loses access to it (it gets "neutered"), but the transfer is essentially free.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Worker: convert pixels and transfer&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pixels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint32Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;61440&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// ... fill pixels from JSNES frameBuffer ...&lt;/span&gt;
&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;frame&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pixels&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pixels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="c1"&gt;// pixels.buffer is now neutered — length 0 in Worker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I used double-buffering: two pixel arrays in the Worker, alternating which one gets filled and transferred. In practice, I found that just reallocating a new &lt;code&gt;Uint32Array(61440)&lt;/code&gt; after each transfer was simpler and fast enough — 245KB allocation at 60fps is well within V8's comfort zone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PWA Part
&lt;/h2&gt;

&lt;p&gt;Turning this into a Progressive Web App was its own education. A few things I learned:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;iOS Safari has no Fullscreen API.&lt;/strong&gt; Not &lt;code&gt;requestFullscreen&lt;/code&gt;, not &lt;code&gt;webkitRequestFullscreen&lt;/code&gt;, not any variant. I discovered this when the fullscreen button simply did nothing on an iPhone. The only way to get "fullscreen" on iPhone is &lt;code&gt;display: standalone&lt;/code&gt; in your web manifest + adding to home screen. Even then, the status bar stays — Apple never lets you hide it.&lt;/p&gt;

&lt;p&gt;I ended up building a &lt;strong&gt;CSS-simulated fullscreen&lt;/strong&gt;: toggling a body class that hides everything except the game canvas and touch controls. But then the exit button didn't work. Turns out, on iOS, a &lt;code&gt;position: fixed&lt;/code&gt; button placed outside the main touch-responsive container &lt;strong&gt;silently fails to receive touch events&lt;/strong&gt;. The button renders, you can see it, but tapping does nothing. I had to move the exit control inside the same overlay that handles game input. That one cost me a few hours of confused debugging.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PWA icons on iOS must be PNG, not SVG, and RGB not RGBA.&lt;/strong&gt; Safari ignores SVG &lt;code&gt;apple-touch-icon&lt;/code&gt; links entirely. And if your PNG has an alpha channel, iOS sometimes renders a blank or uses its default icon. My custom pixel-art icon only appeared after I converted it from RGBA to RGB using Pillow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service Worker caching is aggressive and separate from Safari's cache.&lt;/strong&gt; Deleting Safari data doesn't clear a PWA's cache. You have to delete the home screen app icon first, then clear Safari data, then re-add. Learned this the hard way when testers kept seeing old versions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;viewport-fit: cover&lt;/code&gt; meta tag&lt;/strong&gt; is what lets your app extend under the iPhone notch. Without it, you get black bars.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus: Background Execution Control
&lt;/h2&gt;

&lt;p&gt;One thing I didn't expect — the Worker architecture gives you easy control over background behavior. Since the emulation loop runs on &lt;code&gt;setInterval&lt;/code&gt; inside a Web Worker (which browsers don't throttle in background tabs), the game keeps running even when the user switches apps or tabs. That's great for audio continuity, but terrible for battery life.&lt;/p&gt;

&lt;p&gt;The fix is trivial: listen for &lt;code&gt;visibilitychange&lt;/code&gt; on the main thread and send a pause/resume message to the Worker. The emulation stops completely when the app is backgrounded and picks up exactly where it left off when the user returns. No state loss, no audio glitch on resume. If you ever need background execution (say, for a music player or a long-running computation), just don't send the pause — the Worker keeps ticking regardless of what the main thread is doing. Having that as a conscious choice rather than a browser-imposed limitation is a nice side effect of the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;On the same slow Android phone that produced dying-battery audio with the single-threaded architecture: smooth, consistent, correct-speed audio. The Web Worker generates samples at a steady 60fps via &lt;code&gt;setInterval&lt;/code&gt;, completely independent of the main thread's rendering frame rate. The SharedArrayBuffer bridge adds effectively zero latency.&lt;/p&gt;

&lt;p&gt;The visual frames might drop to 30fps on a slow device — the game looks a bit less smooth — but the audio is untouched. That's the right tradeoff. Humans tolerate choppy video far better than choppy audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Thread architecture is a day-one decision, not an optimization.&lt;/strong&gt; I built the single-threaded version first because it was faster to prototype. Then I spent more time patching audio hacks than the Worker migration ultimately took. If your app does real-time audio/video processing, put the producer on a separate thread from the start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SharedArrayBuffer is the right tool for high-frequency inter-thread data.&lt;/strong&gt; For audio at 44,100 samples/second, &lt;code&gt;postMessage&lt;/code&gt; adds too much jitter. For input events at 10-30/second, &lt;code&gt;postMessage&lt;/code&gt; is perfectly fine. Match the tool to the frequency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transferable objects are free.&lt;/strong&gt; If you're passing large &lt;code&gt;ArrayBuffer&lt;/code&gt;s between threads via &lt;code&gt;postMessage&lt;/code&gt;, mark them as transferable. Zero copy, zero overhead. Just remember the sender loses access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PWAs on iOS are a different platform entirely.&lt;/strong&gt; Don't assume web APIs work the same. The Fullscreen API doesn't exist. Touch events behave differently for fixed-position elements. Icons need specific formats. Test on an actual iPhone, not just Chrome DevTools mobile emulation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test on the slowest device first.&lt;/strong&gt; If I'd tested on the old Android phone on day one, I would have designed for Workers from the start. Testing only on fast hardware hides architectural problems that become very expensive to fix later.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a mobile developer (Android/Kotlin, Flutter) exploring the browser as a platform for real-time applications. If you've dealt with Web Workers, SharedArrayBuffer, or PWA quirks on iOS, I'd love to hear about your experiences in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>pwa</category>
      <category>webworkers</category>
    </item>
    <item>
      <title>Building in Public: The Architecture of a Solo Rust Project</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Mon, 06 Apr 2026 08:18:27 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/building-in-public-the-architecture-of-a-solo-rust-project-40jm</link>
      <guid>https://dev.to/sharminsirajudeen/building-in-public-the-architecture-of-a-solo-rust-project-40jm</guid>
      <description>&lt;h1&gt;
  
  
  Building in Public: The Architecture of a Solo Rust Project
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;I'm the creator of &lt;a href="https://drengr.dev" rel="noopener noreferrer"&gt;Drengr&lt;/a&gt;, an MCP server that gives AI agents eyes and hands on mobile devices. I started this blog to share the engineering behind it. No pretending to be a neutral observer writing a think piece — I built this, and I'm here to talk about it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm a solo developer building a Rust project, and I want to talk about what that actually looks like. Not the polished "launched on Product Hunt and got 500 stars" version, but the real one — the architecture decisions made at midnight, the bugs that took days, and the strange irony of using AI to build AI tooling.&lt;/p&gt;

&lt;p&gt;Drengr started as a research question: can I give an AI agent a phone? No venture capital, no team, no timeline pressure. Just curiosity and a problem that felt important enough to spend months on. Building in public means sharing the journey honestly, including the parts that don't look impressive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm Building This Alone
&lt;/h2&gt;

&lt;p&gt;The honest answer is that this project started before I knew it was a project. After ten years of writing Android apps and watching every UI test suite I touched decay faster than we could maintain it, I started experimenting with whether AI could do better. I hacked together a Python script that captured screenshots and sent them to an LLM API with action instructions. It worked badly, but it worked. That script became a prototype, the prototype became an architecture, and the architecture demanded Rust.&lt;/p&gt;

&lt;p&gt;At no point did I sit down and say "I'm going to build a product." I kept solving the next problem. The next problem kept being interesting. Drengr is my first real tool — the first thing I've built that isn't an internal script or a weekend experiment. Six months later, I have about 6,300 lines of Rust, a working MCP server, and the beginning of something I think could matter.&lt;/p&gt;

&lt;p&gt;Solo development has real trade-offs. I don't have anyone to review my code. I don't have anyone to challenge my architectural decisions. When I make a mistake, there's no one to catch it until a user reports a bug. The upside is speed — I can refactor the entire transport layer on a Saturday without scheduling a meeting.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;Drengr's architecture is built around one core abstraction: the transport layer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Transport Trait
&lt;/h3&gt;

&lt;p&gt;A single Rust trait defines what it means to "talk to a device":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;trait&lt;/span&gt; &lt;span class="n"&gt;Transport&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;capture_screen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;Screenshot&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;get_ui_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;UiElement&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;execute_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;()&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;query_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="n"&gt;StateResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt;&lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three implementations exist: &lt;strong&gt;ADB&lt;/strong&gt; for Android devices and emulators, &lt;strong&gt;simctl&lt;/strong&gt; for iOS simulators, and &lt;strong&gt;Appium&lt;/strong&gt; for cloud device farms. Each speaks a completely different protocol. ADB uses shell commands and binary protocols. Simctl uses Apple's command-line tools. Appium uses HTTP/WebDriver.&lt;/p&gt;

&lt;p&gt;The rest of the codebase doesn't know or care which one is active. The MCP handler, the OODA loop, the screen annotation system — they all work through the trait. Adding a new platform means implementing four methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Handler
&lt;/h3&gt;

&lt;p&gt;The MCP server reads JSON-RPC from stdin and writes responses to stdout. This sounds simple until you realize that the device interactions also write to stdout (ADB commands, for instance, produce output). One of my earliest architectural decisions was redirecting child process I/O to avoid polluting the MCP channel.&lt;/p&gt;

&lt;p&gt;The handler routes incoming tool calls to one of three paths: &lt;code&gt;drengr_look&lt;/code&gt; triggers a screen capture and UI tree extraction, &lt;code&gt;drengr_do&lt;/code&gt; dispatches an action to the transport layer, and &lt;code&gt;drengr_query&lt;/code&gt; reads state without side effects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screen Annotation
&lt;/h3&gt;

&lt;p&gt;When the agent calls &lt;code&gt;drengr_look&lt;/code&gt;, it doesn't just get a raw screenshot. Drengr extracts the UI hierarchy, identifies interactive elements, assigns each a number, and returns both the annotated information and the element metadata. The agent can then say "tap element 7" instead of "tap at coordinates (342, 891)."&lt;/p&gt;

&lt;p&gt;This annotation system is more important than it might seem. It bridges the gap between how the AI perceives the screen (as a visual field) and how the device accepts input (as structured commands). Without it, every interaction requires the agent to estimate pixel coordinates from visual inspection, which is unreliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 6,300 Lines of Rust Taught Me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The compiler is your strictest code reviewer.&lt;/strong&gt; I've lost count of the number of times the borrow checker rejected code that I was confident was correct, only to realize on reflection that it was catching a real problem. Not always a bug — sometimes a design issue. "You can't hold a mutable reference to the transport while also iterating over its UI tree results" is the compiler's way of saying "your data flow is tangled."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If it compiles, it probably works.&lt;/strong&gt; This cliche has limits — logic errors still exist, integration tests still matter — but the density of runtime bugs per line of code is lower than anything I've experienced in other languages. When I do hit a bug, it's almost always in my logic, not in my memory management, not in my error handling, and not in my concurrency model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ownership semantics forced better architecture.&lt;/strong&gt; In Python or JavaScript, I'd have passed the transport connection around freely, probably storing references in three different places. Rust forced me to think about who owns the connection and who borrows it. That constraint produced a cleaner architecture than I would have designed voluntarily.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hardest Bug
&lt;/h2&gt;

&lt;p&gt;MCP over stdio means Drengr reads JSON-RPC requests from stdin and writes responses to stdout. Simple enough — until you spawn an ADB shell command that also writes to stdout.&lt;/p&gt;

&lt;p&gt;The first time this happened, the MCP client received a response that started with a valid JSON-RPC frame, continued with "List of devices attached," and then had another JSON-RPC frame. The client understandably choked.&lt;/p&gt;

&lt;p&gt;The fix required redirecting all child process stdout to &lt;code&gt;/dev/null&lt;/code&gt; or to a captured buffer, using &lt;code&gt;os::unix::io&lt;/code&gt; and &lt;code&gt;dup2&lt;/code&gt; to manage file descriptors at the system call level. It's about 30 lines of code. It took me two full days to debug, because the symptoms were intermittent — ADB only writes to stdout under certain conditions, so the MCP corruption was sporadic.&lt;/p&gt;

&lt;p&gt;This is the kind of bug that doesn't exist in simpler architectures. If Drengr were an HTTP server instead of a stdio server, the problem would never have arisen. But MCP over stdio is the standard for local tool servers, and for good reason — it's simpler for the client, requires no port management, and works inside sandboxed environments. The complexity is justified; the bug was the price of admission.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Irony of Using AI to Build AI Tooling
&lt;/h2&gt;

&lt;p&gt;I use Claude Code daily to work on Drengr. Claude helps me write the code that teaches Claude to use phones. The recursion is not lost on me.&lt;/p&gt;

&lt;p&gt;It's genuinely productive. Claude is good at Rust — it understands ownership patterns, suggests idiomatic approaches, and catches issues I miss. When I was implementing the situation engine, Claude helped me think through the state comparison logic. When I was wrestling with async trait objects, Claude explained the &lt;code&gt;Pin&amp;amp;lt;Box&amp;amp;lt;dyn Future&amp;amp;gt;&amp;amp;gt;&lt;/code&gt; pattern in a way that finally clicked.&lt;/p&gt;

&lt;p&gt;The irony runs deeper, though. Every improvement I make to Drengr makes Claude slightly better at interacting with mobile devices. A better screen annotation system means Claude gets better information. A better situation engine means Claude makes fewer mistakes. I'm building a tool that improves the capability of the AI that helps me build the tool.&lt;/p&gt;

&lt;p&gt;I don't think this is unique to my project. Every developer using AI to build AI tools is in this feedback loop. But working on it daily makes the loop very tangible.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's on the Roadmap
&lt;/h2&gt;

&lt;p&gt;Three things I'm actively working on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dashboard.&lt;/strong&gt; A web interface for visualizing test runs, reviewing agent decisions, and correlating UI actions with network traffic. The technical spec is written; implementation is next.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Real-time steering.&lt;/strong&gt; The ability to watch an agent run and redirect it mid-session. "Stop exploring settings, go test the checkout flow instead." This requires a WebSocket connection between the dashboard and the running Drengr process.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Network monitoring.&lt;/strong&gt; An SDK that apps can integrate to capture network traffic during Drengr sessions. This lets the dashboard show what API calls happened alongside each UI action — invaluable for debugging integration issues.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to Get Involved
&lt;/h2&gt;

&lt;p&gt;Drengr is proprietary, but the community is open. I've set up &lt;a href="https://github.com/SharminSirajudeen/drengr-community/discussions" rel="noopener noreferrer"&gt;GitHub Discussions&lt;/a&gt; for questions, feedback, and feature requests. The areas where I'd most appreciate input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Testing on diverse devices.&lt;/strong&gt; I develop on a limited set of emulator configurations. Reports of how Drengr behaves on different Android versions, screen sizes, and manufacturer overlays are extremely valuable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompt engineering for test scenarios.&lt;/strong&gt; The quality of Drengr's autonomous testing depends heavily on how the goal is expressed. I'm collecting effective prompts and would love contributions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Bug reports and feature ideas.&lt;/strong&gt; The best way to shape Drengr's direction is to use it and tell me what's missing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or just try it. &lt;code&gt;curl -fsSL https://drengr.dev/install.sh | bash&lt;/code&gt;. Connect a device. Point Claude at it. Tell me what happens.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The best feedback isn't "great project." It's "I tried this and it broke." That's how the tool gets better.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Building in public means accepting that people will see the rough edges. I'm okay with that. The rough edges are where the interesting problems live.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Drengr is free to use and available on &lt;a href="https://www.npmjs.com/package/drengr" rel="noopener noreferrer"&gt;npm&lt;/a&gt;. It supports Android (physical devices, emulators), iOS simulators (full gesture support), and cloud device farms (BrowserStack, SauceLabs, AWS Device Farm, LambdaTest, Perfecto, Kobiton). Built in Rust. Single binary. No runtime dependencies.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>appium</category>
      <category>mobiledev</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Why I Chose Rust Over C and C++ for Drengr</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Mon, 06 Apr 2026 08:14:23 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/why-i-chose-rust-over-c-and-c-for-drengr-2fk3</link>
      <guid>https://dev.to/sharminsirajudeen/why-i-chose-rust-over-c-and-c-for-drengr-2fk3</guid>
      <description>&lt;h1&gt;
  
  
  Why I Chose Rust Over C and C++ for Drengr
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;I'm the creator of &lt;a href="https://drengr.dev" rel="noopener noreferrer"&gt;Drengr&lt;/a&gt;, an MCP server that gives AI agents eyes and hands on mobile devices. I started this blog to share the engineering behind it. No pretending to be a neutral observer writing a think piece — I built this, and I'm here to talk about it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I tell people I built a mobile automation tool in Rust, the first question is always "why not Python?" I've written about that in &lt;a href="https://dev.to/blog/why-not-python"&gt;a separate post&lt;/a&gt;. But the question that actually kept me up at night during the early architecture phase was different: &lt;strong&gt;why not C or C++?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drengr is a CLI tool that talks to Android devices over ADB, iOS simulators over simctl, and cloud devices over Appium WebDriver. It parses UI trees, captures screenshots, manages concurrent device sessions, and serves as an MCP server over stdio. This is systems programming territory. C and C++ have owned this space for decades. So why Rust?&lt;/p&gt;

&lt;p&gt;This isn't a "Rust vs C++" holy war post. I've worked with C and C++ in different contexts over the years — JNI bridges and NDK modules at work when Java wasn't fast enough for real-time audio processing or custom camera pipelines, a raytracer in C++ during university that taught me more about segfaults than about light, and the usual Arduino/embedded experiments that every CS student does at some point. Enough to know what these languages are good at and where they hurt. This is an honest account of a specific decision for a specific project, with the trade-offs I actually faced.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Case for C
&lt;/h2&gt;

&lt;p&gt;C was tempting. ADB itself is written in C++. The Android debug bridge protocol is well-documented at the C level. I could have called into ADB's libraries directly, skipping the subprocess overhead entirely. A C binary would be tiny — potentially under 1MB with static linking and aggressive stripping.&lt;/p&gt;

&lt;p&gt;I seriously considered it. For about two days.&lt;/p&gt;

&lt;p&gt;The problem crystallized when I started sketching the MCP server. MCP is JSON-RPC 2.0 over stdio. That means parsing JSON, routing method calls, managing request/response correlation, handling concurrent tool invocations. In C, I'd need a JSON parser (jansson? cJSON? write my own?), string handling that doesn't segfault, and manual memory management for every request/response lifecycle.&lt;/p&gt;

&lt;p&gt;I've seen enough C codebases to know what this looks like. It looks like 60% of your code being memory management boilerplate, and the remaining 40% being the actual logic you care about. For a research project where I need to iterate fast and try experimental approaches to screen parsing and AI agent loops, that ratio is fatal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Case for C++
&lt;/h2&gt;

&lt;p&gt;C++ was a stronger contender. Modern C++ (17/20) has smart pointers, string_view, std::optional, std::variant — many of the ergonomic features that make Rust pleasant to write. The ADB ecosystem is native C++. I could use nlohmann/json for parsing. The standard library has threads, mutexes, condition variables.&lt;/p&gt;

&lt;p&gt;Three things killed it for me:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Build System Problem
&lt;/h3&gt;

&lt;p&gt;I wanted a single static binary that anyone could curl and run. No shared library dependencies, no runtime requirements, no "install libfoo-dev first." In Rust, this is &lt;code&gt;cargo build --release --target x86_64-unknown-linux-musl&lt;/code&gt;. Done.&lt;/p&gt;

&lt;p&gt;In C++, static linking is a odyssey. CMake or Meson? Which standard library — libstdc++ or libc++? Static linking glibc is technically possible but discouraged and produces larger binaries with potential compatibility issues. Musl works but you need a separate toolchain. Cross-compilation for Apple Silicon from Linux? I'd need a cross-compiler toolchain per target triple.&lt;/p&gt;

&lt;p&gt;Cargo handles all of this. I add a target, run the build, get a binary. The CI matrix in my GitHub Actions workflow is 20 lines. The equivalent CMake + cross-compilation setup would be 200+.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Concurrency Without Fear
&lt;/h3&gt;

&lt;p&gt;Drengr manages multiple concurrent operations: the MCP server handles requests while the SDK server listens for in-app network events, the OODA loop runs autonomous agent sessions, and the explore mode does BFS traversal with concurrent screen captures. These all share state — the current device transport, the screen annotation cache, the situation engine.&lt;/p&gt;

&lt;p&gt;In C++, shared mutable state across threads means choosing between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Raw mutexes with manual lock/unlock discipline (and hoping you never forget)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Atomic operations for primitives (and hoping your lock-free algorithm is actually correct)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Higher-level abstractions like folly::Synchronized (and adding Facebook's folly as a dependency)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Data races in C++ are undefined behavior. Not "your program crashes." Undefined behavior. The compiler is allowed to do literally anything. Time travel. Nasal demons. In practice, it means subtle corruption that shows up three hours into a test session as a garbled screenshot or a silently wrong element count.&lt;/p&gt;

&lt;p&gt;In Rust, the type system prevents data races at compile time. If I try to share a mutable reference across threads without proper synchronization, it doesn't compile. Period. The compiler forces me to use &lt;code&gt;Arc&amp;amp;lt;Mutex&amp;amp;lt;T&amp;amp;gt;&amp;amp;gt;&lt;/code&gt; or channels or atomics explicitly. I can't accidentally share a raw pointer to a screen buffer across two async tasks.&lt;/p&gt;

&lt;p&gt;For a tool that manages real device sessions — where a bug could mean sending the wrong tap to the wrong device — this isn't a nice-to-have. It's a requirement.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The Dependency Story
&lt;/h3&gt;

&lt;p&gt;Drengr depends on reqwest (HTTP client), tokio (async runtime), serde (serialization), image (screenshot processing), and about 30 other crates. Adding a dependency in Rust is one line in Cargo.toml. Cargo downloads, compiles, and statically links it. Version resolution is automatic. Security advisories are tracked by &lt;code&gt;cargo audit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In C++, every dependency is a project. Do they use CMake? Meson? Autotools? Their own bespoke build system? Do they support static linking? Are their transitive dependencies compatible with mine? The Conan and vcpkg package managers have improved this, but they're still far from Cargo's "it just works" experience.&lt;/p&gt;

&lt;p&gt;I estimated that managing C++ dependencies alone would cost me 2-3 weeks of the early development timeline. In a solo project where every week counts, that's not acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Miss From C/C++
&lt;/h2&gt;

&lt;p&gt;Honesty requires admitting what Rust costs me.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compile Times
&lt;/h3&gt;

&lt;p&gt;A clean build of Drengr takes about 90 seconds. An incremental build after touching one file takes 8-12 seconds. The equivalent C project would compile in under 5 seconds clean, under 1 second incremental. When I'm iterating on screen parsing logic and want to test against a real device, those seconds add up.&lt;/p&gt;

&lt;p&gt;I've mitigated this with &lt;code&gt;cargo watch&lt;/code&gt; and by structuring the crate to minimize recompilation, but it's a real cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Learning Curve
&lt;/h3&gt;

&lt;p&gt;I knew C and C++ before I knew Rust. The borrow checker's mental model — ownership, borrowing, lifetimes — took weeks to internalize. There were days early in the project where I spent more time fighting the compiler than writing features. Async Rust made it worse: pinning, Send/Sync bounds, the colored function problem.&lt;/p&gt;

&lt;p&gt;If I'd written Drengr in C++, the first prototype would have been done a week earlier. No question. But I believe the Rust version has fewer bugs, and I spend almost zero time debugging memory issues. That trade-off has compounded in my favor over the months since.&lt;/p&gt;

&lt;h3&gt;
  
  
  FFI Friction
&lt;/h3&gt;

&lt;p&gt;ADB is a C++ tool. Some interactions would be more natural in C++ — direct FFI into ADB's libraries, for example. Instead, I shell out to the adb binary as a subprocess. It works, but it adds latency (spawning a process per command) and complexity (parsing stdout). A C++ implementation could potentially link against libadb directly.&lt;/p&gt;

&lt;p&gt;In practice, the subprocess approach has been fine. ADB commands complete in 10-50ms typically, and the parsing is straightforward. But it's an architectural compromise I wouldn't need in C++.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;p&gt;After six months of development:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;~6,300 lines of Rust&lt;/strong&gt; — this includes the MCP server, three device transports (ADB, simctl, Appium), the OODA loop, the explore mode, the test runner, the SDK server, screen annotation, and the situation engine&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Zero memory-related bugs&lt;/strong&gt; in production. Not one use-after-free, double-free, buffer overflow, or data race&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;189 tests&lt;/strong&gt;, all passing. The test suite runs in under 3 seconds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Binary size: ~15MB&lt;/strong&gt; stripped, with LTO fat optimization. A C equivalent might be 3-5MB, but 15MB for a tool that includes an HTTP client, JSON parser, image processing, and async runtime is reasonable&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cold start: ~15ms&lt;/strong&gt; to first MCP response. This matters when AI agents are waiting&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;If I started over tomorrow, I'd still choose Rust. But I'd do a few things differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Start with synchronous code, add async later.&lt;/strong&gt; I went async-first with tokio, which complicated the early prototyping phase. Many of the ADB interactions don't benefit from async — they're sequential command-response pairs. I could have started synchronous and migrated the concurrent parts later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Use fewer abstractions early.&lt;/strong&gt; I over-engineered the transport trait in the first version. Three concrete implementations of a simple interface would have been clearer than a trait with twelve methods and two associated types.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Accept more unsafe.&lt;/strong&gt; I avoided unsafe entirely for the first four months. Some of the ADB binary protocol parsing would have been cleaner with unsafe pointer arithmetic in a well-tested, isolated module. Rust's unsafe isn't C — it's a clearly bounded region where you tell the compiler "I've verified this manually." I was too cautious.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Real Answer
&lt;/h2&gt;

&lt;p&gt;The real reason I chose Rust over C and C++ isn't any single technical argument. It's this: Rust lets me write systems-level code at the speed I think, with the confidence that the compiler has caught the classes of bugs that would otherwise cost me debugging days.&lt;/p&gt;

&lt;p&gt;For a solo developer building a research project that interacts with real hardware, manages concurrent sessions, and serves as infrastructure for AI agents — that confidence isn't a luxury. It's the difference between shipping and not shipping.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't have a team to review my pointer arithmetic. I don't have a QA department to catch my data races. I have the Rust compiler. And it's the most reliable colleague I've ever worked with.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;C and C++ are extraordinary languages. They power the systems Drengr sits on top of — the operating systems, the ADB daemon, the simctl infrastructure. I have deep respect for them. But for this project, at this scale, as a solo developer? Rust was the right call.&lt;/p&gt;

&lt;p&gt;The binary works. The code is correct. And I sleep well at night knowing the compiler has my back.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Drengr is free to use and available on &lt;a href="https://www.npmjs.com/package/drengr" rel="noopener noreferrer"&gt;npm&lt;/a&gt;. It supports Android (physical devices, emulators), iOS simulators (full gesture support), and cloud device farms (BrowserStack, SauceLabs, AWS Device Farm, LambdaTest, Perfecto, Kobiton). Built in Rust. Single binary. No runtime dependencies.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>appium</category>
      <category>mobiledev</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
    <item>
      <title>From ADB Shell to AI Agent: The Quiet Revolution in Mobile Automation</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Mon, 06 Apr 2026 08:12:22 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/from-adb-shell-to-ai-agent-the-quiet-revolution-in-mobile-automation-7jn</link>
      <guid>https://dev.to/sharminsirajudeen/from-adb-shell-to-ai-agent-the-quiet-revolution-in-mobile-automation-7jn</guid>
      <description>&lt;h1&gt;
  
  
  From ADB Shell to AI Agent: The Quiet Revolution in Mobile Automation
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;I'm the creator of &lt;a href="https://drengr.dev" rel="noopener noreferrer"&gt;Drengr&lt;/a&gt;, an MCP server that gives AI agents eyes and hands on mobile devices. I started this blog to share the engineering behind it. No pretending to be a neutral observer writing a think piece — I built this, and I'm here to talk about it.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Correction (28 July 2026):&lt;/strong&gt; an earlier version of the comparison table listed Maestro and Appium as having no AI features and no MCP support. Both have them — Maestro ships &lt;code&gt;assertWithAI&lt;/code&gt;, &lt;code&gt;extractTextWithAI&lt;/code&gt;, and AI defect detection alongside a first-party MCP server, and Appium maintains &lt;a href="https://github.com/appium/appium-mcp" rel="noopener noreferrer"&gt;&lt;code&gt;appium/appium-mcp&lt;/code&gt;&lt;/a&gt;. The table below is corrected, and the distinction I was actually reaching for is now its own row. Thanks to everyone who flagged it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mobile test automation has a longer history than most developers realize, and the AI-driven approach I'm exploring with Drengr sits at the end of a progression that started with raw ADB shell commands in 2009. Understanding that progression matters — not because history is inherently interesting (though I think it is), but because each generation solved real problems while creating new ones. Every mobile automation tool, including mine, is a response to the limitations of what came before. Knowing those limitations helps evaluate what's genuinely new and what's just repackaging.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ADB Era (2009-2012)
&lt;/h2&gt;

&lt;p&gt;Android Debug Bridge shipped with the Android SDK, and it included a deceptively simple capability: &lt;code&gt;adb shell input&lt;/code&gt;. You could inject taps, swipes, and key events from a terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adb shell input tap 500 300
adb shell input text &lt;span class="s2"&gt;"hello"&lt;/span&gt;
adb shell input swipe 500 1500 500 500 300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developers wrote bash scripts that chained these commands together. Open the app, wait 2 seconds, tap the login button at coordinates (340, 780), type the username, tap the next field at (340, 860), type the password.&lt;/p&gt;

&lt;p&gt;The problems were immediate and severe. Coordinates were absolute pixels. A script written for a 1080p phone broke on a 720p phone. A script written for one app version broke when the developer moved a button 50 pixels down. There was no way to query the UI state — you sent commands blind and hoped for the best.&lt;/p&gt;

&lt;p&gt;But ADB shell automation proved something important: developers wanted to automate mobile testing, even with terrible tools. The demand was real.&lt;/p&gt;

&lt;h2&gt;
  
  
  UIAutomator and Espresso (2012-2015)
&lt;/h2&gt;

&lt;p&gt;Google responded with proper frameworks. UIAutomator provided black-box testing — you could find elements by resource ID, text, or description, rather than coordinates. Espresso provided white-box testing for Android — fast, deterministic tests that ran inside the app process.&lt;/p&gt;

&lt;p&gt;These were real, production-quality tools. Espresso, in particular, is excellent. Its automatic synchronization with the UI thread eliminates an entire category of flaky tests. If you're doing Android-only testing with access to the source code, Espresso remains hard to beat in 2026.&lt;/p&gt;

&lt;p&gt;The limitations: both are Android-only, language-locked to Java or Kotlin, and require compilation against the app. You can't use Espresso to test someone else's app. You can't use UIAutomator for iOS. And for teams building cross-platform products, maintaining separate test suites for each platform is expensive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appium's Universal Vision (2013-2020)
&lt;/h2&gt;

&lt;p&gt;Appium had an ambitious idea: apply the WebDriver protocol — the same standard that powered Selenium for web testing — to mobile devices. Write tests in any language. Run them against any platform. One API to rule them all.&lt;/p&gt;

&lt;p&gt;The vision was compelling, and Appium built a real foundation. It proved that cross-platform mobile testing was possible. Major companies adopted it. A huge ecosystem of plugins, drivers, and integrations grew around it.&lt;/p&gt;

&lt;p&gt;But the architecture carried inherent weight. Appium runs a Node.js server that translates WebDriver commands into platform-specific actions through a chain of drivers. Setting up Appium meant installing Node.js, Java (for the Android driver), the appropriate SDKs, and getting all the versions to align. Session management was fragile. Tests that passed on one Appium version broke on the next. "Flaky tests" became almost synonymous with mobile automation in many teams.&lt;/p&gt;

&lt;p&gt;Appium built the foundation. I want to be clear about that — a lot of what exists today in mobile automation stands on Appium's groundwork.&lt;/p&gt;

&lt;h2&gt;
  
  
  Maestro's Simplification (2022-2024)
&lt;/h2&gt;

&lt;p&gt;Maestro, from mobile.dev, asked a sharp question: what if mobile testing was actually simple? Their answer was YAML-based test flows that you could write in minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;appId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;com.example.app&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;launchApp&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sign&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;In"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;inputText&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user@example.com"&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;email_field"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;tapOn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Continue"&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;assertVisible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;back"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five-minute setup. No WebDriver server. No driver management. Just a CLI that talked directly to the device. Maestro proved that developer UX matters in testing tools — that a tool people actually enjoy using gets adopted, even if it has fewer features than the heavyweight alternative.&lt;/p&gt;

&lt;p&gt;What Maestro didn't change: you still wrote every test manually. Their AI commands (&lt;code&gt;assertWithAI&lt;/code&gt;, &lt;code&gt;extractTextWithAI&lt;/code&gt;) make individual &lt;em&gt;assertions&lt;/em&gt; smarter, but the flow they run inside is still one you authored, step by step. Every edge case had to be anticipated by a human who understood the app. The tool was simpler, but the work was the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Shift (2024-2026)
&lt;/h2&gt;

&lt;p&gt;Two things happened in 2024-2025 that opened a genuinely new direction for mobile automation.&lt;/p&gt;

&lt;p&gt;First, multimodal LLMs became good enough to reliably interpret screenshots. Not perfectly — I've written about the limitations — but well enough to identify buttons, text fields, navigation elements, and app state from a screenshot alone. The agent could &lt;em&gt;see&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Second, Anthropic published the Model Context Protocol. MCP gave those capable-but-isolated LLMs a standard way to discover and invoke external tools. An AI model could now say "I want to tap element 5 on this screen" and have that intention reliably translated into a device action through a well-defined protocol.&lt;/p&gt;

&lt;p&gt;These two ingredients — vision and tool use — are what make AI-driven mobile testing possible. Not just theoretically possible, but practically achievable by a solo developer building in Rust on weekends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I Think This Is Heading
&lt;/h2&gt;

&lt;p&gt;The progression I see is from &lt;strong&gt;imperative&lt;/strong&gt; to &lt;strong&gt;declarative&lt;/strong&gt; to &lt;strong&gt;goal-oriented&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;ADB was imperative: tap here, swipe there, type this. Espresso was declarative: find this element, verify this state. Maestro was declarative with better DX.&lt;/p&gt;

&lt;p&gt;Drengr is my attempt at goal-oriented: "verify that a user can sign up, log in, and post a message." The agent figures out the how. It adapts to the specific app. It handles UI variations and unexpected states. You describe what should work, not how to test it.&lt;/p&gt;

&lt;p&gt;I'm not claiming this is solved. The previous sections of this blog document the limitations in detail. But I do believe the direction is correct: AI agents that explore apps like humans do, finding bugs through curiosity rather than scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison
&lt;/h2&gt;

&lt;p&gt;This is my honest assessment of the current landscape. I've tried to be fair — every tool on this list solves real problems for real teams.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Appium&lt;/th&gt;
&lt;th&gt;Maestro&lt;/th&gt;
&lt;th&gt;Detox&lt;/th&gt;
&lt;th&gt;Espresso / XCUITest&lt;/th&gt;
&lt;th&gt;Drengr&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Server + driver + client lib&lt;/td&gt;
&lt;td&gt;Single binary&lt;/td&gt;
&lt;td&gt;npm + build config&lt;/td&gt;
&lt;td&gt;In-project, platform SDK&lt;/td&gt;
&lt;td&gt;Single binary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Platforms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Android, iOS, web&lt;/td&gt;
&lt;td&gt;Android, iOS, web&lt;/td&gt;
&lt;td&gt;React Native only&lt;/td&gt;
&lt;td&gt;One platform each&lt;/td&gt;
&lt;td&gt;Android, iOS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Test is written as&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Code (many languages)&lt;/td&gt;
&lt;td&gt;Declarative YAML&lt;/td&gt;
&lt;td&gt;JS code&lt;/td&gt;
&lt;td&gt;Platform code&lt;/td&gt;
&lt;td&gt;A plain-English goal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who picks the next step&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The agent&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AI features&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Via MCP + plugins&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;assertWithAI&lt;/code&gt;, &lt;code&gt;extractTextWithAI&lt;/code&gt;, defect detection&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;The entire loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MCP server&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (first-party)&lt;/td&gt;
&lt;td&gt;Yes (first-party)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes — it &lt;em&gt;is&lt;/em&gt; the product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deterministic&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mostly&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt; — same goal, different path each run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best at&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Breadth, any stack&lt;/td&gt;
&lt;td&gt;Fast, reliable scripted flows&lt;/td&gt;
&lt;td&gt;RN integration tests&lt;/td&gt;
&lt;td&gt;Deepest platform access&lt;/td&gt;
&lt;td&gt;The paths nobody scripted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maturity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Very mature&lt;/td&gt;
&lt;td&gt;Mature&lt;/td&gt;
&lt;td&gt;Mature&lt;/td&gt;
&lt;td&gt;Very mature&lt;/td&gt;
&lt;td&gt;Early&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two rows carry the argument. "Who picks the next step" is the real line — Maestro's AI asserts &lt;em&gt;inside&lt;/em&gt; a flow you wrote, and Appium's MCP hands drive primitives to an agent you orchestrate. In both, a human still authored the sequence. That is the thing Drengr does not require.&lt;/p&gt;

&lt;p&gt;I want to call attention to the "Deterministic" row. Drengr is the only "No" in that column, and that matters. When you run an Espresso test, you get the same result every time. When you run a Drengr exploration, you might get different paths, different findings, different coverage. That's a feature for exploratory testing and a limitation for regression testing. Both are valid use cases; the right tool depends on what you need.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Appium built the foundation. Maestro proved that developer UX matters. I built Drengr because I saw a gap: what if the test itself was intelligent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Whether that intelligence proves more valuable than determinism in practice is still an open question. I have early evidence that it is, for certain types of testing. But I'd rather present the question honestly than claim to have answered it definitively.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Drengr is free to use and available on &lt;a href="https://www.npmjs.com/package/drengr" rel="noopener noreferrer"&gt;npm&lt;/a&gt;. It supports Android (physical devices, emulators), iOS simulators (full gesture support), and cloud device farms (BrowserStack, SauceLabs, AWS Device Farm, LambdaTest, Perfecto, Kobiton). Built in Rust. Single binary. No runtime dependencies.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>appium</category>
      <category>mobiledev</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Giving Claude a Phone: How I Built an MCP Server for Mobile Devices</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Mon, 06 Apr 2026 08:08:19 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/giving-claude-a-phone-how-i-built-an-mcp-server-for-mobile-devices-270b</link>
      <guid>https://dev.to/sharminsirajudeen/giving-claude-a-phone-how-i-built-an-mcp-server-for-mobile-devices-270b</guid>
      <description>&lt;h1&gt;
  
  
  Giving Claude a Phone: How I Built an MCP Server for Mobile Devices
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;I'm the creator of &lt;a href="https://drengr.dev" rel="noopener noreferrer"&gt;Drengr&lt;/a&gt;, an MCP server that gives AI agents eyes and hands on mobile devices. I started this blog to share the engineering behind it. No pretending to be a neutral observer writing a think piece — I built this, and I'm here to talk about it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After a decade of writing Android apps, I'd accepted a certain rhythm: write the code, build, deploy to a device, tap around manually, find the bug, go back to the IDE, fix it, repeat. When AI coding assistants arrived, they changed most of that loop — Claude could write a RecyclerView adapter with DiffUtil callbacks and proper coroutine scoping faster than I could type the class name. But it couldn't tap a single button on the emulator sitting right next to it. The code was flawless. The app was running. And the AI that wrote it had absolutely no way to verify its own work.&lt;/p&gt;

&lt;p&gt;That disconnect — combined with years of watching Espresso and Appium test suites rot faster than teams could maintain them — made me think there had to be a better way. What if the AI could see the screen, understand what it's looking at, and interact with the app directly? Not through brittle element IDs, but through actual comprehension. This is the story of how I built Drengr — my first real tool, an MCP server that gives AI agents eyes and hands on mobile devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Frustration That Started It
&lt;/h2&gt;

&lt;p&gt;If you've used Claude or any capable LLM for mobile development, you've hit this wall. The AI helps you write code, debug layouts, even architect entire features. But the moment you need to verify something on an actual device, you're on your own. Copy the code, build, deploy, tap around, find the bug, go back to the AI, describe what you saw in words.&lt;/p&gt;

&lt;p&gt;It's 2026, and the feedback loop between AI and mobile devices is still mediated entirely by human hands and human descriptions. As someone who spent ten years in that loop, it felt wrong to me. Not because automation is always better, but because the information loss is enormous. I can describe a broken layout to Claude, but Claude seeing the broken layout is fundamentally different.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Insight: MCP as the Bridge
&lt;/h2&gt;

&lt;p&gt;Anthropic's Model Context Protocol gave me the architecture I needed. MCP defines a standard way for AI models to discover and invoke tools — a JSON-RPC protocol over stdio or HTTP. Instead of building a bespoke integration, I could build an MCP server that exposes mobile device capabilities as tools that any MCP-compatible client can call.&lt;/p&gt;

&lt;p&gt;The key insight was constraint. I didn't need to expose every possible device operation. I needed exactly three tools that would give an AI agent enough capability to understand and interact with any mobile app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Tools, Three Verbs
&lt;/h3&gt;

&lt;p&gt;Drengr exposes exactly three MCP tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;drengr_look&lt;/strong&gt; — Observes the current screen. Captures a screenshot, extracts the UI hierarchy, and returns an annotated view where every interactive element is numbered. The agent sees what a user would see, but with machine-readable structure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;drengr_do&lt;/strong&gt; — Executes an action. Tap element 3, type "hello world", swipe up, press back. These are the hands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;drengr_query&lt;/strong&gt; — Reads device state without side effects. Check if an element exists, read text content, get the current activity name. This is the quiet observer — it never changes anything.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. Three tools. Every mobile interaction I've needed — from opening apps to navigating complex flows to filling forms — reduces to sequences of look, do, and query.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Claude Actually Does With a Phone
&lt;/h2&gt;

&lt;p&gt;Let me describe a real session. I asked Claude, through Drengr, to "open YouTube and find a video about the Model Context Protocol."&lt;/p&gt;

&lt;p&gt;Claude called &lt;code&gt;drengr_look&lt;/code&gt; first. It received back an annotated screenshot showing the home screen with numbered elements — the app drawer, status bar icons, and the YouTube icon labeled as element 14. Claude called &lt;code&gt;drengr_do&lt;/code&gt; with &lt;code&gt;{"action": "tap", "element": 14}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;YouTube opened. Claude called &lt;code&gt;drengr_look&lt;/code&gt; again. Now it could see the YouTube home feed with a search icon at element 2. It tapped that, got a keyboard and search field, typed "Model Context Protocol MCP", and hit enter. Results appeared. Claude called &lt;code&gt;drengr_look&lt;/code&gt; one more time, identified the first relevant result, and tapped it.&lt;/p&gt;

&lt;p&gt;Total time: about 40 seconds. Total human intervention: zero. Claude navigated an app it had never been configured to use, adapting to whatever UI state it encountered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting It Up
&lt;/h2&gt;

&lt;p&gt;The MCP configuration is minimal. Here's what goes in your &lt;code&gt;claude_desktop_config.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"drengr"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drengr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"DRENGR_PLATFORM"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"android"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the entire integration. Drengr ships as a single binary — no Python virtualenv, no npm dependencies, no Docker container. You install it, point your MCP client at it, and Claude gains the ability to interact with whatever device is connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest Limitations
&lt;/h2&gt;

&lt;p&gt;I want to be transparent about where this breaks down, because it does break down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vision isn't perfect.&lt;/strong&gt; The UI hierarchy doesn't always capture everything visible on screen. Custom-drawn views, game canvases, and some Flutter widgets can appear as opaque rectangles. The agent can see the screenshot, but without structured element data, it's guessing at tap coordinates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Some gestures are hard to express.&lt;/strong&gt; A simple tap or swipe works reliably. But complex gestures — pinch to zoom, long-press-then-drag, multi-finger interactions — are difficult to represent in a tool call. I've implemented the common ones, but there's a long tail of interactions that don't map cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Latency adds up.&lt;/strong&gt; Each look-do cycle involves capturing a screenshot, extracting the UI tree, sending it to the AI, waiting for a decision, and executing the action. On a fast local setup, each cycle takes 3-5 seconds. Over a network to a cloud device, it can be 8-12 seconds. For a 20-step flow, that's minutes of wall time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Token costs are real.&lt;/strong&gt; Screenshots and UI trees are not small. A single &lt;code&gt;drengr_look&lt;/code&gt; response can be several thousand tokens. A complex navigation flow might consume 50,000-100,000 tokens. This isn't free, and it's something I think about when designing how much context to include in each response.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Changes
&lt;/h2&gt;

&lt;p&gt;The immediate application is testing — give Claude a goal, let it explore the app, report what it finds. But I think the more interesting implication is broader. MCP mobile support means AI agents can participate in workflows that were previously human-only. Filing bug reports with actual screenshots. Verifying that a deployment worked on a real device. Walking through a user flow to understand it before writing code.&lt;/p&gt;

&lt;p&gt;The gap between "AI that understands code" and "AI that understands the product" has always been the device. Drengr is my attempt to close that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;I'm working on a dashboard for visualizing test runs, real-time network monitoring so the agent can correlate UI actions with API calls, and a steering system that lets you redirect the agent mid-run. The core — three tools, one binary, MCP-native — won't change. Everything else is about making that core more useful.&lt;/p&gt;

&lt;p&gt;If you want to try it: &lt;code&gt;curl -fsSL https://drengr.dev/install.sh | bash&lt;/code&gt;. It takes about 10 seconds. I'd genuinely appreciate feedback on what works, what doesn't, and what you'd want it to do that it can't yet.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Drengr is free to use and available on &lt;a href="https://www.npmjs.com/package/drengr" rel="noopener noreferrer"&gt;npm&lt;/a&gt;. It supports Android (physical devices, emulators), iOS simulators (full gesture support), and cloud device farms (BrowserStack, SauceLabs, AWS Device Farm, LambdaTest, Perfecto, Kobiton). Built in Rust. Single binary. No runtime dependencies.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>appium</category>
      <category>mobiledev</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Field Notes: How Drengr's Architecture Aligns with (and Diverges from) Current Research</title>
      <dc:creator>Sharmin Sirajudeen</dc:creator>
      <pubDate>Mon, 06 Apr 2026 08:06:18 +0000</pubDate>
      <link>https://dev.to/sharminsirajudeen/field-notes-how-drengrs-architecture-aligns-with-and-diverges-from-current-research-1ifh</link>
      <guid>https://dev.to/sharminsirajudeen/field-notes-how-drengrs-architecture-aligns-with-and-diverges-from-current-research-1ifh</guid>
      <description>&lt;h1&gt;
  
  
  Field Notes: How Drengr's Architecture Aligns with (and Diverges from) Current Research
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;I'm the creator of &lt;a href="https://drengr.dev" rel="noopener noreferrer"&gt;Drengr&lt;/a&gt;, an MCP server that gives AI agents eyes and hands on mobile devices. I started this blog to share the engineering behind it. No pretending to be a neutral observer writing a think piece — I built this, and I'm here to talk about it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've been an Android engineer for about ten years. I still remember the first time I discovered Espresso. I was genuinely thrilled — here was a framework from Google, deeply integrated with the Android SDK, that could simulate real user behavior and verify UI state. I dove in headfirst. Wrote hundreds of tests. Felt like I was doing engineering the right way.&lt;/p&gt;

&lt;p&gt;Then reality set in. Tests that passed locally failed on CI because of animation timing. Tests that worked on a Pixel broke on a Samsung because of slightly different view hierarchies. A designer moved a button into a BottomSheet and forty tests turned red overnight — none of them testing anything related to that button. I spent more time maintaining the test suite than it saved me in bug prevention. And this was Google's own tool, built by one of the most capable engineering organizations on the planet. If Espresso was the best we had, the problem wasn't implementation — it was the entire approach.&lt;/p&gt;

&lt;p&gt;I moved through Appium, UIAutomator, tried Maestro when it came out. Each one was a refinement of the same fundamental idea: match elements by ID or XPath, perform actions, assert state. And each one broke the same way — the moment the UI evolved, the tests fossilized. I've sat in sprint retrospectives where someone says "the UI tests are red again" and everyone nods like it's weather. And the part that quietly frustrated me most: the decision-makers above me — experienced, respected leaders who'd built careers on shipping great mobile products — had accepted these tools as the ceiling. Not out of laziness, but out of familiarity. When every conference talk, every "best practices" blog post, and every Google I/O session tells you Espresso is the answer, questioning it feels like questioning gravity. So the test suites stayed brittle, the teams stayed frustrated, and the leadership stayed confident they were using the best tools available. After a while, you start to wonder whether brittle UI tests are almost as good as not having tests at all.&lt;/p&gt;

&lt;p&gt;That frustration is where Drengr started. Not from a paper. Not from a hackathon. From years of watching test suites rot faster than we could maintain them, and a quiet conviction that AI could do something fundamentally better — tests that understand what they're looking at instead of matching on fragile element IDs.&lt;/p&gt;

&lt;p&gt;I started prototyping in late 2024. A simple idea: what if an AI agent could look at a screen, understand what it sees, and interact with the app the way a human would? No hardcoded selectors. No XPath expressions that shatter on the next release. Just "navigate to the settings page and verify the toggle works." If the UI changes, the agent adapts. Self-evolving tests.&lt;/p&gt;

&lt;p&gt;Drengr is still early. I'm still figuring things out, still iterating, still learning what works and what doesn't. But recently I took some time to look at what the academic research community has been publishing — and I was surprised to find that researchers at Google, Meta, Microsoft, Tencent, and Princeton have been circling the same problems from different angles. Some of their solutions look like mine. Some are fundamentally different. A few of their insights are already changing how I think about what I'm building.&lt;/p&gt;

&lt;p&gt;This post is my attempt to map the territory honestly — where Drengr's early architecture converges with published research, where it diverges, and what I've learned from reading the papers after building the first version of the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Observe-Act Loop: Independent Convergence
&lt;/h2&gt;

&lt;p&gt;Drengr's core architecture is three MCP tools: &lt;code&gt;drengr_look&lt;/code&gt; (observe the screen), &lt;code&gt;drengr_do&lt;/code&gt; (execute an action), and &lt;code&gt;drengr_query&lt;/code&gt; (read structured data). An AI agent calls these in a loop — look at the screen, decide what to do, do it, look again.&lt;/p&gt;

&lt;p&gt;In late 2023, Zhang et al. at Tencent published &lt;strong&gt;AppAgent: Multimodal Agents as Smartphone Users&lt;/strong&gt; (arXiv:2312.13771). Their system does the same thing — observe the screen, decide, act — but as a Python agent framework. What struck me was their screen annotation approach: they number interactive elements on the screenshot so the LLM can reference them by ID. I'd independently arrived at the same design for Drengr's element numbering system. When two teams solve the same problem the same way without talking to each other, it usually means the solution is natural to the problem space.&lt;/p&gt;

&lt;p&gt;A month later, Wang et al. published &lt;strong&gt;Mobile-Agent&lt;/strong&gt; (arXiv:2401.16158), taking a purely vision-centric approach — no XML dumps, no accessibility tree, just screenshots plus detection and OCR models. Their finding that you &lt;em&gt;don't need&lt;/em&gt; system metadata to navigate apps effectively was an important validation. Drengr deliberately uses both screenshots and the accessibility tree — the tree is faster to parse, costs almost nothing in tokens, and gives precise element bounds that vision models still struggle with. But Mobile-Agent's results are a useful signal: as vision models improve, the tree may become optional, and Drengr's architecture is designed to make that transition seamless when the time is right.&lt;/p&gt;

&lt;p&gt;The key difference between Drengr and these systems: they're agent frameworks. Drengr is infrastructure. AppAgent and Mobile-Agent are Python applications that contain both the perception logic and the decision-making. Drengr separates these entirely — it handles perception and action, and delegates all decision-making to whatever LLM is on the other end of the MCP connection. This is a fundamentally different deployment model, and it's what lets Drengr work with Claude Desktop, Cursor, Windsurf, or any other MCP client without modification.&lt;/p&gt;

&lt;h2&gt;
  
  
  The OODA Loop: Military Theory Meets AI Agents
&lt;/h2&gt;

&lt;p&gt;When I implemented &lt;code&gt;drengr run&lt;/code&gt; — the autonomous agent mode — I structured it as an OODA loop: Observe (capture screen), Orient (situation engine analyzes what changed), Decide (LLM picks an action), Act (execute it). I chose OODA because it maps cleanly to the problem. The alternatives — simple while loops, state machines, behavior trees — all felt either too rigid or too unstructured.&lt;/p&gt;

&lt;p&gt;I was genuinely surprised to find that Schneier and Raghavan published &lt;strong&gt;Agentic AI's OODA Loop Problem&lt;/strong&gt; in IEEE Security &amp;amp; Privacy in 2025, analyzing exactly this pattern from a security perspective. Their key insight is that every stage of the OODA loop is a distinct attack surface. Prompt injection corrupts the Observe phase. Data poisoning corrupts Orient. Probabilistic decision-making without output verification corrupts Act. They specifically mention MCP and tool-calling systems as creating compounded vulnerabilities.&lt;/p&gt;

&lt;p&gt;Reading this paper directly influenced Drengr's security model. The &lt;code&gt;drengr_look&lt;/code&gt; observation phase cross-references the visual screenshot against the accessibility tree — if the two disagree (an element is visible but not in the tree, or vice versa), that inconsistency is surfaced in the situation report. It's not full tamper-evidence yet, but the dual-source design gives Drengr a foundation that purely vision-based systems don't have. Schneier and Raghavan's framing helped me see that as a security property, not just an implementation detail.&lt;/p&gt;

&lt;p&gt;More recently, Yasuno published &lt;strong&gt;RAPTOR-AI for Disaster OODA Loop&lt;/strong&gt; (arXiv:2602.00030) in early 2026, applying the OODA pattern to disaster response with entropy-aware strategy selection. The concept of adjusting confidence thresholds based on situational entropy maps directly to what Drengr's situation engine does — detecting when the screen hasn't changed (stuck detection), when the app has crashed, or when the agent is in an unfamiliar state.&lt;/p&gt;

&lt;h2&gt;
  
  
  BFS App Exploration: An Old Idea, Reimagined
&lt;/h2&gt;

&lt;p&gt;Drengr's &lt;code&gt;drengr explore&lt;/code&gt; mode does BFS traversal of an app — systematically tapping every interactive element, recording the resulting screens, and building a navigation graph. I built this because I needed a way to map unfamiliar apps before writing test suites for them.&lt;/p&gt;

&lt;p&gt;The academic lineage here goes back to &lt;strong&gt;DroidBot&lt;/strong&gt; by Li et al. (IEEE/ACM ICSE-C 2017), which built state transition models from live UI interactions. DroidBot used hard-coded heuristics to decide what to tap next. Drengr replaces those heuristics with an LLM decision layer — the agent can reason about whether a button is likely to navigate somewhere useful or just dismiss a dialog.&lt;/p&gt;

&lt;p&gt;Wen et al. at Microsoft Research took this further with &lt;strong&gt;AutoDroid&lt;/strong&gt; (ACM MobiCom 2024), combining LLM-driven exploration with a reusable knowledge graph. Their publication at MobiCom — a top-tier systems conference — establishes this as a recognized systems contribution, not just an ML exercise. Drengr's approach is architecturally simpler — a single Rust binary versus a Python/LLM stack — but the core insight is the same: BFS exploration is dramatically more effective when guided by a language model than by heuristics.&lt;/p&gt;

&lt;h2&gt;
  
  
  ReAct and Tool Use: The Conceptual Foundation
&lt;/h2&gt;

&lt;p&gt;Two papers form the conceptual bedrock of what Drengr enables, even though I hadn't read either when I started building.&lt;/p&gt;

&lt;p&gt;Yao et al. at Princeton published &lt;strong&gt;ReAct: Synergizing Reasoning and Acting in Language Models&lt;/strong&gt; (ICLR 2023, arXiv:2210.03629). ReAct interleaves chain-of-thought reasoning with executable actions — the model reasons about what to do, issues an action, observes the result, reasons again. Every time Claude calls &lt;code&gt;drengr_look&lt;/code&gt;, reasons about what to tap, then calls &lt;code&gt;drengr_do&lt;/code&gt;, it's executing a ReAct loop. Drengr is, architecturally, a ReAct-compatible tool suite for mobile devices.&lt;/p&gt;

&lt;p&gt;Schick et al. at Meta published &lt;strong&gt;Toolformer&lt;/strong&gt; (NeurIPS 2023, arXiv:2302.04761), demonstrating that LLMs can learn when and how to call external tools. Toolformer's tools were information retrieval APIs — calculators, search engines, QA systems. Drengr's tools have physical side effects. When &lt;code&gt;drengr_do&lt;/code&gt; taps a button, a real device changes state. That distinction matters — the consequences of a wrong action are much more significant than a wrong search query.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen Understanding: Where the Field Is Heading
&lt;/h2&gt;

&lt;p&gt;Two papers from Google Research point to where Drengr's perception layer might evolve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ScreenAI&lt;/strong&gt; (Baechler et al., IJCAI 2024, arXiv:2402.04615) is a 4.6B-parameter vision-language model fine-tuned specifically for UI understanding. It identifies UI elements — buttons, text fields, images — at the pixel level from raw screenshots. Currently, Drengr uses the Android accessibility tree alongside screenshots for element identification. ScreenAI suggests that the screenshot alone might eventually be sufficient, which would make Drengr's perception layer identical across Android, iOS, and any other platform with a display.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spotlight&lt;/strong&gt; (Li and Li, arXiv:2209.14927, 2023) goes even further — a vision-only model for mobile UI understanding that &lt;em&gt;outperforms&lt;/em&gt; methods using both screenshots and view hierarchies. This directly challenges Drengr's current design of using the accessibility tree as a primary data source. If vision-only models can outperform metadata-enhanced models, then Drengr's &lt;code&gt;drengr_query&lt;/code&gt; tool (which reads the UI tree) might eventually become redundant — replaced by richer visual understanding from the LLM itself.&lt;/p&gt;

&lt;p&gt;For now, the accessibility tree remains the right default — it's reliable, fast, and doesn't require a specialized vision model. But Drengr's perception layer is designed as a swappable trait, so when vision-only models reach the point where they consistently outperform metadata-enhanced approaches across device types and screen densities, the switch is an implementation change, not an architectural one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Survey: Situating Drengr in the Field
&lt;/h2&gt;

&lt;p&gt;Wang et al. published &lt;strong&gt;GUI Agents with Foundation Models: A Comprehensive Survey&lt;/strong&gt; (arXiv:2411.04890, 2024) — a systematic review of 100+ papers on LLM-based GUI agents across web, desktop, and mobile. Reading this survey was like looking at a map after you've already hiked the trail. I recognized the landmarks.&lt;/p&gt;

&lt;p&gt;Drengr's three-tool architecture fits cleanly into the survey's taxonomy of perception-grounding-action pipelines. What the survey made clear is that most systems in this space are tightly coupled — the perception, grounding, and action components are part of the same codebase, usually Python. Drengr's contribution is decoupling these: it handles perception and action, and lets any MCP-compatible LLM handle grounding and reasoning. This is a systems architecture choice, not an ML innovation — but it's one that the survey suggests is underexplored.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Drengr Diverges
&lt;/h2&gt;

&lt;p&gt;After reading all of this, here's what I think Drengr is doing differently — or at least trying to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Infrastructure, not framework.&lt;/strong&gt; Almost every paper describes an end-to-end agent. Drengr is deliberately not an agent — it's the hands and eyes that agents use. This separation came from ten years of watching tightly-coupled testing tools become unmaintainable. It's a pattern I saw repeated across every mobile organization I worked in — teams with brilliant leadership, seasoned VPs, directors who'd shipped apps to hundreds of millions of users — and yet the testing infrastructure always calcified the same way. The tooling forced coupling, but I also think there was a deeper issue — the mental model at the top often stopped at "we need more test coverage" without questioning whether the testing paradigm itself was the bottleneck. When you've shipped successful apps for years using a certain approach, it takes a particular kind of intellectual honesty to ask whether that approach has a ceiling. Most organizations optimized within the paradigm rather than questioning it. When your test framework is also your test runner is also your assertion library is also your device manager, everything breaks together. I think the industry internalized that pain as normal. Drengr's hypothesis is that it doesn't have to be. Separate the perception and action layer from the intelligence layer, and each can evolve independently. The agent will change. The tools should remain.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rust, not Python.&lt;/strong&gt; Every system cited above is Python. Drengr is a single static Rust binary. As an Android engineer, I know what it's like to ask a team to install a tool with twelve dependencies. I wanted &lt;code&gt;curl | bash&lt;/code&gt; and done. That choice has trade-offs — I wrote about them in &lt;a href="https://dev.to/blog/why-not-python"&gt;a separate post&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MCP first — by design.&lt;/strong&gt; People ask why I released Drengr as an MCP server before building a standalone CLI agent. The answer comes from watching this industry long enough to know what survives and what doesn't. AI models improve every few months. The agent that's state-of-the-art today will be obsolete by next year. But the ability to observe a screen, tap a button, and read a UI tree? That's stable. That's the invariant. By releasing the tool layer first — as an MCP server that any AI client can consume — I'm building on the part that lasts. Claude Desktop uses it today. Cursor uses it today. Whatever comes next year will use it too, because the interface is standardized. If I'd built a monolithic agent instead, I'd be rewriting it every time a better model dropped. The Model Context Protocol didn't exist when most of these papers were written. Drengr's bet is that a standard protocol between AI agents and tools is more valuable than another custom agent framework. I might be wrong. But ten years of watching tightly-coupled tools age badly makes me think this bet is right.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Born from the field, not the lab.&lt;/strong&gt; This isn't a research project with a team, compute budget, and publication timeline. It's one Android engineer who got tired of writing tests that broke every sprint and decided to try a different approach. The architecture reflects that — pragmatic, incremental, shaped by what I actually needed rather than what's theoretically optimal.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Reading these papers after building the first version taught me something I didn't expect: the problems I was solving alone — in my apartment, after work, on weekends — are the same problems that well-funded research teams at Google and Microsoft are working on. That's both humbling and encouraging.&lt;/p&gt;

&lt;p&gt;The convergence gives me confidence that I'm not building something crazy. The divergence — particularly Drengr's choice to be infrastructure rather than an agent, and to use the accessibility tree alongside vision rather than vision alone — reflects deliberate trade-offs, not gaps. Where the academic work explores what's theoretically optimal, Drengr is built around what's practically reliable today while keeping the architecture open to what's coming.&lt;/p&gt;

&lt;p&gt;I'm not an academic. I don't have a lab or a publication record. I'm an Android engineer with a decade of scar tissue from brittle test suites, building a tool shaped by what I actually needed in the field. The researchers cited here are formalizing the theory behind problems I've been solving through iteration and observation. We're approaching the same territory from different directions — and I think both directions produce insights the other can't.&lt;/p&gt;

&lt;p&gt;If you're working in this area — whether you're writing papers or building tools or just frustrated with your own test suite — I'd love to hear from you. This space is wide open, and I think we're all just getting started.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Zhang et al. "AppAgent: Multimodal Agents as Smartphone Users." arXiv:2312.13771, 2023.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wang et al. "Mobile-Agent: Autonomous Multi-Modal Mobile Device Agent with Visual Perception." arXiv:2401.16158, 2024.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wen et al. "AutoDroid: LLM-powered Task Automation in Android." ACM MobiCom, 2024.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schneier &amp;amp; Raghavan. "Agentic AI's OODA Loop Problem." IEEE Security &amp;amp; Privacy, 2025.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Yasuno. "RAPTOR-AI for Disaster OODA Loop." arXiv:2602.00030, 2026.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Li et al. "DroidBot: A Lightweight UI-Guided Test Input Generator for Android." IEEE/ACM ICSE-C, 2017.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wang et al. "GUI Agents with Foundation Models: A Comprehensive Survey." arXiv:2411.04890, 2024.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schick et al. "Toolformer: Language Models Can Teach Themselves to Use Tools." NeurIPS, 2023.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Yao et al. "ReAct: Synergizing Reasoning and Acting in Language Models." ICLR, 2023.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Baechler et al. "ScreenAI: A Vision-Language Model for UI and Infographics Understanding." IJCAI, 2024.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Li &amp;amp; Li. "Spotlight: Mobile UI Understanding using Vision-Language Models with a Focus." arXiv:2209.14927, 2023.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Drengr is free to use and available on &lt;a href="https://www.npmjs.com/package/drengr" rel="noopener noreferrer"&gt;npm&lt;/a&gt;. It supports Android (physical devices, emulators), iOS simulators (full gesture support), and cloud device farms (BrowserStack, SauceLabs, AWS Device Farm, LambdaTest, Perfecto, Kobiton). Built in Rust. Single binary. No runtime dependencies.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>appium</category>
      <category>mobiledev</category>
      <category>testing</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
