Skip to main content

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.

The sync agent is a translation layer between files on disk and typed Marfa items. Unlike transport layers — Syncthing, iCloud Drive, Obsidian Sync — that move bytes between machines, the sync agent interprets files as typed data so other Marfa clients can read, write, and reason over them. Published to npm as @withmarfa/sync; the CLI binary is marfa-sync. The engine is UI-agnostic. The CLI drives it over a loopback HTTP control surface; menu-bar and desktop UIs use the same surface.

Install

npm install -g @withmarfa/sync
Installs the marfa-sync binary on your PATH. Confirm with marfa-sync --help.

Quick start

1

Start the agent against a folder

marfa-sync start <folder> --url <api-url> --key <api-key>
Watches the folder, registers it as the first root, and begins reconciling against the server.
2

Optionally run as a daemon

Pass --daemon to install a launchd service so the engine survives terminal sessions:
marfa-sync start <folder> --daemon --url <api-url> --key <api-key>
3

Confirm it's running

marfa-sync status
Reports per-root sync state, open conflicts, and the engine’s HTTP port.

Configuration

Configuration merges from three sources in priority order: CLI flags, environment variables, then ~/.marfa/sync.json.
VariableDescription
MARFA_API_URLMarfa server URL
MARFA_API_KEYAPI key for authentication
The credential’s default_tier shapes what tier items the agent writes. items.bulk is admin-only — a standard-scoped credential gets a one-time warning and the engine downshifts to per-item creates for the rest of the session.

Adding roots

The agent watches one or more roots. Manage them at runtime:
CommandPurpose
marfa-sync roots add <path> [--debounce <ms>]Register a new root. Returns its id.
marfa-sync roots listList registered roots with sync state.
marfa-sync roots rm <id>Stop watching a root. Items already on the server are not deleted.
Each root carries a self-contained <root>/.marfa/ tree — config, mapping rules, ignore patterns, SQLite bookkeeping, status snapshot, conflict copies. The structure parallels .git: per-root state stays with the folder, engine-wide state lives at ~/.marfa/.

Item types and mapping rules

When a file is pushed, the agent decides what type the resulting Marfa item carries. Resolution order, first match wins:
  1. Frontmatter type: if present (text files only)
  2. User rules from <root>/.marfa/mappings.json
  3. Built-in mime defaults (image/*core.file.image, audio, video)
  4. Fallback: core.file
User rules let the agent promote files to richer types based on path, mime, or frontmatter shape. Example — turn every .md file in Notes/ into a core.note, with title taken from the first H1 and body from the post-frontmatter content:
{
  "rules": [
    {
      "id": "notes",
      "match": { "glob": "Notes/**/*.md" },
      "transform": {
        "type": "core.note",
        "tier": "library",
        "propertyExtractors": [
          { "kind": "first-h1", "into": "title" },
          { "kind": "body-after-frontmatter", "into": "body" },
          { "kind": "frontmatter-key", "key": "tags", "into": "tags" }
        ]
      }
    }
  ]
}
Match predicates: glob (gitignore-style), mime, frontmatter (key/value match, or true for presence). All present predicates AND together.

Folder structure

Paths are properties, not graph structure. The agent stores each file’s root-relative path as the item’s source_id (e.g. Daily/2026/05/08.md). It does not fabricate folder items with parent-of edges, and there is no core.folder type.
Filesystem hierarchy is metadata about where bytes sit on disk — not data about the items themselves. Treating it as a property rather than as a graph keeps item counts honest, sidesteps two-way maintenance of a folder tree the user already maintains on disk, and matches how the other surface clients (Notes, Messages, Mailbox) model their content: no first-class folder primitive, hierarchy expressed through other means.

Querying by folder

source_id is a system field on every item, so the standard filter language reaches it directly. To list everything under a folder, use a starts_with prefix match:
source_id starts_with "Daily/"
Combine with type, tier, or other filters as normal:
type eq "core.note" AND source_id starts_with "Daily/2026/" AND tier eq "library"
See Search and query for the full filter language and operator set. A folder view in any Marfa client is a saved query of this shape — no graph traversal needed.

Cross-device behavior

source_id is relative to the vault root, so the same logical vault on a different machine produces the same source_id values regardless of where on disk it lives. A Mac with the vault at ~/Notes/ and a Mac with it at /Volumes/Work/Notes/ both produce source_id = "Daily/2026/05/08.md" for the same file. Absolute paths live only in the agent’s local state.db and never reach the server. A folder query (source_id starts_with "Daily/") returns the same items regardless of which device synced them. Folder structure is portable across the user’s devices for free.

Removing the agent removes the items

Items the agent creates are stamped with a source that resolves to the agent’s system.connection row. Revoking that Connection cascades deletion of every item it authored — no separate “delete this folder” operation exists or is needed. To stop syncing one root without dropping the others, marfa-sync roots rm <id> stops watching but does not delete the items already on the server; to delete the items, revoke the Connection or trash them through the standard item lifecycle.

When to use folders vs tags or edges

Folders are good at what filesystems are good at: where the bytes live. They give the user a familiar mental model for organizing files on disk and they give clients a cheap prefix-query for “everything under here”. For grouping that needs first-class data treatment — “all notes about Project X”, “everything attached to this meeting”, “the chain of replies in this thread” — use tags or typed edges. Tags are flat labels that travel with the item regardless of where the file moves on disk; edges express directional relationships between items that the query layer and other apps can traverse. Both are independent of where the file sits in the folder tree.

Rename detection

Renaming a file inside a watched root preserves the Marfa item id, its edges, and its version history. The agent matches the new path’s inode against recently-unlinked paths in a 5-second window to recognize the rename, then updates the item’s source path on the server in place. No delete-and-recreate, no orphan edges. Cross-volume moves fall through to delete + create cleanly (different inodes). Renames that span longer than 5 seconds — for example, an engine crash between unlink and add — fall through the same way.

Conflict handling

When the server and disk diverge on the same item, the agent snapshots the conflicting local copy to <root>/.marfa/conflicts/<basename>.<id>.<ext>, force-pushes the local version to preserve it in version history, pulls the server’s version to the canonical path, and emits a conflict-opened event. Resolve in one of two ways:
  • Edit the canonical file and delete the conflict file — the watcher picks up the unlink and closes the conflict.
  • Run marfa-sync resolve <conflict-id> --keep local|server. local copies the conflict file over the canonical path and re-pushes; server simply deletes the conflict file.
The same 8-hex conflict-id appears in the conflict filename, status.json, and the CLI command — no translation step between surfaces.

Supported filesystems

The agent’s stance on filesystems known to misbehave under continuous watching is “warn and allow”: register the root, surface a root-warned event so a UI can show the warning inline, and let the user proceed with eyes open.
Detected by path-prefix against the system iCloud container. iCloud’s eviction and redownload cycle generates spurious filesystem events the agent cannot distinguish from genuine writes, so real-time sync is unreliable. Move the folder out of iCloud Drive for reliable two-way sync.
Detected by inspecting active mounts. Push (local-to-server) works fine; bidirectional real-time does not, because remote writes don’t always trigger filesystem-change events on macOS or Linux.

Engine events

The agent exposes a server-sent-events stream tailable via marfa-sync events. Each event is a typed record.
EventFires on
file-syncedFile pushed or pulled successfully. Carries direction and action (created, updated, deleted, renamed).
file-failedPush or pull failed. Carries reason.
conflict-openedNew conflict detected; conflict file written to .marfa/conflicts/.
conflict-resolvedUser picked a winner or deleted the conflict file.
root-added / root-removedRoot configuration changed at runtime.
root-warnedFilesystem caveat surfaced at root-add or recovery.
root-resync-started / root-resync-finishedFull root rescan in progress.

Command reference

CommandPurpose
marfa-sync start <folder> [--daemon] [--url <api-url>] [--key <api-key>] [--types <path>]Start the engine and register the folder as a root.
marfa-sync stopStop the daemonised engine.
marfa-sync status [--json]Report engine and per-root state.
marfa-sync stats [--json]Show engine runtime stats — pending events, totals processed, circuit-breaker state.
marfa-sync roots add <path> [--debounce <ms>]Register a new root at runtime.
marfa-sync roots listList registered roots.
marfa-sync roots rm <id>Remove a root from the watch set.
marfa-sync resync <root-id>Re-scan a root from scratch and reconcile against the server.
marfa-sync resolve <conflict-id> --keep local|serverResolve an open conflict by choosing a winner.
marfa-sync eventsTail the engine event stream.
status queries the loopback HTTP surface first and falls back to per-root <root>/.marfa/status.json when the engine isn’t reachable. Every other command requires the engine to be running.

Troubleshooting

Check the API key is valid (marfa-sync status shows the engine state and any auth errors). Confirm the folder isn’t on a warned filesystem — root-warned events surface in marfa-sync events. If mappings.json is in use, verify the rules actually match the files in the folder; a file with no matching rule and no frontmatter type: falls through to the core.file fallback, which the agent skips for text content.
The rename-detection window is 5 seconds. Renames that span longer — for example, a crash between the unlink and the add — fall through to delete + create. Cross-volume moves do the same because the inode changes. To recover the original item’s edges and history, restore from the version history or delete the new item and rename within the window.
Folder structure isn’t modeled as items. See Folder structure above — query items by source_id prefix to list everything under a folder.
Copy ~/.marfa/sync.json to the new machine. The state SQLite databases under each <root>/.marfa/ repopulate on first marfa-sync start. The Marfa items already live on the server; nothing has to migrate.

Repo

Source at withmarfa/sync. Engine internals, mapping rule schema, the loopback HTTP control surface, and the conflict-resolution state machine live in the repo’s CLAUDE.md.