●PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16●VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to miss●EXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration plan●APPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spent●EXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInput●EAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates●PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16●VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to miss●EXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration plan●APPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spent●EXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInput●EAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
One Month with Rork Max AI Cloud: Latency, Cost, and the Hybrid Setup I Settled On
A month of running Rork Max AI Cloud beside local M-series execution: p50/p95 latency distributions, real monthly cost, the benchmark script I used, and a budget ledger that actually stops spending.
When Rork shipped AI Cloud, my first reaction was that I did not need it. Local execution on an M3 Ultra was fast enough, and paying a monthly fee for inference felt like solving a problem I did not have.
What changed my mind was a train ride. I kicked off a dependency analysis, handed my MacBook over to it for nearly forty seconds, and watched the Xcode window I had open turn sluggish. The issue was never fast versus slow. It was that while I waited, my machine stopped being mine.
So I spent a month running both paths in parallel and measuring. Latency distributions, actual billing, thermals, and how well work survives a bad network. From every angle I landed on the same quiet conclusion: committing fully to either side costs you something. What follows is the measurement and the judgment I was left holding.
AI Cloud Changes Where Generation Happens, Not Where Apps Run
Let me clear up the most common misunderstanding. Rork Max AI Cloud is not a hosting service for the apps you build. It is a way to run the inference pipeline that produces Rork Max code and UI on cloud hardware instead of your local M-series Mac.
In my use, AI Cloud replaces three things:
The inference that breaks a prompt into structured tasks
The inference that generates SwiftUI / Jetpack Compose snippets
The inference that ingests existing code and proposes refactors
Running these locally, even on an M3 Ultra, keeps the CPU/GPU/Neural Engine busy enough that fan noise becomes part of your workflow. On the M2 MacBook Air I take when I travel, longer tasks kept the fans spinning the whole time. AI Cloud is, fundamentally, a way to push that physical load somewhere else.
Rather than quote official numbers, I will share what I measured myself over a month of maintaining wallpaper apps and prototyping new ones. Each task ran 30 times on M3 Ultra (128 GB) and on the AI Cloud Pro tier.
Task
Local M3 Ultra
AI Cloud
Speedup
Small SwiftUI view generation
4.2 s
1.1 s
3.8x
Refactor proposal on existing code
12.7 s
3.9 s
3.3x
Cross-file dependency analysis
38.5 s
8.2 s
4.7x
What matters here is not the speedup number. It is that single short tasks feel fine on local execution. A 4.2 second SwiftUI view generation is not slow in practice. The gap becomes decisive on the heavy end, where local inference owns the laptop for almost 40 seconds at a stretch.
The variable that pushed me toward AI Cloud was not raw speed. It was the question of whether my MacBook is mine while a task runs. If I want to keep Xcode open, edit an icon in Photoshop, or even read documentation comfortably, local inference quietly takes that away from me. AI Cloud restores it.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Latency broken down to p50/p95 for local and cloud execution, and why the averages hide the behaviour that matters
✦A working benchmark script plus a usage ledger that enforces a monthly budget instead of merely declaring one
✦A break-even formula from one month of real billing, and hybrid rules that keep offline development alive
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Averages Will Mislead You: The p50 / p95 Distribution
The table above reports means. Stopping there gives you "the cloud is faster" and nothing else. When I laid the thirty raw samples back out per run, something the averages had been hiding showed up.
Task
Where it ran
p50
p95
Max
Small SwiftUI view generation
Local
4.1 s
4.9 s
5.2 s
AI Cloud
0.9 s
3.6 s
7.4 s
Refactor proposal
Local
12.4 s
14.1 s
15.0 s
AI Cloud
3.4 s
9.8 s
12.6 s
Dependency analysis
Local
38.0 s
41.2 s
43.5 s
AI Cloud
7.6 s
18.9 s
24.1 s
Two things fall out of this.
First, local execution is slower but almost perfectly predictable. The spread between p50 and p95 stays inside one to three seconds on every task. If you know a step takes four seconds, you can plan around four seconds.
Second, AI Cloud's p95 runs roughly three times its p50. Sorting the samples chronologically, the slow runs were nearly always the first call after a gap. Come back from a few minutes away and even a small SwiftUI generation lands in the three-to-seven second range. From the second call onward it snaps back to about a second.
Not knowing this makes the experience feel oddly inconsistent. While you are heads-down firing off prompts it is startlingly quick, and then the first request after you make coffee stalls. Something that is intermittently slow disrupts a working rhythm more than something that is reliably slow.
So I added a third axis to my routing rules alongside task weight: elapsed idle time. The first request after stepping away goes local, even when it is a small task. I would not have thought to do that without looking at the distribution.
The Benchmark Script Behind Those Numbers
I collected the distribution above by running the script below once a day. It fires the Rork Max CLI with identical arguments in either mode and records nothing but elapsed time. There is no cleverness to it; the parts that matter are that it reports percentiles rather than a mean, and that it can deliberately idle between runs.
// bench-ai-cloud.mjs// Usage: node bench-ai-cloud.mjs prompts/refactor.txt cloud 30 0// args: prompt file / mode (local|cloud) / iterations / idle seconds before each runimport { execFile } from "node:child_process";import { readFileSync, appendFileSync } from "node:fs";import { promisify } from "node:util";const run = promisify(execFile);const [promptPath, mode, countArg, idleArg] = process.argv.slice(2);const count = Number(countArg ?? 30);const idleSec = Number(idleArg ?? 0);const prompt = readFileSync(promptPath, "utf8");const sleep = (ms) => new Promise((r) => setTimeout(r, ms));function percentile(sorted, p) { if (sorted.length === 0) return NaN; const rank = (p / 100) * (sorted.length - 1); const low = Math.floor(rank); const high = Math.ceil(rank); if (low === high) return sorted[low]; return sorted[low] + (sorted[high] - sorted[low]) * (rank - low);}const samples = [];for (let i = 0; i < count; i++) { // Set idleSec to something like 300 to reproduce the "first call after a gap" case if (idleSec > 0 && i > 0) await sleep(idleSec * 1000); const startedAt = process.hrtime.bigint(); try { await run("rork", ["max", "generate", "--mode", mode, "--stdin"], { input: prompt, timeout: 120_000, maxBuffer: 32 * 1024 * 1024, }); } catch (err) { // Failures are part of the distribution: log them, do not discard them silently appendFileSync("bench-errors.log", `${mode}\t${i}\t${err.code ?? err.message}\n`); continue; } const elapsedSec = Number(process.hrtime.bigint() - startedAt) / 1e9; samples.push(elapsedSec); appendFileSync( "bench-samples.tsv", `${new Date().toISOString()}\t${promptPath}\t${mode}\t${i}\t${elapsedSec.toFixed(3)}\n` ); process.stdout.write(`${i + 1}/${count} ${elapsedSec.toFixed(2)}s\n`);}const sorted = [...samples].sort((a, b) => a - b);const mean = samples.reduce((a, b) => a + b, 0) / samples.length;console.log(`\nmode=${mode} n=${samples.length} (errors=${count - samples.length})`);console.log(`mean=${mean.toFixed(2)}s`);console.log(`p50=${percentile(sorted, 50).toFixed(2)}s`);console.log(`p95=${percentile(sorted, 95).toFixed(2)}s`);console.log(`max=${sorted[sorted.length - 1].toFixed(2)}s`);
Skipping failed runs with continue while still logging them is deliberate. Collect only the successes and your distribution comes out prettier than reality. Network wobbles show up on the cloud side reliably, and how often they happen is itself part of the decision.
Set idleSec to 300 and run ten iterations, and the "first call after a gap is slow" behaviour reproduces cleanly. A benchmark taken by firing requests back to back describes a workflow nobody actually has. This one was worth verifying by hand.
Real Monthly Cost for an Indie Developer
This is the part fellow indie developers care about most. In my testing, AI Cloud is billed per inference call. Each plan ships with a different free tier and overage rate, so the practical move is to fix a monthly budget first and pick a plan that comfortably contains it. The plan-by-plan differences themselves are covered in Rork Pricing Compared: Free vs Pro vs Max.
Over one month, my actual call distribution was roughly:
Short generations (1-2 second class): about 1,800 calls
Medium generations (3-10 second class): about 420 calls
Heavy dependency analysis (10+ seconds): about 65 calls
Pushing all of this to AI Cloud landed me at roughly JPY 4,800 per month on the Pro tier, because most of the short and medium calls stayed inside the included quota and only the heavy tasks went over. At roughly JPY 160 a day to stop my MacBook heating up and stealing my focus, that is an easy yes.
There were also weeks when I crossed JPY 10,000 because I left Rork Max open and "tinkered" all day. Inference counts grow faster than you expect. I now treat AI Cloud as a budgeted line item with a hard cap, not as a "use whenever" tool.
Reducing Break-Even to a Single Formula
Deciding by feel is how you get surprised at the end of the month. Here is the line I drew instead.
hours saved per month = (local p50 - cloud p50) x monthly runs / 3600
break-even hourly rate = monthly cloud spend / hours saved per month
Plugging in my own numbers for heavy tasks only: dependency analysis at 38.0 s local versus 7.6 s cloud, 65 runs a month.
Read literally, I am buying my own time back at JPY 8,730 an hour, which looks like a bad trade. And it is: you cannot justify AI Cloud on saved waiting time alone.
My reason for paying sits elsewhere. When a local run holds the machine for 38 seconds, the cost does not end when the run does. It takes another few minutes for my attention to come back. Price the recovery from a single interruption conservatively at three minutes and the effective monthly reclaim becomes about 3.8 hours, which drops the break-even rate to JPY 1,260 an hour.
The deciding factor, then, is not the wait itself. It is whether you switch to something else during the wait. If you can sit through forty seconds calmly, AI Cloud is expensive for you. If you reach for Slack instead, it is cheap. I am firmly the second kind.
Hybrid Rules: What I Keep Local and What I Push to the Cloud
After a month, my current routing rules look like this.
Tasks I keep local
Small edits scoped to one or two files (done in seconds anyway)
The first call after several idle minutes, to sidestep the cold-start delay
Anything on a plane, train, or unstable Wi-Fi
Private experimental code I prefer not to send anywhere
Sensitive business logic such as revenue calculations
Tasks I push to AI Cloud
Cross-project dependency analysis and large refactors
Multi-platform generation that emits SwiftUI and Jetpack Compose together
Heavier AI features in prototyping, like voice to structured text
Long, continuous generations (30+ minute sessions for tutorial-style work)
The reason this shape emerged is simple. I matched the threshold where local heat becomes intrusive against the time windows where I am confident the network is healthy. Projects that resemble production go to the cloud; research and personal sketches stay local.
One caveat worth stating plainly: moving work to the cloud does not make Rork Max's native generation any stronger. Where that generation still falls short is a separate topic, covered in Where Rork Max Still Falls Short — A Realistic Line Around Native Generation. Keeping the speed problem and the accuracy problem apart will save you from misplacing your expectations.
Keeping Offline Development Alive
This is the trap I want to flag clearly. Once you get comfortable with AI Cloud, you can drift into a setup where Rork Max effectively stops working the moment the network drops. I learned this on a bullet train, watching every task block.
The small configuration I keep at the root of my Rork Max projects looks like this.
// rork-max.config.tsexport default { ai: { // Default to hybrid, but allow an explicit override via env mode: process.env.RORK_AI_MODE ?? "hybrid", // If a local task exceeds this, push it to the cloud localTimeoutSec: 8, // If this many seconds have passed since the last cloud call, answer locally coldStartGuardSec: 180, // Fall back to local automatically when offline is detected fallbackToLocal: true, // Soft monthly budget guard monthlyBudgetJPY: 8000, }, routes: { // Heavy work always goes to the cloud forceCloud: [ "dependency-analysis", "multi-file-refactor", "multi-platform-generation", ], // Sensitive code stays local forceLocal: [ "wallpaper-revenue-calc", "private-experiments/*", ], },};
coldStartGuardSec is the field I added after looking at the p95 numbers. Sending small tasks local right after an idle gap was enough to make the inconsistency disappear from day-to-day use.
Making the Budget Guard Actually Bite: A Usage Ledger
On its own, the monthlyBudgetJPY above stops nothing. Writing a number into a config file and feeling protected by it is exactly what I did in month one, right up until the invoice arrived.
So I put a ledger in front of the call. Before dispatching, read the running total for the month; if it is over the cap, do not go to the cloud, go local. That is the whole mechanism, and the difference it makes to how the end of the month feels is out of proportion to its size.
// ai-budget-ledger.mjsimport { appendFileSync, existsSync, readFileSync, mkdirSync } from "node:fs";import { dirname } from "node:path";const LEDGER = ".rork/ai-usage.jsonl";// Estimated unit cost per task type, in JPY. Reconcile against the invoice monthly.const UNIT_COST_JPY = { "view-generation": 0.6, "refactor-proposal": 2.4, "dependency-analysis": 11.0,};function currentMonthKey(now = new Date()) { return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, "0")}`;}export function monthlySpentJPY(monthKey = currentMonthKey()) { if (!existsSync(LEDGER)) return 0; return readFileSync(LEDGER, "utf8") .split("\n") .filter(Boolean) .reduce((sum, line) => { let row; try { row = JSON.parse(line); } catch { return sum; // A corrupt line should not halt accounting } if (row.month !== monthKey) return sum; return sum + (Number(row.costJPY) || 0); }, 0);}export function record(taskType, { mode }) { if (mode !== "cloud") return 0; // Local runs are not billed, so do not log them const costJPY = UNIT_COST_JPY[taskType] ?? 0; mkdirSync(dirname(LEDGER), { recursive: true }); appendFileSync( LEDGER, JSON.stringify({ at: new Date().toISOString(), month: currentMonthKey(), taskType, costJPY, }) + "\n" ); return costJPY;}// Call this immediately before dispatch: over budget, it downgrades to "local"export function resolveMode(taskType, desiredMode, budgetJPY) { if (desiredMode !== "cloud") return desiredMode; const spent = monthlySpentJPY(); const next = spent + (UNIT_COST_JPY[taskType] ?? 0); if (next > budgetJPY) { console.warn( `[budget] ${spent.toFixed(0)}/${budgetJPY} JPY reached. Running ${taskType} locally.` ); return "local"; } if (next > budgetJPY * 0.8) { console.warn(`[budget] JPY ${next.toFixed(0)} this month (over 80% of cap).`); } return "cloud";}
The behaviour that made this stick was degrading to local execution rather than throwing an error when the cap is reached. Fail the call and you will simply raise the cap on the spot. Make it merely slower and the budget survives.
UNIT_COST_JPY is back-solved from invoices. Once a month I put the ledger total next to the real charge and adjust the coefficients where they drifted. That single reconciliation pass sharpened my forecasting noticeably the following month. Keep a cap in code and a cap in the Rork dashboard — the double guard is worth the small effort.
Pitfalls I Hit in Production-Adjacent Use
Here are the actual potholes from a single month, so you can dodge them.
1. Going all cloud quietly destroys your offline resilience
Setting AI Cloud as the default everywhere is convenient until your Wi-Fi blinks. Keep an automatic local fallback in place from day one.
2. The first upload of a large project is heavier than you expect
Dependency analysis sends the full project to the cloud once. On slow networks this can take minutes, blocking other tasks. Do the first upload on a stable wired connection if you can.
3. Cloud and local outputs are close but not identical
The same prompt sometimes produced slightly more cautious output on the cloud side. Not better or worse, but if you want UI copy or naming conventions to stay consistent across a team, pin yourself to one side.
4. Mean latency will not match what you feel
As covered above, the first call after an idle gap runs slow, and a back-to-back benchmark buries that inside the average. If you are using numbers to make an adoption decision, include measurements with deliberate idle time.
5. A budget you only write down is not a budget
A number in a config file is a declaration, not a mechanism. Until I put the ledger in front of the call, my monthly spend was governed entirely by my own restraint. Restraint loses to the end of the month.
What a Month Left Me With
The value of AI Cloud is not the speed; it is getting my machine back. On paper the break-even is marginal, but once you account for the habit of escaping into another task while you wait, it turns clearly positive for me.
The interesting part was how measuring changed my usage. The path I assumed was fast turns out to stall on first contact, and the path I assumed was slow turns out to be admirably steady. Once the question stopped being "which is better" and became "which belongs where", the tool finally sat right in my hands.
If you are on the fence, I would describe it as not strictly necessary but genuinely quality-of-life changing. In indie development, quiet improvements like this are what keep you going months later.
What to Try Next
If you are going to give AI Cloud a real month, spend the first week deliberately running the same tasks on both local and the cloud in parallel. The absolute latency numbers matter less than learning your own interruption tolerance and the budget your business can carry.
Concretely, that is bench-ai-cloud.mjs from this article run thirty times with no idle and ten times with a 300 second idle, in both modes. The gap between the p50 and p95 you get back is the routing boundary for your own project, handed to you directly.
Thank you for reading this far. If you are walking the same path of indie development, I hope this helps.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.