Zero-Downtime AI Agent Migration: Cloud to Home

The Problem

JARVIS has been running 24/7 in the cloud for months — answering questions, managing tasks, routing notifications. It’s become infrastructure. The kind of thing you notice when it’s gone.

Now I need to move it to local hardware. Not because the cloud version broke, but because I want lower latency, more control, and the ability to run models locally via Ollama. The constraint: zero downtime. JARVIS handles real-time Slack messages from multiple channels. If it goes offline for even a minute during the migration, messages get lost. There’s no replay buffer. Slack’s Event API is fire-and-forget.

The non-negotiables:

  • Zero message loss during migration
  • Memory continuity — the agent needs access to all historical context
  • Transparent handover — users shouldn’t notice the switch
  • Rollback capability — if home hardware fails, cloud must take over seamlessly

Why Not a Simple Cutover?

The naive approach: shut down cloud, spin up home, update DNS. But this breaks every requirement:

  1. Downtime window: Even a 30-second cutover means lost messages
  2. Memory gap: Cloud and home start with divergent state
  3. No rollback: If home hardware dies at 3am, cloud can’t resume without manual intervention
  4. Testing risk: Can’t validate home setup without going live

Instead, I need both systems running simultaneously during the transition, with intelligent routing and shared memory.

Architecture: Dual-Core JARVIS

Dual-core JARVIS architecture diagram showing Home Core (Mac Mini + OpenClaw + Ollama) and Cloud Core (MeshClaw + Bedrock) connected via Tailscale mesh, both sharing Cerebro vault via Dropbox

Dual-core architecture: Home and Cloud instances share memory via Dropbox-synced Cerebro vault

The solution: two independent JARVIS instances that share a single memory layer.

Home Core

  • Hardware: Mac Mini with M4 Pro
  • Runtime: OpenClaw (my local agent runtime)
  • LLM: Ollama running mistral-nemo:12b and llama3.3:70b for fast local inference
  • Network: Connected to cloud via Tailscale mesh for secure tunnel
  • Advantage: Sub-200ms response time, no API rate limits, runs private models

Cloud Core

  • Hardware: Existing cloud VM
  • Runtime: MeshClaw (distributed agent runtime)
  • LLM: AWS Bedrock for access to Amazon-internal tools and knowledge
  • Advantage: Already stable, integrated with work systems, fallback if home dies

Both cores run identical agent code. The only difference is infrastructure.

The Slack App Problem

Here’s where it gets tricky. JARVIS connects to Slack via Socket Mode — Slack pushes events over a WebSocket connection. The problem: Socket Mode round-robins events across all connected clients with the same app token.

If both Home and Cloud connect to the same Slack app:

  • Message A goes to Home
  • Message B goes to Cloud
  • Neither agent has complete conversation context
  • Users see split-brain responses

Solution: Two separate Slack apps.

  • JARVIS-Home: New app token, connects only to Home Core
  • JARVIS-Cloud: Existing app token, connects only to Cloud Core

This creates routing isolation — each app receives 100% of its events. The question becomes: how do we route messages to the right app?

Channel-Based Routing

The routing rule is simple:

IF (direct_message OR personal_channel):
    route → JARVIS-Home
ELSE:
    route → JARVIS-Cloud

Why this split?

  • Personal channels and DMs: Low-latency responses matter, often casual queries, no need for Amazon-internal tools → Home wins
  • Work channels: May require Bedrock models with access to internal knowledge, less latency-sensitive → Cloud wins

Implementation:

  1. Add JARVIS-Home to my personal workspace and DM channels
  2. Leave JARVIS-Cloud in work channels
  3. Gradually migrate work channels to Home once proven stable

The beauty: users control the routing. By choosing which app to @-mention or which channel to post in, they implicitly select Home or Cloud. No central routing service required.

Memory Architecture: Cerebro Vault

The hard part: both cores need access to the same memory — conversation history, task state, learned preferences, factual knowledge.

Traditional approaches:

  • Shared database: Adds complexity, introduces latency, single point of failure
  • Event bus: Requires real-time sync, network dependencies, out-of-order risk
  • API layer: Centralized service that both cores call — defeats the point of distributed architecture

Better approach: Dropbox-synced filesystem.

I already have an Obsidian vault called Cerebro — my personal knowledge base, 200+ markdown notes synced across all devices via Dropbox. Obsidian is just a fancy text editor; the vault is a directory of .md files.

Key insight: Put agent memory inside the Cerebro vault.

~/Dropbox/cerebro/
├── conversations/
│   ├── 2026-05-03-project-planning.md
│   ├── 2026-05-04-code-review.md
├── tasks/
│   ├── active.md
│   ├── completed.md
├── knowledge/
│   ├── people.md
│   ├── projects.md
│   ├── preferences.md
└── agent-memory/
    ├── jarvis-state.json
    ├── context-cache.json

Both Home and Cloud mount the same Dropbox folder. Cerebro becomes the shared memory substrate. Dropbox handles:

  • Conflict resolution (operational transform)
  • Offline-first sync (optimistic updates)
  • Multi-device consistency (CRDT-like behavior)

Benefits:

  1. No new infrastructure — leverages existing sync mechanism
  2. Filesystem semantics — both agents just read/write files
  3. Human-readable — I can inspect memory in Obsidian
  4. Automatic history — Dropbox keeps 30-day version history

Trade-offs:

  • Eventual consistency — not real-time, ~1-2 second sync latency
  • Conflict potential — if both agents write simultaneously, Dropbox may create conflicted copies
  • Not transactional — no ACID guarantees

For JARVIS’s use case, eventual consistency is fine. Conversations are mostly sequential; parallel writes are rare. If a conflict occurs, both agents log the conflict and reconcile manually.

The Handover Protocol

Migration happens in four phases:

Phase 1: Shadow Mode (Days 1-7)

Home Core runs but doesn’t respond to Slack. It:

  • Listens to events (via JARVIS-Home app)
  • Processes messages
  • Updates Cerebro memory
  • Logs what it would have said

Cloud Core continues handling all responses.

Purpose: Validate that Home Core sees events correctly, memory sync works, no crashes.

Phase 2: Canary Routing (Days 8-14)

JARVIS-Home joins 2-3 low-traffic personal channels. Home Core starts responding there. Cloud Core still handles work channels.

Both cores update shared memory. We monitor for:

  • Response quality (is Home as good as Cloud?)
  • Memory consistency (do both see the same context?)
  • Latency (is Home actually faster?)

Purpose: Smoke test with real users in controlled scope.

Phase 3: Progressive Migration (Days 15-30)

Add JARVIS-Home to additional channels, one per day. Users can explicitly @JARVIS-Home to force routing to Home Core, or @JARVIS-Cloud to stick with cloud.

Monitor for:

  • Home Core stability (uptime, crash recovery)
  • Memory divergence (if any)
  • User feedback

Phase 4: Cloud Deprecation (Day 30+)

Once Home Core proves stable:

  1. Remove JARVIS-Cloud from all non-critical channels
  2. Keep Cloud running in read-only mode as warm standby
  3. If Home dies, manually re-add Cloud to channels (rollback plan)
  4. After 90 days of Home stability, decommission Cloud

Memory Sync Details

Each agent writes to Cerebro using append-only logs:

// agent-memory/jarvis-state.json
{
  "events": [
    {
      "timestamp": "2026-05-03T14:32:01Z",
      "agent": "home",
      "type": "message_received",
      "channel": "C123456",
      "user": "U789012",
      "text": "What's the status of PR #42?",
      "response": "PR #42 merged 2 hours ago."
    }
  ]
}

Reads are simple: each agent loads the full log and replays events to build current state. This is inefficient but correct — no cache invalidation bugs, no race conditions.

Optimization for later: snapshot + delta pattern (write full snapshot every 100 events, then deltas).

Conflict Resolution

If both agents write to jarvis-state.json simultaneously, Dropbox creates:

  • jarvis-state.json
  • jarvis-state (Cloud's conflicted copy).json

Each agent runs a reconciliation job every 5 minutes:

  1. Check for conflicted copies
  2. Merge by timestamp (total order)
  3. Delete conflicted copies
  4. Log the merge event

Since events are timestamped and append-only, merging is deterministic.

Observability

Each agent reports health metrics to a shared status.json:

{
  "home": {
    "last_heartbeat": "2026-05-03T14:35:22Z",
    "uptime_seconds": 86400,
    "messages_handled": 347,
    "errors_24h": 2
  },
  "cloud": {
    "last_heartbeat": "2026-05-03T14:35:19Z",
    "uptime_seconds": 2419200,
    "messages_handled": 8932,
    "errors_24h": 0
  }
}

A monitoring script checks:

  • Are both agents alive? (heartbeat within last 60s)
  • Is memory sync working? (both agents see recent events)
  • Are response times acceptable? (Home < 500ms, Cloud < 2s)

If Home Core goes down:

  1. Alert via Slack
  2. Manually re-add JARVIS-Cloud to affected channels
  3. Investigate Home Core failure
  4. Once fixed, resume migration

Why This Works

The key insights:

  1. Dual-core architecture avoids cutover risk — both systems run simultaneously, no big-bang migration
  2. Dropbox as shared memory layer — leverages existing infrastructure, no new services
  3. Channel-based routing — users control routing via app choice, no central router
  4. Append-only memory — simplifies conflict resolution, enables deterministic replay
  5. Progressive rollout — canary channels validate before full migration

Lessons Learned

What I’d do differently:

  • Start with shadow mode earlier — ran cloud-only for months before considering migration, should have built dual-core from day one
  • Structured logging from the start — had to retrofit event logs to enable memory sync
  • Health checks before memory writes — early version had a bug where unhealthy agents still wrote to Cerebro, polluting shared state
  • Version agent code carefully — Home and Cloud must stay in sync, otherwise memory schema diverges

Next Steps

After migration completes:

  1. Edge deployment — add a third core on a Raspberry Pi at a friend’s house, geographic redundancy
  2. Memory compaction — current append-only log grows unbounded, need periodic compaction
  3. Real-time sync — Dropbox is good but not real-time, consider syncthing or custom rsync
  4. Multi-agent coordination — extend pattern to other agents (EDITH for knowledge search, FRIDAY for automation)

Conclusion

Zero-downtime migration isn’t about cutting over cleanly — it’s about running two systems in parallel with shared state. By treating memory as a filesystem problem (not a database problem) and using Dropbox as a sync layer, I avoided building a distributed system from scratch.

The dual-core architecture isn’t just a migration strategy — it’s the new steady state. Home Core handles fast, local queries. Cloud Core provides fallback and access to work systems. Both share memory via Cerebro. If either fails, the other continues.

Infrastructure should be boring. JARVIS migrations should be invisible.

Now the hard part: proving it works.