Routing Between Local Gemma and Cloud Gemini 3.5 Flash by How Easily You Can Verify the Output
When I split local and cloud work by whether it was sensitive, or by which was faster, my decisions wobbled every time. The axis that finally held was different: if the output is wrong, can I catch it cheaply and undo it cheaply? Here is a router that chooses a model from verifiability and recovery cost, with working code and a measurement ledger.
For a while, I could not settle on how to split work between local Gemma and cloud Gemini 3.5 Flash.
At first I drew the line at sensitivity: confidential code stayed local, everything else went to the cloud. Then I switched to speed versus intelligence. Both sounded reasonable, yet in practice my decisions wobbled every time. The same file would land on a different side depending on what I happened to be doing that day.
One night, a code-formatting job I had left to a scheduled run produced a quietly broken diff and sat there unnoticed until morning. The output looked natural, plausible at a glance. That was when it finally clicked. The question I should have been asking was not "is this model smart enough" but "if this output is wrong, can I notice it cheaply and roll it back cheaply?"
This article lays out a design that routes models by verifiability and recovery cost rather than by raw intelligence or latency. I will walk through the decision rule, a working router, and the ledger that tells you afterward whether the routing was actually right, all in the shape I run as an indie developer on my own apps.
Gemini 3.5 Flash beats the previous 3.1 Pro on nearly every benchmark while running close to four times faster than other frontier models. On the numbers alone, you want to push everything to the cloud.
But in solo indie operation, raw model performance is rarely the bottleneck. The jam is somewhere else: the time a human spends confirming the output is correct, the effort of reverting when it is wrong, and the cleanup when that mistake has already rippled outward. None of these disappear no matter how capable the model is.
So the routing question needs reframing. Not "which model is better" but "can this task absorb a failure safely?" If it can, a lower-accuracy local Gemma is fine. If it cannot, the task belongs on the cloud side with heavier verification and approval, even when speed argues otherwise.
The hinge is verifiability. A task whose correctness can be checked automatically and cheaply lets you catch mistakes immediately, which means the loss from a weaker model stays bounded. A task that only a human can judge lets mistakes flow downstream.
Scoring a Task on Three Axes
I score each task on three axes. The point is that all three measure the nature of the task, not the nature of the model.
Axis
Question
Low (leans local)
High (leans cloud)
Verifiability
Can correctness be checked automatically?
Types, tests, linters decide it instantly
Only a human reading it can judge meaning
Reversibility
Can a mistake be undone cheaply?
Revert at the granularity of a commit
External sends, billing, irreversible writes
Blast radius
How far does a failure spread?
Single file, stays local
Production, multiple apps, users affected
A task that scores low on all three can absorb a failure silently. That is the territory to hand to local Gemma. Local keeps the round trips fast and spends none of your cloud usage cap.
A task that scores high on even one axis carries a larger penalty for failure. That belongs on the cloud, with a human approval added on top. Keeping the mistake from escaping matters more than being fast.
The important move is to decide this scoring once per task type and then freeze it. Deliberating in the moment is exactly what made my decisions wobble. Moving the judgment into the design is the whole idea.
✦
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
✦A decision table and Python router that scores each task on whether it can be auto-verified, cheaply reverted, and how far a failure spreads, then picks local or cloud
✦A logging harness that records realized correction rate, latency, and cost per route so you can retune thresholds from data rather than instinct
✦The monitoring points for catching a silently degrading local model and a verifier that has quietly gone stale
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.
Left to human intuition, the scoring does not reproduce. So I use a small router that derives the route from a task's attributes. Antigravity agents and scheduled runs pick their model through this one function.
from dataclasses import dataclassfrom enum import Enumclass Route(Enum): LOCAL = "local" # Gemma via Ollama CLOUD = "cloud" # Gemini 3.5 Flash CLOUD_WITH_REVIEW = "cloud_review" # cloud + human approval@dataclassclass Task: kind: str auto_verifiable: bool # decidable by types/tests/linters reversible: bool # revertible at commit granularity blast_radius: str # "local" | "repo" | "production"def route(task: Task) -> Route: # Anything reaching production: safety over speed. Always gate on approval. if task.blast_radius == "production": return Route.CLOUD_WITH_REVIEW # Irreversible work cannot absorb a mistake. Put a human in the loop. if not task.reversible: return Route.CLOUD_WITH_REVIEW # Auto-verifiable, revertible, narrow. A failure is absorbed safely. if task.auto_verifiable and task.blast_radius == "local": return Route.LOCAL # Otherwise cloud, sized to how heavy the verification is. return Route.CLOUD# Exampletasks = [ Task("format_diff", auto_verifiable=True, reversible=True, blast_radius="local"), Task("release_note_translation", auto_verifiable=False, reversible=True, blast_radius="repo"), Task("admob_config_change", auto_verifiable=False, reversible=False, blast_radius="production"),]for t in tasks: print(f"{t.kind:28} -> {route(t).value}")
The output:
format_diff -> local
release_note_translation -> cloud
admob_config_change -> cloud_review
Formatting goes local, translation where meaning matters goes cloud, and a revenue-touching config change goes to cloud-with-approval. A decision I used to agonize over is now fixed by the task's attributes alone.
The branching is written flatly on purpose. This is a place I reread and tune by hand while operating. Rather than making it clever, I keep it readable top to bottom. When an agent edits it, an intent that shows up plainly in the diff is safer.
The ordering matters too. Blast radius and reversibility come first because they express irrecoverability. Verifiability shrinks the loss, but in front of an operation you cannot take back, how easy it is to verify becomes secondary. Reject the dangerous conditions first.
Confirming the Routing Was Right, From Measurements
The router is only a hypothesis. A task you judged "local is enough" can turn out to be full of corrections. So I always attach a ledger that measures the outcome afterward.
I record the route, the elapsed time, and most importantly the correction rate: the fraction of that route's output that a human or verifier later had to fix.
import jsonimport timefrom pathlib import PathLEDGER = Path.home() / ".antigravity" / "route_ledger.jsonl"LEDGER.parent.mkdir(parents=True, exist_ok=True)def run_with_ledger(task: Task, execute): chosen = route(task) started = time.monotonic() result = execute(chosen) # the actual model call elapsed = time.monotonic() - started record = { "kind": task.kind, "route": chosen.value, "elapsed_s": round(elapsed, 2), "verified": result.get("verified"), # passed the verifier "corrected": result.get("corrected"), # fixed afterward "ts": time.time(), } with LEDGER.open("a") as f: f.write(json.dumps(record, ensure_ascii=False) + "\n") return result
After running it for a week, the tendencies per route become visible. Here is one snapshot from my own setup. Treat it as a rough guide; it shifts a lot with model version and task nature.
Route
Avg elapsed
Correction rate
Reading
local (formatting, minor fixes)
~2-4 s
~6%
Within reach of auto-verification. Reasonable
cloud (translation, summaries)
~3-6 s
~14%
Meaning corrections remain. Consider approval
cloud_review (config changes)
minutes with approval
~2%
Human approval is doing its job
These numbers drive threshold retuning. If the local correction rate creeps up over time, that is a signal the "absorbs failure safely" premise is starting to break. Either the auto-verification is leaking, or the model is quietly degrading. If a cloud task type shows a low enough correction rate, you can drop the approval and move it to the faster route.
Move thresholds from this ledger's trend, not from instinct. That is what keeps the routing a living part of operation rather than a one-time design.
Where It Quietly Breaks in Operation
This design fails quietly in a few places. Decide in advance where to watch.
First, the verifier going stale. You leaned local on the grounds that the task was "auto-verifiable," but if that verifier stays lax, mistakes slip through. That is exactly why I did not notice the broken diff until morning. Treat the verifier as seriously as the artifact. Revisit the mesh of tests and linters every time you add a route.
Second, the local model degrading silently. A change to quantization settings, a growing context length, a bloating prompt. Each shaves accuracy little by little, yet none surfaces as an error. Only a gradual rise in correction rate reveals it. A weekly glance at the ledger is your only early warning.
Third, misdeclared attributes. If you let the agent do the scoring too, it will sometimes optimistically declare "this is reversible." Reversibility and blast radius should be decided mechanically from observable facts, the repository state and the deploy target, not from the model's self-report. Not delegating the judgment to opinion is what holds this routing's reliability together.
A Step for Tomorrow
Start by writing down about five tasks you routinely hand to an agent and scoring them on the three axes above. Roughly half will likely land in "auto-verifiable, revertible, narrow." Moving just those to local Gemma noticeably eases both your cloud usage cap and the round-trip wait. By contrast, keep changes that ripple outward when they break — an AdMob revenue setting, an App Store submission flow — on cloud-with-approval without hesitation.
And always attach the ledger to what you moved. A single number, the correction rate, will tell you whether your routing was right far more honestly than instinct.
I went through a few quiet failures of my own before arriving at this design. If it spares you even a little of the same detour, I would be glad. Thank you for reading.
Share
Thank You for Reading
Antigravity 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.