File Sync

Share state across multiple instances of an agent-native app through a database.

Overview

File sync watches your local files, pushes changes to a database, and pulls remote updates back to disk. Files stay the source of truth — the database is just a sync target.

Out of the box it uses Drizzle with SQLite — no external services, no extra packages. Just add a config file with your sync patterns and you're done.

Setup

Create a sync-config.json in your content root (typically data/) with the file patterns you want to sync:

{
  "syncFilePatterns": [
    "data/**/*.json",
    "data/**/*.md",
    "!data/local-only/**"
  ]
}

That's it. When Drizzle is set up (which it is by default), the sync engine detects the config file and starts syncing automatically. The SQLite database is created at data/sync.db on first run.

The default template server already calls createFileSync() — no server code changes needed.

Configuration

FieldDescription
syncFilePatternsGlob patterns for files to sync. Supports negation with ! prefix.
privateSyncFilePatternsPatterns for files that sync to a per-user channel instead of the shared channel.

Denylist

The sync engine always blocks sensitive files regardless of your patterns — .env*, *.key, node_modules/, .git/, *.db, and editor/OS junk files. This prevents accidental credential leaks.

External Backends

For multi-server deployments or real-time team sync, you can swap the default SQLite backend for an external database. Set FILE_SYNC_BACKEND in your .env:

Firestore

pnpm add firebase-admin
FILE_SYNC_BACKEND=firestore
GOOGLE_APPLICATION_CREDENTIALS=./service-account.json

The collection is created automatically — no additional setup needed.

Supabase

pnpm add @supabase/supabase-js
FILE_SYNC_BACKEND=supabase
SUPABASE_URL=https://xyz.supabase.co
SUPABASE_PUBLISHABLE_KEY=sb_publishable_...

Supabase requires a files table:

CREATE TABLE files (
  id TEXT PRIMARY KEY,
  path TEXT,
  content TEXT,
  app TEXT,
  owner_id TEXT,
  last_updated BIGINT,
  created_at BIGINT
);
CREATE INDEX idx_files_app_owner ON files(app, owner_id);

Convex

pnpm add convex
FILE_SYNC_BACKEND=convex
CONVEX_URL=https://your-project.convex.cloud

Convex requires a schema and functions — see the repo README for the full Convex setup.

Custom Adapters

If the built-in backends don't fit, you can implement the FileSyncAdapter interface from @agent-native/core/adapters/sync for any database. An adapter is a single class with five methods: query, get, set, delete, and subscribe. The sync engine handles file watching, conflict resolution, and retry queues — your adapter just talks to the database.

import type { FileSyncAdapter, FileRecord, FileChange, Unsubscribe }
  from "@agent-native/core/adapters/sync";

class MyAdapter implements FileSyncAdapter {
  async query(appId: string, ownerId: string) { /* return all records */ }
  async get(id: string) { /* return one record or null */ }
  async set(id: string, record: Partial<FileRecord>) { /* upsert */ }
  async delete(id: string) { /* remove */ }
  subscribe(appId: string, ownerId: string,
    onChange: (changes: FileChange[]) => void,
    onError: (error: any) => void): Unsubscribe { /* listen for changes */ }
}

Pass your adapter to FileSync and call initFileSync() — the engine does the rest.