多设备同步与对齐
完整规格见 00-product-spec.md。
| 场景 | 挑战 |
|---|---|
| 工作 Mac + 生活 Mac | 知识要部分共享,观察数据要隔离;生活机偶尔用于办公 |
| 换新电脑 | 知识需要带走,PG 索引重建,观察从零开始 |
| 多机重度用户(如金融) | 3-10 台设备,严格分区、审计、冲突可控 |
核心矛盾:YZOS 是 local-first,但用户的工作记忆天然跨设备。
1. 同步知识,不同步一切
Section titled “1. 同步知识,不同步一切”数据分四层,每层的同步策略不同:
| 层级 | 内容 | 是否跨设备同步 | 理由 |
|---|---|---|---|
| L1 知识真源 | OKF(memory / knowledge / entities) | 同步 | 体积小、git 友好,是供料层的核心 |
| L2 人工覆盖 | entity_overrides、声纹、非密钥配置 | 同步 | 用户的修正应全局生效 |
| L3 索引 | PostgreSQL | 不同步,各设备自建 | 可从 L1 重建,避免同步 PG 二进制文件 |
| L4 原始观察 | 截图、语音、AI 会话快照 | 默认不同步 | 体积大、隐私敏感,且与设备语境绑定 |
上层 AI 通过 MCP 查询的是 L1+L3。L1 对齐后,各设备用自己的定时 reconcile 重建 L3 即可。
2. OKF 是可移植单元
Section titled “2. OKF 是可移植单元”每个 OKF 文件都是独立的 Markdown 文件加 frontmatter,天然适合:
git push/pull- Syncthing / iCloud 文件夹同步
- 用
yzos export --bundle打包迁移
任何一台设备上的 PG 索引,都只是同一套 OKF 文件的本地副本。
3. 观察数据携带设备语境
Section titled “3. 观察数据携带设备语境”结晶进知识库的条目必须记录来源设备,合并时才能判断冲突:
# entity frontmatter 扩展device_id: macbook-pro-workdevice_label: Work MacBookcontext: work # work | personal | travelfirst_seen_device: macbook-pro-workdevices_seen: [macbook-pro-work, macbook-air-life]4. 用户自选同步后端,YZOS 不绑定云服务
Section titled “4. 用户自选同步后端,YZOS 不绑定云服务”YZOS 不提供、不运营同步云服务。用户用自己已有的基础设施,YZOS 只负责:
- 定义同步范围(哪些 OKF 目录参与同步)
- 定义对齐流程(pull -> 合并 -> reconcile -> push)
- 按配置调用标准工具或监听已同步的目录
两种模式:
| 模式 | 说明 | 典型后端 |
|---|---|---|
| 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"]
同步后端详解
Section titled “同步后端详解”所有后端共用同一套顶层 sync.toml 配置(scope、conflict、interval),只有 [sync.backend.*] 段不同。
Passive - 文件夹监听(iCloud / 已有云同步)
Section titled “Passive - 文件夹监听(iCloud / 已有云同步)”用户把 ~/.yzos/contexts/shared/(或整个 sync bundle)放进已由系统或第三方同步的目录:
[sync]enabled = truemode = "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 习惯的用户。
Active - rsync over SSH
Section titled “Active - rsync over SSH”用户配置自己的 SSH 目标(家里的 NAS、云主机、办公室跳板机):
[sync]enabled = truemode = "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 执行:
# pullrsync -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)
Active - WebDAV
Section titled “Active - WebDAV”自托管或第三方 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 / digesttls_verify = truetimeout_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"委托给外部工具
Active - S3 兼容对象存储
Section titled “Active - S3 兼容对象存储”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:9000region = "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/流程:
- pull:ListObjects -> 对比 manifest -> 下载 hash 不一致的文件
- merge:应用本地冲突策略
- push:上传变更文件 -> 更新 manifest(乐观锁 CAS)
适合:多机 Hub、金融用户自建 MinIO、无固定公网 IP 的场景。
Active - Git
Section titled “Active - Git”[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 均可
Passive / 已有配置 - Syncthing
Section titled “Passive / 已有配置 - Syncthing”用户自行配置 Syncthing 同步文件夹,YZOS 用 passive 模式监听:
[sync]mode = "passive"watch_dir = "~/Sync/yzos-shared"YZOS 不与 Syncthing API 集成(避免硬依赖),而是直接复用用户已有的 P2P 能力。
可选委托 - rclone
Section titled “可选委托 - rclone”高级用户可设 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 “场景一:工作机 + 生活机(偶尔交叉办公)”推荐方案:双 Context + 部分共享
Section titled “推荐方案:双 Context + 部分共享”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 (同步配置)生活机偶尔办公
Section titled “生活机偶尔办公”生活机检测到“办公模式”时(手动切换、连接公司 VPN、打开工作 repo):
context临时切换为work(或work@personal-device)- 新结晶的条目写入
shared/或work/,标记device_id=macbook-air-life - 同步到工作机后,工作机的 PG 索引会包含“生活机上产生的办公片段”
- 生活类 app(私聊、音乐)仍只写入
personal/,不会进入shared/
| 规则 | 说明 |
|---|---|
personal/ 默认不同步到工作机 |
需要用户显式 opt-in |
work/ 可以同步到生活机 |
方便在家查项目知识 |
| 聊天观察按 context 过滤 | 生活机的聊天 app 不会与工作机的聊天 app 混在一起 |
| PII 脱敏发生在入库之前 | 同步传输的始终是已脱敏的 OKF |
场景二:换新电脑
Section titled “场景二:换新电脑”迁移流程(5 步)
Section titled “迁移流程(5 步)”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"]
migrate-bundle 内容
Section titled “migrate-bundle 内容”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.models、paths.database、paths.observations 的内容,新机根据 config.yaml 的 paths 本地创建目录,用 yzos models fetch 拉取权重,PG 从空库开始通过 reconcile 重建索引。
新机的 PG 从空库启动,定时的 reconcile 和 embedding_refresh 会从全量 OKF 建立索引。耗时取决于知识量(约 1 万条 OKF 需要几分钟)。
- 继续保留:旧机的观察继续进行,作为一个
device_id不同的第二节点 - 退役:
yzos device retire标记设备离线,实体 metadata 保留历史devices_seen记录
场景三:多机重度用户(如金融)
Section titled “场景三:多机重度用户(如金融)”拓扑:Hub + Spoke(推荐)
Section titled “拓扑:Hub + Spoke(推荐)”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
实体 ID 必须全局唯一
Section titled “实体 ID 必须全局唯一”避免两台机器各自发现“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 记录每台设备的每次修改,冲突时可以追溯。
审计(金融场景)
Section titled “审计(金融场景)”-- entity_revisions 扩展device_id TEXT NOT NULL,sync_gen BIGINT NOT NULL, -- 同步代数,防止回放- 所有跨设备写入都带
device_id和sync_gen - Hub 可选维护 append-only 日志(谁在何时推送了什么)
- 原始观察(L4)永不离开产生它的机器,满足合规要求
同步协议(YZOS 内置层)
Section titled “同步协议(YZOS 内置层)”不论后端是 Git 还是 Syncthing,YZOS 统一用同一套抽象:
device.json
Section titled “device.json”{ "device_id": "macbook-pro-work-7f3a", "device_label": "Work MacBook Pro", "context": "work", "created_at": "2026-01-15T00:00:00Z", "public_key": "..."}sync.toml(完整示例)
Section titled “sync.toml(完整示例)”[sync]enabled = truemode = "active" # passive | activebackend = "rsync" # passive | rsync | webdav | s3 | git | rcloneinterval_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 | manualdevice_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
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 passiveyzos sync config init # 交互式生成 sync.toml
yzos export migrate-bundle -o ~/Desktop/yzos-migrate.tar.zstyzos import migrate-bundle ~/Desktop/yzos-migrate.tar.zstyzos device listyzos device retire <device_id>sync_state(本机状态)
Section titled “sync_state(本机状态)”~/.yzos/.sync_state/ -> manifest.json (本机 scope 内所有文件的 path + blake3 hash + mtime) -> last_pull.json -> last_push.json -> conflicts/ (未解决冲突的副本) -> shared/entities/project/foo.md.device-B各场景推荐配置
Section titled “各场景推荐配置”| 场景 | 拓扑 | 推荐后端 | 模式 |
|---|---|---|---|
| 工作 + 生活,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 的关系
Section titled “与上层 AI 的关系”多设备对齐后,上层 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 的首要目标。
Rust 模块规划
Section titled “Rust 模块规划”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)