> ## Documentation Index
> Fetch the complete documentation index at: https://docs.myme.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Deployment modes

> Three ways Marfa can run — local-only, server-synced, third-party-synced

Marfa has one SDK surface and three deployment modes. Apps use the same client code regardless of mode; only the transport and enforcement guarantees change.

<Info>
  **Mode versus client.** Sync agents, importers, CLIs, and similar automation tools are **apps with credentials**, not a deployment mode and not a server feature. Each runs against whichever mode its host chose. The word *sync* in "server-synced" describes data movement between a client and the server — it is independent of any particular client, including clients that happen to carry *sync* in their name.
</Info>

## Local-only

**SDK + local SQLite store. No server.**

The SDK talks to an on-device store rather than a remote API. Items, edges, metadata, extensions — all persisted locally. Suitable for:

* Single-app, single-device setups. A journal app that doesn't need cross-app data.
* Multi-app setups sharing a local store via OS mechanisms (iOS App Groups, macOS shared containers).
* Apps that want to start local and optionally graduate to server-synced later.

### Trade-offs

* **No conflict resolution.** Single writer per store, or app-coordinated. The server's version-conflict machinery doesn't apply.
* **No cross-app provenance enforcement.** Apps trust the local SDK. `source` is stamped best-effort by whichever SDK writes; there's no server to validate it.
* **No sync.** Data stays on-device. Reconciling across devices needs an external mechanism.

### When to use

Prototypes, offline-first apps, privacy-first apps, apps that run disconnected for extended periods. Apps that later move to server-synced without changing their code.

## Server-synced

**SDK + the Marfa server.**

The canonical mode. Multi-tenant, multi-app, real-time via SSE, durable storage, server-stamped non-forgeable provenance, OAuth grants, audit trail, metrics.

<Warning>
  Running more than one Marfa instance behind a load balancer has a small number of operational caveats — rate limits are per-instance, and SSE needs sticky sessions. See [Scaling and operational limits](#server-synced-scaling-and-operational-limits) below before adding a second instance.
</Warning>

All the mode-specific features called out across these docs (server-stamped `source`, SSE `/events`, OAuth flow, webhook delivery, cross-tenant sharing, admin metrics) apply only here.

### Trade-offs

* **Requires connectivity.** Without a network, apps can't read or write unless they run their own local cache on top.
* **Introduces a server to operate.** Self-hosted or managed by someone — someone is running Postgres and keeping it healthy.

### When to use

The platform-thesis mode. Any time data matters across apps, devices, and time.

## Third-party-synced

**SDK + local SQLite + an external sync layer.**

The SDK reads and writes locally, same as in local-only mode. An external sync engine (CloudKit, a custom WebDAV bridge, a file-sync system) replicates the SQLite store across devices.

### Trade-offs

* **No Marfa server.** No cross-tenant sharing, no OAuth, no SSE. Whatever the third-party tool provides is what you get.
* **Conflict resolution is the third-party tool's.** Apps inherit CloudKit's last-writer-wins-per-zone semantics, for example.
* **Provenance is best-effort.** Same as local-only — no server to enforce `source`.
* **Tenancy maps to the third-party account.** One iCloud account = one tenant. A user with personal and work iCloud accounts runs them as separate Marfa tenants.

### When to use

Apps that want cross-device sync without standing up a Marfa server. Consumer iOS/macOS apps that lean on CloudKit for free sync. Privacy-first setups where data never leaves the user's own cloud account.

## What's the same across modes

* The **SDK surface.** `client.items.create`, `client.edges.create`, etc. are identical.
* The **data shape.** Items, edges, metadata, extensions, attachments all have the same structure.
* The **type system.** Core types and custom types register the same way. Inheritance works the same way.
* The **edge model.** Dual-gate write semantics apply when permissions are present; otherwise trust-based (local-only, third-party-synced).

## What's mode-specific

| Feature                     | Local-only          | Server-synced                      | Third-party-synced           |
| --------------------------- | ------------------- | ---------------------------------- | ---------------------------- |
| `source` non-forgeable      | ✗ (SDK best-effort) | ✓ (credential-stamped)             | ✗ (SDK best-effort)          |
| SSE `/events`               | ✗                   | ✓                                  | ✗                            |
| OAuth grants                | ✗                   | ✓                                  | ✗                            |
| Webhook delivery            | ✗                   | ✓                                  | ✗                            |
| Audit trail                 | ✗                   | ✓                                  | ✗                            |
| Admin metrics               | ✗                   | ✓                                  | ✗                            |
| Cross-tenant sharing        | N/A                 | ✓                                  | ✗ (per account)              |
| Rate limiting               | N/A                 | ✓                                  | N/A                          |
| Conflict resolution         | app-coordinated     | field-level optimistic concurrency | third-party tool's semantics |
| Retention / storage tiering | app-coordinated     | tier-aware, server-enforced        | per the third-party tool     |
| Works offline               | ✓ (always local)    | ✗ (unless local cache layered)     | ✓ (sync on reconnect)        |

## Server-synced: scaling and operational limits

Marfa is safe to horizontally scale behind a load balancer. Cross-instance coordination is handled through Postgres — no Redis, no separate worker tier. A few caveats remain; none block multi-instance deployments but operators should know them.

* **Webhook delivery is cluster-safe.** The delivery poller uses `SELECT … FOR UPDATE SKIP LOCKED` to atomically claim pending rows, so a given delivery is picked up by exactly one instance. Failed instances release claims automatically after a short TTL.
* **Background jobs run once per tick cluster-wide.** Trash purge, version thinning, audit cleanup, feed expiry, and event-log cleanup each run under a named Postgres advisory lock, so only one instance performs work on each tick. Other instances skip the tick cleanly.
* **API-key `last_used_at` is DB-enforced.** The conditional UPDATE debounces writes at the database layer, so writes collapse to at most one per hour per key regardless of how many instances are handling requests.
* **Rate limits are per-instance.** The rate limiter keeps counters in the server process, not Postgres. With N instances behind a load balancer, a caller limited to `RATE_LIMIT_REQUESTS` gets roughly `N × RATE_LIMIT_REQUESTS` effective requests per window. Treat the setting as a per-instance soft cap — set it conservatively and rely on your load balancer or upstream gateway for hard global limits.
* **SSE subscriptions attach to the process-local event bus.** A client connected to instance A won't see events published on instance B. Configure sticky sessions at the load balancer for the `/events` route (most balancers support this out of the box — AWS ALB, nginx `ip_hash` / `sticky cookie`, Cloudflare `Origin-Steering: sticky`, etc.) so each SSE connection stays on one instance for its lifetime.

For any deployment larger than "one instance on one host", rolling restarts, graceful shutdown, and a shared Postgres database are all the infrastructure you need.

## Choosing

* **Build against server-synced by default.** It's the mode with the most guarantees and the one most features target.
* **Add local-only as an option** if your app has a meaningful offline story or a "free tier" that doesn't need a server. Same SDK call sites; different client config.
* **Use third-party-synced** when you specifically want to avoid running a server and your platform's sync layer meets the bar.

Switching modes later is mostly a config change in the SDK. The type system, edge model, and data shape don't change.
