Most automation products do not fail because their model is weak.

They fail because orchestration is too expensive to run at the pace users expect.

If you are building a browser-native automation engine on Supabase and Vercel free tier, the default architecture usually looks rational on paper and catastrophic in operation: decision logic in Edge Functions, state transitions logged on every micro-step, and a browser client reduced to a remote actuator.

That pattern collapses under three pressures:

I built the system behind this article while working on YouxAI, so the examples come from code I authored. AI was used to help draft and edit the article structure, but the technical claims, implementation details, and examples were checked against the codebase. This is not a product announcement; the useful part is the architecture pattern and the trade-offs that made it hold under resource constraints.

1) The Resource Trap: Server-Side Orchestration Is the Wrong Default for Browser Automation

There is a seductive architecture many teams start with:

  1. Content script snapshots page state.
  2. Edge Function decides next action.
  3. Decision is persisted.
  4. Client executes one step.
  5. Client reports back.
  6. Repeat.

It gives a false sense of control. You centralize the "brain" but decentralize the cost.

Why this explodes compute budget

Automation is inherently high-frequency and stateful. A single multi-page application can emit dozens of state events:

If each event touches Edge runtime and database writes, infra spend is dominated by coordination, not business logic.

The rough cost shape is:

total_orchestration_cost ~= events_per_job * (function_invocation + db_write + retry_multiplier)

Free tiers tolerate light burst workloads. They do not tolerate chatty control planes.

Why this also hurts reliability

A server cannot directly manipulate browser reality. It cannot introspect transient DOM states, observe rendering races, or recover from portal-specific timing behavior without client round-trips.

So the server "controller" still depends on a client executor, and you get a fragile ping-pong loop:

server decide -> client attempt -> server reconcile -> client attempt

Under real-world latency and portal variability, that creates familiar failure modes:

What looks like a coordination strategy becomes a distributed systems tax.

For browser automation, especially on free-tier infrastructure, server-centric orchestration is often the wrong default because it pays premium prices for low-leverage work.

2) Client-Dominant Execution: Put the Control Loop Where State Is Observable

I flipped the architecture around a single rule:

The component that can observe and act on browser state should own the execution loop.

In practice:

Responsibility split

Extension side (hot path):

Supabase side (durable path):

This is not just a separation-of-concerns exercise. It directly removes network round-trips from the critical path.

Message flow that keeps the loop local

The execution pattern is intentionally asymmetric:

sidepanel/content -> background (ack pending)
background -> offscreen/local engine or content action
background -> commit durable transition to Supabase

The first acknowledgement is immediate to avoid message-channel timeout behavior in extension runtime. Work completes asynchronously. Results are routed back to the requester tab or runtime channel.

That "pending first, resolve later" contract keeps UI responsive while preserving deterministic completion behavior.

Why this scales better on free-tier plans

  1. You eliminate step-level server invocations for local decisions.
  2. You reduce control-plane write volume by committing only meaningful state boundaries.
  3. You align computation with active user sessions, where browser context is already loaded.
  4. You recover from service-worker churn via local cached pending state instead of server rehydration loops.

In one production save path, pending application data is cached in extension storage, deduplicated against existing database records, and only then persisted. If conditions are transient, such as auth or network instability, status remains pending for retry rather than forcing noisy partial commits.

Trade-off: no illusion of always-on workers

Client-dominant systems are session-bound. If the extension is inactive, the orchestration loop is inactive.

For browser-native automation, that is usually acceptable and often desirable. The product is not pretending to be a headless backend job engine; it is optimizing real-time assistance where browser state exists.

3) The "Ghost Runner" Offline Queue: Decouple Review From Live Tab Execution

Review flows become expensive when they require reopening full portal sessions for every pending item.

The solution I settled on was a Ghost Runner model: capture a structured form snapshot once, then review asynchronously from stored data.

Snapshot model

At discovery time, serialize form structure into JSON artifacts containing:

This artifact is enough to drive a pending-review workflow without maintaining live tabs for every queue item.

Why this is a free-tier multiplier

It separates three concerns that are usually tightly coupled:

  1. Discovery: expensive, DOM-dependent, timing-sensitive.
  2. Review: user-facing, asynchronous, often non-urgent.
  3. Execution: high-confidence action phase.

Without decoupling, each review interaction re-pays the full browser orchestration cost. With snapshots, review becomes cheap and mostly read-oriented.

Engineering constraints you cannot skip

Offline queue review is only safe with replay guards:

Otherwise, "offline review" degrades into stale-plan execution.

The broader pattern

Ghost Runner is not just a user-interface feature. It is a resource strategy.

Capture expensive context once. Reuse it across control surfaces. Execute only when you have enough confidence and active runtime context.

That pattern applies beyond job applications to any browser automation workflow with intermittent human approval.

4) Batch Persistence Strategy: Control Write Rate or Lose the System

Even after moving orchestration client-side, you can still fail by writing too frequently.

The governing principle is simple:

Event granularity should not dictate persistence granularity.

Problem: write amplification and trigger pressure

If every micro-event is persisted, you accumulate:

This compounds fast on free-tier databases.

Tactic 1: local buffering, threshold-based flush

Keep high-resolution activity local and persist summarized checkpoints.

A practical strategy:

Conceptually:

append(event);
if (bufferSize >= MAX || elapsed >= FLUSH_MS || state.isTerminal) {
  pushBatch(compact(buffer));
  clear();
}

This turns a noisy event stream into a manageable write pattern.

Tactic 2: exponential backoff with jitter on transient failures

Retries should reduce pressure, not amplify it.

Transient responses such as 429503, or network interruptions need:

In one extraction path, this pattern was necessary to avoid thundering-herd behavior when upstream services degraded.

Tactic 3: circuit breaking for unstable dependencies

When local AI or remote extraction dependencies repeatedly fail, continued retries across the entire pipeline create cascading instability.

Circuit breakers provide a controlled pause:

This prevents error bursts from becoming persistent write storms.

Tactic 4: idempotency and dedupe at write boundaries

Batching and retries only work if replays are safe.

You need explicit replay-safe semantics:

Without idempotency, retries are indistinguishable from duplication.

With idempotency, retries become a resilience tool instead of a data-quality risk.

5) Conclusion: Free-Tier Viability Is an Architecture Decision

If your product is browser-native and orchestration-heavy, infrastructure limits are not a late-stage optimization problem. They are first-order architecture constraints.

Client-dominant orchestration works because it aligns control with observability:

This is the difference between "we can demo automation" and "we can operate automation economically."

For bootstrapped teams on Supabase and Vercel free tier, that distinction is existential.

The durable lesson is straightforward: stop treating your backend like a puppeteer for tab state. Use it as a durable coordination layer. Let the client run the loop.

That architecture is not just cheaper. It is usually more correct.