ローカルファーストのAIノートアプリで、同期が一番難しい理由
木村 優志

Papillonは、ローカルのMarkdownファイルを中心に扱うAIノートアプリとして開発しています。
ノートがローカルにあるなら、同期はファイルをクラウドへアップロードし、別の端末へダウンロードするだけに見えるかもしれません。しかし実際には、同期はこのアプリで最も難しい部分の一つです。
端末Aでノートを編集している間に、端末Bでも同じノートを編集する。AIが会話の結果をノートへ書き込む。画像を追加する。片方の端末で削除する。同期中も、利用者はエディタを開いたまま入力を続けている。
この状況で大切なのは、「同期が完了した」と表示することではありません。利用者やAIが加えた編集を、黙って失わせないことです。
Papillonは、ローカルのノートをAIと育てるためのワークスペース
Papillonは、ObsidianライクにローカルのMarkdownファイルを扱うワークスペースです。利用者のノートを特定のクラウドサービスや独自形式へ閉じ込めず、ローカルのMarkdownファイルとして保管します。
Markdownであれば、Papillon以外のエディタでも開けます。フォルダー構成も、日次ノートも、画像も、利用者が自分で確認・整理できます。アプリを使わないとノートを読めない状態にはしたくありません。
すでにObsidianを使っている場合は、既存の保管庫フォルダーを変換せずに、そのままPapillonで開けます。これまで書きためたMarkdownノートとフォルダー構成を維持したまま、AIとのチャットやノート操作を加えられるようにしています。
そのうえで、Papillonはノートを書くためのエディタ、フォルダーやリンクをたどるためのワークスペース、AIへ相談するためのチャットを一つにまとめます。
AIとの対話も、返答を読むだけで終わらせません。会話で決まったこと、調査結果、日々の記録、タスクなどを、必要に応じてノートへ残せるようにします。開いているノートへ追記するのか、日次ノートへ残すのか、既存の関連ノートを更新するのか。AIがノートを扱うからこそ、利用者自身が普段使っているファイル構造を壊さないことが重要になります。
また、ノートは一つの端末だけで使うとは限りません。複数端末で同期し、必要に応じてエンドツーエンド暗号化も使えるようにしています。
Papillonが目指しているのは、AIに情報を預けることではありません。利用者が持つノートを起点に、AIが整理・記録・検索を手伝いながら、知識を自分の手元で育てられるワークスペースです。
同期は、ファイルコピーではない
ファイルを一方向にコピーするだけなら、同期は比較的単純です。
しかし、ローカルファーストのアプリでは、複数の端末がそれぞれ編集の起点になります。クラウドは唯一の正解を持つ場所ではなく、端末間で変更を交換するための場所です。
そのため、同じノートについて少なくとも次の状態を区別する必要があります。
ローカルだけが変わった
→ アップロードする
リモートだけが変わった
→ ダウンロードする
ローカルとリモートの両方が変わった
→ 比較し、マージまたは競合として扱う 「新しい方で上書きする」という方針は簡単ですが、もう片方で書いた内容を失います。ノートアプリでは、数行の追記でも失われれば信頼を大きく損ないます。
AIが書くと、同期の条件がさらに増える
AIノートアプリでは、人がエディタで入力する以外にも、ノートが変わります。
たとえば、AIが会話から決定事項を抽出して日次ノートへ追記する。調査結果を既存ノートの該当セクションへ書き込む。タスクを追加する。こうした操作は、利用者が「保存」を押すタイミングと一致しません。
AIによる書き込みを特別なものとして扱うと、同期処理は複雑になります。Papillonでは、AIの変更も人による編集と同じく、通常のローカル変更として扱う方針にしています。
重要なのは、誰が変更したかではなく、どのファイルが、最後に同期した状態からどう変わったかです。
この扱いにすると、AIの書き込みも人の編集も、同じ競合検出・マージ・履歴の仕組みに乗せられます。
マージには、共通の出発点が必要になる
端末Aと端末Bのノートが異なっていると分かっても、どちらを残せばよいかは、それだけでは判断できません。
必要なのは、両方が編集を始める前の共通の状態です。
最後に同期したノート
├─ 端末Aでの編集
└─ 端末Bでの編集 この三つを比べるのが、3-way mergeです。
端末Aが文書の前半を編集し、端末Bが後半へ追記しただけなら、多くの場合は両方の変更を一つのノートへ統合できます。一方、同じ行や同じ見出しを別々に書き換えていれば、自動的に正解を選ぶことはできません。
その場合は、競合があったことを明示し、利用者が確認できる状態で残す必要があります。同期のために、もっともらしい方を勝手に選ぶべきではありません。
Markdownはマージしやすいが、画像は違う
Markdownのようなテキストファイルは、行や見出しの単位で差分を比較できます。そのため、変更箇所が重なっていなければ、自動で統合できる可能性があります。
しかし、添付画像やPDFなどのバイナリファイルには同じ方法を使えません。
二つの端末が同名の画像を別々に更新したとき、内容を行単位で混ぜることはできません。どちらかを上書きすれば、もう片方が失われます。
こうした場合は、元のファイルを黙って置き換えるのではなく、競合したリモート版を別名で残す方が安全です。利用者は二つを見比べ、どちらを採用するか決められます。
「自動マージできない」ことは失敗ではありません。編集を失わせず、判断を利用者へ返すための設計です。
開いているノートは、同期中にも変わり続ける
もう一つ難しいのは、同期の最中に利用者が同じノートを編集している場合です。
同期を始めた時点の内容を基準にリモートと統合しても、その間にエディタ上で新しい入力が加わっているかもしれません。同期結果でエディタを丸ごと置き換えれば、利用者がいま入力した文章を失う可能性があります。
そのため、同期開始時の内容を基準として持ち、リモートから取得した内容と、エディタで続けられた編集をあらためて3-way mergeする必要があります。
同期はバックグラウンド処理ですが、利用者の編集を守るという点では、エディタの一部です。
E2EEでは、クラウドは内容を判断できない
Papillonでは、ノート保管庫をエンドツーエンドで暗号化する選択肢も扱っています。
E2EEを有効にすると、クラウドに保存されるのは暗号化されたデータです。クラウド側はノートの内容を読めないため、内容を理解してマージしたり、競合を解決したりできません。
代わりに、端末側で復号して比較・マージを行います。クラウド側では、オブジェクトのバージョンを示すETagなどを使い、「自分が見ていた版の後に別の変更が入っていないか」を確認します。
これは実装を複雑にしますが、ノートの内容をサーバーへ預けずに同期するためには必要な境界です。プライバシーは、暗号化アルゴリズムだけでなく、どこで比較・マージ・競合解決を行うかという設計にも表れます。
同期で守るべきものは、最新状態ではなく編集の経緯
同期機能を作るとき、「全端末を最新状態にそろえる」ことに目が向きます。
もちろんそれは重要です。しかし、より大切なのは、各端末で行われた編集を消さずに扱えることです。
- ローカルだけの変更を確実にアップロードする
- リモートだけの変更を安全に取り込む
- 両方が変わったときは、共通の出発点から統合する
- 自動で統合できない変更は、競合として残す
- 同期中に続いたエディタ上の入力も失わせない
ローカルファーストのAIノートアプリでは、AIがノートを書くほど、この設計が重要になります。
AIが便利に書き込めることと、利用者が安心して自分のノートを預けられることは、別の問題です。後者を支えるのが、目立たない同期とマージの設計だと考えています。



