跳转到内容

多设备同步与对齐

完整规格见 00-product-spec.md

场景 挑战
工作 Mac + 生活 Mac 知识要部分共享,观察数据要隔离;生活机偶尔用于办公
换新电脑 知识需要带走,PG 索引重建,观察从零开始
多机重度用户(如金融) 3-10 台设备,严格分区、审计、冲突可控

核心矛盾:YZOS 是 local-first,但用户的工作记忆天然跨设备。

数据分四层,每层的同步策略不同:

层级 内容 是否跨设备同步 理由
L1 知识真源 OKF(memory / knowledge / entities) 同步 体积小、git 友好,是供料层的核心
L2 人工覆盖 entity_overrides、声纹、非密钥配置 同步 用户的修正应全局生效
L3 索引 PostgreSQL 不同步,各设备自建 可从 L1 重建,避免同步 PG 二进制文件
L4 原始观察 截图、语音、AI 会话快照 默认不同步 体积大、隐私敏感,且与设备语境绑定

上层 AI 通过 MCP 查询的是 L1+L3。L1 对齐后,各设备用自己的定时 reconcile 重建 L3 即可。

每个 OKF 文件都是独立的 Markdown 文件加 frontmatter,天然适合:

  • git push/pull
  • Syncthing / iCloud 文件夹同步
  • yzos export --bundle 打包迁移

任何一台设备上的 PG 索引,都只是同一套 OKF 文件的本地副本。

结晶进知识库的条目必须记录来源设备,合并时才能判断冲突:

# entity frontmatter 扩展
device_id: macbook-pro-work
device_label: Work MacBook
context: work # work | personal | travel
first_seen_device: macbook-pro-work
devices_seen: [macbook-pro-work, macbook-air-life]

4. 用户自选同步后端,YZOS 不绑定云服务

Section titled “4. 用户自选同步后端,YZOS 不绑定云服务”

YZOS 不提供、不运营同步云服务。用户用自己已有的基础设施,YZOS 只负责:

  1. 定义同步范围(哪些 OKF 目录参与同步)
  2. 定义对齐流程(pull -> 合并 -> reconcile -> push)
  3. 按配置调用标准工具监听已同步的目录

两种模式:

模式 说明 典型后端
Passive(被动) 用户已用 iCloud / Syncthing 等同步某个文件夹,YZOS 只监听该目录的变更并 reconcile iCloud Drive、Syncthing、Dropbox
Active(主动) YZOS daemon 按调度执行 pull/push rsync+SSH、WebDAV、S3、Git
flowchart LR
    ORCH["YZOS Sync Orchestrator<br/>scope filter, merge, reconcile"]
    ORCH -->|passive watch| ICLOUD["iCloud folder"]
    ORCH -->|rsync over SSH| NAS["self-hosted NAS / SSH"]
    ORCH -->|webdav| DAV["Nextcloud / Office 365 / WebDAV"]
    ORCH -->|s3 object| S3["MinIO / AWS S3"]
    ORCH -->|git repo| GIT["GitHub / private Git"]
    ORCH -->|existing P2P| ST["user's Syncthing setup"]

所有后端共用同一套顶层 sync.toml 配置(scope、conflict、interval),只有 [sync.backend.*] 段不同。

Passive - 文件夹监听(iCloud / 已有云同步)

Section titled “Passive - 文件夹监听(iCloud / 已有云同步)”

用户把 ~/.yzos/contexts/shared/(或整个 sync bundle)放进已由系统或第三方同步的目录

[sync]
enabled = true
mode = "passive"
watch_dir = "~/Library/Mobile Documents/com~apple~CloudDocs/yzos-sync"
# 或 Syncthing 已同步的目录,例如 ~/Sync/yzos/
[sync.scopes]
include = ["shared/**", "work/entities/**"]
exclude = ["personal/**", "activity/**"]

YZOS 的行为:

  • FSEvents 监听 watch_dir 内 OKF 文件的变更
  • 外部同步完成后触发 reconcile 和增量 index rebuild
  • 不发起任何网络请求,完全依赖用户自己的 iCloud / Syncthing / Dropbox

适合:两台 Mac、零配置、已有 iCloud 习惯的用户。

用户配置自己的 SSH 目标(家里的 NAS、云主机、办公室跳板机):

[sync]
enabled = true
mode = "active"
backend = "rsync"
interval_minutes = 15
[sync.backend.rsync]
remote = "user@nas.local:/volume1/yzos-sync"
# 或 remote = "user@203.0.113.5:/home/user/yzos-sync"
ssh_config = "~/.ssh/config" # 可选,使用 Host 别名
ssh_identity = "~/.ssh/id_ed25519_yzos"
rsync_args = ["-avz", "--delete-delay", "--exclude", ".sync_state"]
bandwidth_limit = "5m" # 可选
[sync.scopes]
push = ["shared/**", "work/**"]
pull = ["shared/**", "work/**"]

YZOS 执行:

Terminal window
# pull
rsync -avz user@nas:/volume1/yzos-sync/shared/ ~/.yzos/contexts/shared/
# push(仅同步 scope 内、且 mtime 新于 sync_state 的文件)
rsync -avz ~/.yzos/contexts/shared/ user@nas:/volume1/yzos-sync/shared/
  • 密钥留在用户自己的 ~/.ssh/,YZOS 不存密码(或用 Keychain 存 passphrase)
  • 支持 ProxyJump(例如办公室跳到家里的 NAS)
  • 远端维护 .sync_state/manifest.json(文件 path + hash + device_id + mtime)

自托管或第三方 WebDAV(Nextcloud、坚果云、Office 365 等):

[sync]
mode = "active"
backend = "webdav"
interval_minutes = 30
[sync.backend.webdav]
url = "https://cloud.example.com/remote.php/dav/files/alex/yzos-sync"
# 凭证存 Keychain,不写进 sync.toml 明文
auth = "keychain:yzos-webdav" # 或 basic / digest
tls_verify = true
timeout_secs = 120
[sync.scopes]
push = ["shared/**"]
pull = ["shared/**", "work/knowledge/**"]

实现:Rust reqwest 加 WebDAV PROPFIND/GET/PUT/MKCOL,或使用 dav-server 客户端库。

  • 增量同步:对比远端 ETag / Last-Modified 与本地 sync_state
  • 大文件:服务端支持时分块上传
  • 不依赖 rclone,但用户也可以设 backend = "rclone" 委托给外部工具

AWS S3、MinIO、Cloudflare R2,或其他厂商的 S3 兼容模式:

[sync]
mode = "active"
backend = "s3"
interval_minutes = 60
[sync.backend.s3]
endpoint = "https://s3.amazonaws.com" # MinIO: https://minio.home:9000
region = "ap-southeast-1"
bucket = "yzos-sync"
prefix = "devices/" # 对象键前缀
# 凭证:Keychain 存 AWS_ACCESS_KEY_ID / SECRET,或 IAM role(远期)
[sync.scopes]
push = ["shared/**", "work/entities/**"]
pull = ["shared/**"]

对象键布局:

s3://yzos-sync/
-> manifest.json (全局文件清单:path、hash、device_id、mtime)
-> shared/knowledge/projects/example-project.md
-> shared/entities/person/...
-> devices/macbook-pro-work/ (可选:每设备 staging 区,合并后进入 shared)
-> .staging/

流程:

  1. pull:ListObjects -> 对比 manifest -> 下载 hash 不一致的文件
  2. merge:应用本地冲突策略
  3. push:上传变更文件 -> 更新 manifest(乐观锁 CAS)

适合:多机 Hub、金融用户自建 MinIO、无固定公网 IP 的场景。

[sync]
mode = "active"
backend = "git"
interval_minutes = 60
[sync.backend.git]
repo = "git@github.com:alex/yzos-knowledge.git"
branch = "main"
# 仅同步 OKF 文本,.gitignore 排除 activity/ voice/ pg/
[sync.scopes]
paths = ["contexts/shared", "contexts/work/knowledge", "contexts/work/entities"]

流程:git pull --rebase -> reconcile -> git add scope 内的变更 -> git commit(消息带 device_id)-> git push

  • OKF Markdown 天然适合 Git
  • 冲突:.md 文件级 merge conflict 会标记为 needs_review
  • 私有 GitHub、Gitea、自建 GitLab 均可

用户自行配置 Syncthing 同步文件夹,YZOS 用 passive 模式监听:

[sync]
mode = "passive"
watch_dir = "~/Sync/yzos-shared"

YZOS 不与 Syncthing API 集成(避免硬依赖),而是直接复用用户已有的 P2P 能力。

高级用户可设 backend = "rclone",YZOS 只生成 rclone 配置并调用它:

[sync.backend.rclone]
remote = "nextcloud:yzos-sync" # 用户 rclone.conf 里已配置的 remote 名

一份 rclone.conf 可覆盖 WebDAV、S3、Google Drive 等数十种后端,YZOS 不重复实现。


凭证类型 存储位置 禁止
SSH 私钥 用户自己的 ~/.ssh/,YZOS 只引用路径 写入 sync.toml
WebDAV 密码 macOS Keychain yzos-webdav 明文存储
S3 Access Key Keychain yzos-s3 明文存储
Git 系统 ssh-agent / credential helper 嵌入仓库

同步内容默认不加密(依赖传输层 TLS / SSH)。用户若需要端到端加密,可自行在 scope 外包一层 gpg,或使用 Syncthing 的设备加密,YZOS 不强制。


场景一:工作机 + 生活机(偶尔交叉办公)

Section titled “场景一:工作机 + 生活机(偶尔交叉办公)”
flowchart TD
    subgraph WORK["Work MacBook - context work"]
        WA["work apps"] --> WC["crystallize into work/"]
    end
    subgraph PERSONAL["Personal MacBook Air - context personal"]
        PA["personal apps"] --> PC["crystallize into personal/"]
    end
    WC --> SHARED
    PC --> SHARED
    subgraph SHARED["shared/ - shared knowledge layer"]
        PROJ["projects/"]
        MEM["common memory"]
    end

目录结构:

~/.yzos/
-> contexts/
-> work/ (工作机主写入)
-> knowledge/
-> memory/
-> entities/
-> personal/ (生活机主写入)
-> ...
-> shared/ (双机同步)
-> knowledge/projects/ (例如 example-project,两边都可能碰到)
-> memory/ (跨场景记忆)
-> entities/ (已确认的人物 / 项目 / 任务)
-> device.json (本机身份)
-> sync.toml (同步配置)

生活机检测到“办公模式”时(手动切换、连接公司 VPN、打开工作 repo):

  1. context 临时切换为 work(或 work@personal-device
  2. 新结晶的条目写入 shared/work/,标记 device_id=macbook-air-life
  3. 同步到工作机后,工作机的 PG 索引会包含“生活机上产生的办公片段”
  4. 生活类 app(私聊、音乐)仍只写入 personal/,不会进入 shared/
规则 说明
personal/ 默认不同步到工作机 需要用户显式 opt-in
work/ 可以同步到生活机 方便在家查项目知识
聊天观察按 context 过滤 生活机的聊天 app 不会与工作机的聊天 app 混在一起
PII 脱敏发生在入库之前 同步传输的始终是已脱敏的 OKF

flowchart TD
    subgraph OLD["Old machine"]
        EXP["yzos export migrate-bundle<br/>OKF + overrides + sync.toml + device.json"]
    end
    EXP --> BUNDLE["yzos-migrate.tar.zst<br/>excludes PG / screenshots / voice / models"]
    BUNDLE --> IMP
    subgraph NEW["New machine"]
        IMP["yzos import migrate-bundle"] --> START["yzos daemon start<br/>PG init + reconcile"]
        START --> REBUILD["yzos index rebuild from OKF"]
        REBUILD --> MODELS["paths.models fetched locally"]
        MODELS --> MCP["configure MCP"]
    end
    MCP --> AI["upper-layer AI keeps working"]
yzos-migrate.tar.zst
-> contexts/ (L1 OKF 全量:default / work / personal / shared)
-> default/
-> knowledge/
-> memory/
-> entities/
-> shared/
-> entity_overrides/ (L2)
-> voiceprints/ (可选)
-> config.yaml (非密钥;含 paths 模板,新机可据此改到 models/observations 的外置路径)
-> sync.toml
-> device.json
-> MANIFEST.json (版本、逐文件 hash 清单、okf_version)

不包含 paths.modelspaths.databasepaths.observations 的内容,新机根据 config.yamlpaths 本地创建目录,用 yzos models fetch 拉取权重,PG 从空库开始通过 reconcile 重建索引。

新机的 PG 从空库启动,定时的 reconcileembedding_refresh 会从全量 OKF 建立索引。耗时取决于知识量(约 1 万条 OKF 需要几分钟)。

  • 继续保留:旧机的观察继续进行,作为一个 device_id 不同的第二节点
  • 退役yzos device retire 标记设备离线,实体 metadata 保留历史 devices_seen 记录

场景三:多机重度用户(如金融)

Section titled “场景三:多机重度用户(如金融)”
flowchart TD
    HUB["Sync Hub - self-hosted, Git or S3"]
    HUB <-->|writes only to desk-a/| DA["Trading desk A"]
    HUB <-->|writes only to desk-b/| DB["Trading desk B"]
    HUB <-->|reads and writes shared/| OFF["Office"]

每台设备:

  • 独立观察(L4 不跨机)
  • 分区写入desk-a/desk-b/shared/
  • 定时 pull/push OKF 到 Hub
  • 本地 PG 索引,覆盖本机 L1 加已同步进来的 L1

避免两台机器各自发现“Example Project”却生成两个不同的 ID:

entity_id = {kind}-{stable_key}
stable_key 生成规则:
person: hash(platform + platform_id) 或 manual_uuid
project: hash(normalized_repo_path) 如 project-sha256(/path/to/repo)
task: hash(project_id + task_fingerprint) 或 ULID(需要同步协议)

两台机器对同一个 repo 路径会生成相同的 project ID,同步时自动合并,不会产生重复。

OKF 文件级冲突(较少见,因为按实体分文件):

策略 适用场景
按文件最新 mtime 默认,最简单
按 device 优先级 金融场景:交易台优先于笔记本
三路合并 同一实体的 summary 字段:保留双方 revision,由定时任务用模型合成
人工仲裁 needs_review=true -> 在 Tauri UI 中显示为冲突

entity_revisions 记录每台设备的每次修改,冲突时可以追溯。

-- entity_revisions 扩展
device_id TEXT NOT NULL,
sync_gen BIGINT NOT NULL, -- 同步代数,防止回放
  • 所有跨设备写入都带 device_idsync_gen
  • Hub 可选维护 append-only 日志(谁在何时推送了什么)
  • 原始观察(L4)永不离开产生它的机器,满足合规要求

不论后端是 Git 还是 Syncthing,YZOS 统一用同一套抽象:

{
"device_id": "macbook-pro-work-7f3a",
"device_label": "Work MacBook Pro",
"context": "work",
"created_at": "2026-01-15T00:00:00Z",
"public_key": "..."
}
[sync]
enabled = true
mode = "active" # passive | active
backend = "rsync" # passive | rsync | webdav | s3 | git | rclone
interval_minutes = 15
[sync.scopes]
push = ["shared/**", "work/knowledge/**", "work/entities/**"]
pull = ["shared/**", "work/**"]
exclude = ["personal/**", "activity/**", "voice/**", "pg/**"]
[sync.conflict]
strategy = "mtime" # mtime | priority | merge | manual
device_priority = ["macbook-pro-work", "macbook-air-life"]
# 按 backend 选一个:
[sync.backend.rsync]
remote = "user@nas.local:/volume1/yzos-sync"
# [sync.backend.webdav]
# url = "https://cloud.example.com/dav/yzos-sync"
# auth = "keychain:yzos-webdav"
# [sync.backend.s3]
# endpoint = "https://s3.amazonaws.com"
# bucket = "yzos-sync"
# prefix = "shared/"
# auth = "keychain:yzos-s3"
# passive 模式示例:
# [sync]
# mode = "passive"
# watch_dir = "~/Library/Mobile Documents/com~apple~CloudDocs/yzos-sync"
flowchart TD
    P1["1. pull remote OKF changes"] --> P2["2. merge at file level"]
    P2 --> P3["3. yzos reconcile - OKF hashes vs local PG"]
    P3 --> P4["4. yzos index rebuild - changed files only"]
    P4 --> P5["5. entity_merge - shared-ID sightings"]
    P5 --> P6["6. push local OKF changes"]
    P6 --> P7["7. update sync_state - last_sync_at, sync_gen++"]
    P7 -.->|next interval| P1
Terminal window
yzos sync status # 后端类型、上次同步时间、待推送/拉取文件数
yzos sync pull # 按配置的 backend 拉取(rsync/webdav/s3/git)
yzos sync push # 推送本地变更
yzos sync reconcile # 仅做 OKF <-> PG 对账(passive 模式常用)
yzos sync test # 测试连通性(SSH/WebDAV/S3)
yzos sync backend list # 可用后端:rsync webdav s3 git rclone passive
yzos sync config init # 交互式生成 sync.toml
yzos export migrate-bundle -o ~/Desktop/yzos-migrate.tar.zst
yzos import migrate-bundle ~/Desktop/yzos-migrate.tar.zst
yzos device list
yzos device retire <device_id>
~/.yzos/.sync_state/
-> manifest.json (本机 scope 内所有文件的 path + blake3 hash + mtime)
-> last_pull.json
-> last_push.json
-> conflicts/ (未解决冲突的副本)
-> shared/entities/project/foo.md.device-B

场景 拓扑 推荐后端 模式
工作 + 生活,2 台 Mac 双机对等 iCloud,同步进 shared/ passive
家里 NAS + 笔记本 星型 rsync + SSH 到 NAS active
自托管 Nextcloud Hub WebDAV active
多机无公网 IP P2P Syncthing(用户自配)+ YZOS passive passive
金融 / 多交易台 Hub + Spoke S3 / MinIO 或私有 Git 库 active
开发者 任意 Git push 到知识 repo active
换新电脑 一次性 migrate-bundle,或 rsync 拉全量
纯离线单机 关闭同步

推荐的 context 划分:work + personal + shared(见场景一)。


多设备对齐后,上层 AI(Claude Code)在任何一台机器上通过 MCP 查询:

  • 看到的是本机 PG 索引,背后是已同步的 L1 OKF
  • yzos_entity_get 返回的 devices_seen 标明了这条知识来自哪台机器
  • 生活机上的 Claude Code 能查到工作机结晶出的 Knowledge,只要它在同步范围内

YZOS 不同步上层 AI 的对话,那属于各设备自己的 Claude Code 会话目录。YZOS 只同步自己结晶出的知识。


阶段 能力
P0 单机 OKF 真源 + PG 重建 + 预留 device_id
P1 迁移 export/import migrate-bundle
P2 passive watch_dir + FSEvents + reconcile(面向 iCloud / Syncthing 用户)
P3 active rsync+SSH -> WebDAV -> S3 -> Git(按此优先级实现)
P4 多机 稳定实体 ID + manifest 乐观锁 + 冲突 UI
P5 rclone 委托、审计日志、可选端到端加密层

单机阶段预留 sync.toml schema 和 .sync_state/,各后端可以分批上线。passive 模式配合 iCloud 成本最低,建议作为 P2 的首要目标。

yzos-core/src/sync/
-> orchestrator.rs (pull -> merge -> reconcile -> push 流程)
-> scope.rs (push/pull/exclude 的 glob 过滤)
-> manifest.rs (blake3 hash 清单)
-> conflict.rs (mtime / priority / merge)
-> passive.rs (FSEvents 监听)
-> backends/
-> rsync.rs (调用 rsync over SSH)
-> webdav.rs
-> s3.rs (aws-sdk-s3 或 rust-s3)
-> git.rs (调用 git CLI)
-> rclone.rs (可选,调用 rclone)