The hybrid sync pattern: how cloud and local actually talk

The wiring between a cloud AI product and a local Mac Studio. SQS for events, S3 for artifacts, EventBridge for schedules, signed manifests for the round-trip.

The hybrid sync pattern: how cloud and local actually talk

The hybrid split between cloud and local is easy to draw on a whiteboard and tricky to make actually run. You sketch a cloud box on the left, a Mac Studio box on the right, an arrow between them labelled "sync," and everyone nods. Then you sit down to build it, and the arrow turns out to be six different arrows doing six different things, and you have to pick the right wire for each one or the whole thing turns into a flaky mess of cron jobs and SSH tunnels.

This piece is the wiring. I'll walk through the actual mechanisms that move work and data between a cloud-side product (the architecture spine we've been describing. Lambda, API Gateway, RDS with pgvector, S3, Bedrock, EventBridge, SQS) and a Mac Studio in the corner doing batch inference, evals, fine-tuning, and image generation. The patterns are concrete. The code stays at the shape level, enough that you can build it, without me writing your boto3 boilerplate for you.

How cloud and local actually talk AWS (cloud) Lambda RDS S3 (artifacts) SQS (event bus) EventBridge Mac Studio (local) SQS poller (launchd) mlx-lm inference S3 sync (boto3) signed-manifest writer EventBridge webhook SQS msg S3 result model artifact (S3) manifest event Six wires. Each side reaches into nothing the other side controls.
Hybrid sync wiring

The framing throughout: the cloud doesn't reach into your house, and your house doesn't reach into the cloud. Both sides hit AWS services that act as the meeting point. SQS is the inbox. S3 is the warehouse. EventBridge is the alarm clock. That's the model. Everything else falls out of it.

Cloud-to-local: SQS as the pull point

When the cloud has work for the local rig, a batch eval, a transcription job, a fine-tune kickoff, an image-generation request from the back-office UI, the cloud doesn't try to push it. Pushing means the cloud has to know your home IP, get past your router, authenticate against something running on your Mac. That's a security and reliability swamp.

The pattern is pull. Cloud-side, a Lambda drops a message onto an SQS queue. That message is small (a few kilobytes) and contains a job descriptor: type, ID, parameters, and an S3 location for any large inputs. SQS, short for Simple Queue Service, is AWS's hosted queue, producers drop messages in, consumers pull them out, with at-least-once delivery semantics, if you want to look it up later.

Mac Studio side, a poller process runs on a launchd schedule, every thirty seconds is a reasonable cadence for batch work. The poller calls ReceiveMessage on the queue, processes whatever it gets, and calls DeleteMessage when it's done. If it crashes mid-process, the message becomes visible again after the visibility timeout, and either this poller or its restarted self picks it up. The reliability comes from idempotency: every job descriptor includes a stable job ID, and the local processor checks "have I already done this one?" before starting, using either a local SQLite ledger or a small entry in S3.

The shape of the local poller, conceptually:

loop:
  msgs = sqs.receive_message(queue, max=10, wait=20)
  for msg in msgs:
    job = parse(msg.body)
    if already_done(job.id): sqs.delete_message(msg); continue
    inputs = s3.get(job.input_uri) if job.input_uri else None
    result = run_job(job, inputs)
    s3.put(job.output_uri, result)
    mark_done(job.id)
    sqs.delete_message(msg)

Notice what's not there. There's no inbound port open on the Mac Studio. There's no WebSocket. There's no cron that wakes up at weird times. There's a poller that asks "is there work?" every thirty seconds. SQS's long-poll (wait=20) means the call blocks until either a message arrives or twenty seconds pass, so the API call count stays sane.

For a financial advisor productizing a portfolio-diagnosis routine, the typical cloud-to-local flow is: customer uploads a portfolio CSV, cloud Lambda drops a "diagnose-portfolio" message on SQS pointing at the CSV in S3, the Mac Studio polls, runs the locally fine-tuned classification model against the holdings, writes the structured diagnosis back to S3, marks the message done. The cloud picks up the result on the next pass (more on how, in a moment).

EventBridge for scheduled work

SQS is the right fit for "the cloud has a piece of work for the local rig, do it whenever you can." It's not the right fit for "run the nightly eval at 2 a.m." or "retrain the secret-sauce model every Sunday." That's what EventBridge schedules are for.

EventBridge can fire a scheduled rule that drops a message onto an SQS queue, hits a Lambda, or pings any other AWS target. For the local-side scheduled work (nightly evals, weekly fine-tunes) the simplest pattern is: EventBridge rule fires on a cron expression, target is the same SQS queue the local poller is reading. The message body declares the scheduled job type. The Mac Studio sees it on the next poll and runs it.

This means everything the Mac Studio does is dispatched through SQS, whether it came from a customer event or a scheduled job. One consumer, one inbox. The Mac Studio doesn't need its own cron table; the schedule lives in AWS, version-controlled in your CDK, and it's the same control plane the cloud uses for everything else.

The marketing strategist's productized brand-positioning method gets the benefit here. Nightly, EventBridge fires a "regenerate brand-asset library" message. Mac Studio polls, picks it up, runs mflux to produce a fresh batch of hero images and social cards based on the latest brand voice fine-tune, writes them to S3 under a versioned prefix, marks done. The cloud-side product just reads the latest version when the customer asks for assets.

S3 as the shared warehouse

Both sides read from and write to S3. That's where anything bigger than a few KB lives. The structure of the bucket matters more than the bucket itself.

The pattern I default to: one bucket per environment (eotm-prod, eotm-staging, eotm-dev), with prefixes carving up the namespace. Roughly:

inputs/<job-type>/<job-id>/...
outputs/<job-type>/<job-id>/...
artifacts/models/<model-name>/<version>/...
artifacts/eval-sets/<eval-name>/<version>/...
artifacts/manifests/<manifest-id>.json

Inputs are written by the cloud, read by the Mac Studio. Outputs the other way. Artifacts (model weights, eval golden examples, training data) are written by whichever side trained them and read by the other side as needed.

The two sides don't share credentials. Cloud-side IAM roles let Lambdas read inputs and write outputs and read artifacts. Mac Studio-side credentials are scoped to a dedicated IAM user with a long-lived access key (stored in the Mac's keychain) that can read inputs, write outputs, and write artifacts under specific prefixes. The Mac Studio cannot read customer data outside the input prefix it was told about. That separation is the audit boundary, the Mac Studio sees only what the cloud explicitly handed it, and only for the job in question.

Want to go deeper on how this connects to retrieval and the secret sauce? The artifact layout above is the same one retrieval is the secret sauce surface assumes for embeddings and corpus files, and the cost story for S3 (free until you egress) lives in the cost model piece from earlier this week.

Local-to-cloud: signed manifests and event triggers

The trickier direction is local pushing results back into the cloud product. The naive version is "Mac Studio writes results to S3, cloud product polls S3 for new files." That works for small scale. It falls apart for two reasons: polling is inefficient, and you want the cloud to react to a result landing, not discover it ten minutes later.

The pattern I use: signed manifest files kick off downstream events.

The Mac Studio finishes a job. It writes the actual output (say, a trained model file or a JSON diagnosis) under outputs/<job-type>/<job-id>/. Then, as the last step, it writes a small manifest.json next to it. The manifest contains: the job ID, the input it consumed, the output paths, a timestamp, and an HMAC signature using a key shared between cloud and local. HMAC is a way of cryptographically signing a small payload with a shared secret, so the receiver can verify the sender knew the secret, if you want to look it up later.

The manifest write is the trigger. S3 has an event notification rule set up on outputs/*/manifest.json keys, when one lands, S3 fires an EventBridge event. A cloud-side Lambda picks it up, verifies the HMAC (rejecting the event if the signature doesn't match), and then dispatches whatever the downstream work is: update the database row, notify the customer, ping the consultant's supervisor queue, kick off the next stage of the pipeline.

The shape, conceptually:

# local side, end of run_job:
write_output(output_path, result)
manifest = {
  job_id, input_uri, output_uri,
  timestamp, content_hash, signature: hmac(shared_key, ...)
}
s3.put(manifest_path, json(manifest))
# that put triggers S3 -> EventBridge -> Lambda

The signature matters. Without it, anyone with write access to the bucket could drop a manifest and trigger cloud-side actions on data they fabricated. With it, the cloud Lambda has a cheap verification step that proves the manifest came from a process that holds the shared key, which lives only in the Mac Studio's keychain and in Secrets Manager on the cloud side, never in the bucket.

For a career coach packaging their resume-positioning review, the round trip looks like: customer uploads a resume, cloud drops a "review-resume" SQS message, Mac Studio runs the locally fine-tuned positioning model, writes the structured review to S3, writes a signed manifest. Manifest landing triggers a Lambda that verifies the signature, writes the review into the customer's RDS row, marks the job complete, and emails the customer. The cloud product never reaches into the Mac Studio. The Mac Studio never reaches into the cloud product. Both reach into AWS services in the middle.

Model artifacts, the special case

The most important local-to-cloud flow is also the simplest: trained model artifacts.

The Mac Studio fine-tunes a small model on the consultant's annotated examples (the secret sauce). The output is a model file, call it model-v23.safetensors plus a config. The Mac Studio writes it to artifacts/models/<consultant-id>/v23/ and then writes a manifest with the version pointer.

Cloud-side, the Lambda that runs inference doesn't know about v23 yet. It loads whatever model version it has cached. The handoff is via cold-start. When a Lambda execution environment cold-starts, its init code reads artifacts/models/<consultant-id>/current (a small pointer file) to find the version it should load, then downloads that version into the Lambda's temp directory and loads it. The pointer file is what gets updated when a new version is ready, the manifest-trigger Lambda is responsible for swapping it.

This means rolling out a new model is two writes: write the new version artifacts, write the new pointer. Existing warm Lambdas keep serving the old model until they recycle. New cold-starts pick up the new one. There's a brief mixed-version window which is fine for the kind of workloads we're talking about; if you need atomic cutover, you flush the Lambda concurrency, but for an MVP you don't.

This pattern also gives you rollback for free. The old version still sits in S3. Repoint the pointer file at the old version. Next cold-start, you're back on the previous model. No deploy, no CDK, no panic.

What doesn't go through this wiring

Two things deliberately stay off the hybrid path.

Customer-facing inference. When a customer's query needs an LLM response in real time, it goes to Bedrock, not the Mac Studio. The Mac Studio's latency is fine for a 30-second batch job; it's not fine for a 1.5-second customer interaction. The hybrid path is for batch, scheduled, and back-office work.

Anything containing raw customer PII the Mac Studio doesn't need. The cloud-side scrubs and tokenises before the SQS message gets created. If the Mac Studio is doing a job that doesn't need the customer's name and email, it doesn't get them. This is a habit thing, easy to be sloppy here when nobody's watching. The day you have a regulator asking where data flows, you'll want to point at the SQS message format and say "those are the only fields that ever cross."

The whole shape

Pull all of this together and the hybrid sync pattern is six pieces:

  1. SQS queue as the cloud-to-local inbox. Pull, not push.
  2. EventBridge schedules as the alarm clock for scheduled local work, dropping into the same queue.
  3. S3 prefixes as the shared warehouse for inputs, outputs, artifacts, and manifests.
  4. Signed manifest writes as the local-to-cloud trigger mechanism, via S3 → EventBridge → Lambda.
  5. HMAC verification as the lightweight integrity check on every manifest the cloud picks up.
  6. Cold-start artifact loading as the model-handoff mechanism, with a pointer file enabling instant rollback.

Six things, each doing one job, each easy to reason about independently. The whole pattern fits in maybe four hundred lines of code across both sides, including the poller, the manifest writer, the verifier Lambda, and the IAM scaffolding in your CDK. The wiring is small. The clarity is large.

If you're building this, my one ask: get the signed manifest pattern in from day one. Polling-based versions of this work, sort of, until they don't. The day you go to production and a customer's "is it done yet?" answer depends on a 5-minute poll interval, you'll regret not having the event trigger. Build the trigger now; thank yourself later.