複数のsandboxを同時に立ち上げて開発していると、それぞれの作業環境が独立しているのは便利な一方で、別のsandboxが何をしているのか分からなくなる。

例えば、一つのsandboxで調査して設計方針を決め、別のsandboxで実装を始めるとする。新しい側には前の会話がなく、なぜその方針になったかも、何が未解決なのかも分からない。結果として同じ調査をやり直したり、すでに決めたことを再検討したりする。別のworkerが編集中のファイルに気づかず、作業がぶつかることもある。

会話履歴はsandbox間で共有されないので、毎回人間が背景を説明し直すことになる。かといって、会話を丸ごとコピーするのは量が多く、次のworkerに必要な情報を探しにくい。

そこで、共有する情報を会話ではなく、開発コンテキストに限定することにした。決定事項、現在の作業、変更したファイル、次の人への注意点が分かれば、次のsandboxは同じ前提から始められる。これを実現するCLIツールとして ctx-sync を作った。

アイデア:会話ではなく、作業に必要な事実を共有する

最初に考えたのは、別のsandboxへ会話をそのまま渡すことではない。会話には試行錯誤や一時的な仮説、デバッグログなどが大量に含まれる。これを全部同期しても、新しいworkerが読むには重いし、共有する必要のない情報まで混ざる。

共有情報を次の三層に分けた。

共有する情報
Project目的、現在の構成、採用済みの設計判断
Coordinationworkerの状態、作業内容、変更ファイル、引き継ぎ事項
Local会話、推論、試しただけの案、一時的なデバッグメモ

共有するのは「なぜこの設計に決まったか」「今どこまで終わっているか」「次の人が注意すること」。推論過程や会話ログそのものは各sandboxのローカルに置き、他のworkerが行動するのに必要な事実だけを残す。

実装:GitHub GistをGitリポジトリとして使う

この共有状態をどこへ置くか。プロジェクトのGitリポジトリへ全部入れると、workerごとの一時的な進捗まで通常のソース管理へ混ざる。独自サーバーを立てると、認証や運用が必要になる。

そこで共有先にはGitHub Gistを使うことにした。GistはGitリポジトリとしてcloneできるので、特別なサーバーを用意せず、既存のGit認証を使って複数のマシンやsandboxから同期できる。

ctx-syncはRust製のctx-sync CLIで、プロジェクト側にはGistを特定する.ctx-sync.tomlを置く。ローカルのcloneやworker identityはプロジェクトディレクトリの外へ保存し、既定ではmacOSのApplication SupportまたはLinuxの~/.local/state/ctx-syncを使う。

Gist側は次のようなファイル構成になっている。

00-meta.json
10-project.md
20-architecture.md
30-decision-<date>-<number>-<slug>.md
40-worker-<short-id>.md

設計判断は一つずつ別ファイルにして、古い判断を書き換えるのではなく、新しい判断を追加して置き換える。各workerは自分の40-worker-*.mdだけを更新する。この分離により、複数のworkerが同時に進めていても、別のworkerの作業状態を上書きしにくくしている。

作業開始と引き継ぎ

新しいsandboxで作業を始めるときは、ctx-sync agent startを実行する。

ctx-sync agent start
    -> 必要ならこのcheckoutをGistへattach
    -> worker identityを登録
    -> 最新の共有contextをpull
    -> architecture、決定事項、作業中worker、注意点を表示

新規プロジェクトなら、最初にGistを作ってworkerを登録する。

ctx-sync init --project example --create-gist --secret
ctx-sync register sandbox-a
ctx-sync agent start

生成された.ctx-sync.tomlをプロジェクトへcommitしておけば、別のworktreeやsandboxから同じGistを見つけられる。参加する側はfresh checkoutでctx-sync agent start --name sandbox-bを実行すればよい。

作業を終えるときは、何を変更したか、重要なファイル、未解決の問題などをworkerのhandoffへ記録する。

ctx-sync agent finish --summary "設定読み込みを追加" \
  --attention "CLI互換性のテストが未実施"

次のsandboxがagent startすると、最新contextをpullしてからonboarding情報を受け取る。各workerが個別に長い引き継ぎ文を書いて会話へ貼る代わりに、一定の形式で共有状態を更新する流れ。

既存プロジェクトへ追加する場合は、ctx-sync bootstrapでリポジトリから共有候補を集められる。--applyを付けてもすぐにGistへ同期するのではなく、まず共有文書へローカル出力する。中身を人間が確認してからctx-sync syncで公開する。

同時更新は自動で潰さない

複数sandboxが同じGistを更新する以上、競合は起こりうる。

ctx-syncの同期はGitのfetch、rebase、pushを行う。別workerが先にpushしていた場合はrebaseしてからpushを試す。fetchからpushまでの間に再び更新されてnon-fast-forwardになったら、最大3回までやり直す。

同じファイルを別々に編集してrebase conflictになった場合は、自動でどちらかを採用しない。rebaseをabortして競合したファイルを表示し、人間が解決できる状態で停止する。共有contextは後続の作業判断に使われるので、内容を黙って上書きするより、競合を見える形にする方を選んだ。

各workerが別々のファイルを所有するのは、競合自体をなくす仕組みではない。ただ、独立して更新できる単位を分け、競合が起きた場合にもどこが衝突したか分かりやすくする。

何でも共有しない

Gistをsecretとして作っても、URLを知っている人は読める。認証情報やAPI tokenを保管する場所ではない。

handoffdecision addでは、sk-ghp_github_pat_や秘密鍵ヘッダーなど、明らかなcredentialらしき文字列を警告する。ただし、これはパターン検出であって秘密情報を完全に見つける仕組みではない。共有前に内容を確認する必要がある。

そのため、ctx-syncが同期するのは作業に必要な短い事実だけにする。デバッグログや会話を丸ごと貼るのではなく、「このエラーは再現済み」「このファイルは別workerが変更中」「この設計判断は採用済み」のように、次の作業で使える形にまとめる。

オーケストレーターではない

ctx-syncはworkerを起動したり、タスクを割り当てたり、sandboxの実行順を決めたりするツールではない。全workerの会話を集めて監視する仕組みでもない。

やることは、共有contextをGitで同期し、workerごとの状態を読み書きすること。Codex向けには、作業開始時に共有状態を読み、設計判断を残し、終了時にhandoffを書くためのSkillも用意した。プロジェクトのAGENTS.mdへ共有ルールを追記するコマンドもあるが、既存の指示を置き換えず、追加部分を確認してからcommitできる。

複数のsandboxを同時に動かすと、会話履歴を共有することより、各workerが同じ前提で作業を始められることの方が重要になる。会話そのものを同期する案ではなく、作業に必要な事実を選んで残す案にして、GistとGitで同期する形にした。ctx-syncはそのための小さな共有ノートとして作った。まずは決定事項と引き継ぎを同期するところから使ってみる。