この記事を書く前にビルドマシンをちらっと見た。5 つの tmux セッションが動いていて、~/.local/state/ai-jobs の下に 372 個のタスクディレクトリ、3.1 GB、load average は 2.90 で二から七の間をずっと漂っている。このマシンは 128 コア、起動してから四日半。数字はサンプリングした一瞬のもので、この段落を書き終えるころにはまた変わっている——それ自体がひとつの説明でもある。

同じ瞬間に手元にあるプロジェクト:Rust + SQL のサイトで統計クエリを直している、Next.js アプリで service worker を退役させている、Bun + Hono のサービスでトンネルの origin を修正している、Prisma + tRPC の monorepo、C# のゲーム私設サーバーで資料フィールドを補っている、C/C++ の SDL3/WASM 移植、Zola/Hexo のドキュメントサイトが二つ。~/Code の下に 64 個のディレクトリ、36 個の git リポジトリ。

これは「たくさんウィンドウを開いた」という類の並列ではない。ウィンドウなら誰でも開ける。難しいのは同時に進めつつ互いを汚染しないことだ。

まず、なぜそれだけの価値があるのか

プロジェクトを一つだけ開いているとき、AI との協働で最も高くつくのは待ち時間だ。全量テストを一回走らせるのに十分、ビルドを待つのに五分、ブラウザ回帰テストが終わるのを待つ。その間、人にできるのはただ待つか、別のものへ切り替えるか——切り替えれば、戻ってきたときにはまたコンテキストを積み直さなければならない。

八つのプロジェクトを並行させる第一の利点はこれだ:待ち時間が他の線で埋まる。A のビルドが走っている間に、B の失敗ログがちょうど返ってくる。C が人による確認を待っている間に、D のテストが赤くなって直す必要が出る。マシン全体のスループットが引き上げられ、人の頭に空きがなくなる。

第二の利点は同じ問題を他の線で再利用できることだ。今日 av-site で「インデックスを作るのはロック取得と同じトランザクションブロック内にいなければならない」と理解したら、AstroDX では自然に「退役する worker の削除順序」を思いつく——どちらも「二つのステップの間に誰かが割り込んではいけない」という話だ。こうしたプロジェクト間の転移の直感は、複数の線を同時に手にしているときしか起きない。

だが利点には前提がある。以下がその前提だ。

ルールは階層化すべきで、コピーしてはいけない

~/.agents/AGENTS.md が唯一の正本で、~/.codex/AGENTS.md、~/.dsh/AGENTS.md はそれへのシンボリックリンク、~/.claude/CLAUDE.md は @ で参照する。ルールを変えるときはこの一份だけを変える——三つに複製すれば必ずドリフトする。

正本にはプロジェクトをまたいで共通のものだけを書く:中国語での対話、Conventional Commits、ローカルはビルドマシンではない、リモートはどのマシンを使うか、30 秒を超えるタスクをどう永続化するか、プロキシはどこか。

プロジェクトレベルの AGENTS.md にはこのプロジェクト特有のことだけを書く。しかも実際にかなり狭く書いている人がいる:

  • av-site/AGENTS.md:CI の五ステップが全部グリーンであることがマージの前提;OpenAPI の型を変えるときは bun run gen:client;#[ignore] を含むデータベーステストで一時的な postgres をどう起動するか;ビルドマシンからの curl は UA を付けないと Cloudflare が 403 を返す;本番調査の前に SET default_transaction_read_only = on; を付ける。
  • eagler-touhou-next/AGENTS.md:この Mac は読み書きと Git だけ、ビルドマシン上のチェックアウトディレクトリはどこか、bun は 1.4.2 に固定、プロジェクト自身の postgres:18 コンテナはどこか。
  • th15/AGENTS.md はわずか 15 行:二つのツリーの帰属。

中間の「ビルドマシンはどれか」という類は実は両方のファイルに書かれている。重複に見えるが役割が違う——正本は「ビルドマシンがあり、優先度に従って選ぶ」と言い、プロジェクトファイルは「このプロジェクトはもうこれと決まっていて、チェックアウトディレクトリはここだ」と言う。前者はルール、後者は事実だ。

もう一つ厳しいものがある:どのプロジェクトも「どこで走らせるか」を明確に書けなければならない。書けなければ、まだ並行に走らせる準備ができていないということだ。

タスクには身元がなければならない

リモートで走らせるものは ssh host 'bun run build' で済ませてはいけない——SSH が切れれば失われ、再接続してもどこまで走ったかわからない。

正本の「リモート SSH タスクの永続化と切断からの復旧」ではこう規定している(全プロジェクト共通):

各タスクに独立した ID、独立した tmux セッション、独立した ~/.local/state/ai-jobs/<任务ID>/ ディレクトリを一つずつ。ディレクトリ内では run.sh の冒頭で set -uo pipefail、umask 077、exec > output.log、trap が終了コードをファイルに書き込み、echo $$ > pid、date -Is > started、それから PATH と HTTP(S)_PROXY を明示的に export する。

起動、ログ、終了コード、プロセス番号、開始時刻の五つがすべてディスク上にある。SSH が切れたら元の ID で戻って状態を読む、再投入はしない。tmux セッションがなくなったが終了コードもない場合、状態は不明とみなし、プロセスと成果物の確認を続ける。成功とも失敗とも決めつけてはいけない。

禁止されているのは固定の ai-task セッションを共有することだ——複数の線が同じセッションに send-keys するのは、見知らぬ shell にコマンドを放り込むのと同じだ。

最近のタスクの終了コードには 0 もあれば 1 もある(adjust-download-policy-red は 1、adjust-prod-cold は 1、adjust-consent は一群の 1/0 が混在)。並行とは八つの線がすべてグリーンになることではなく、八つの線の結果がそれぞれディスクに落ち、それぞれ調べられ、赤いものが他を巻き込まないことだ。

一度に一箇所だけ書く

同じファイルを二つの線が同時に変更するのは、このワークフローの最大の事故源だ。

分派の単位はプロジェクトではなくファイルだ。今日の四本の線の書き込み集合は:av-site/infra/migrations/*、AstroDX/apps/web/public + src/lib、acgn-flow/src/server/*、drama-site/apps/web/src/*。四つの組は互いに交わらない。
(これは同じ記事の前編・その1です。)

逆に、同じ package.json やルートの tsconfig を変更する場合は、それらを前後依存に並べ、一本を終わらせてから次を始める。ここでの強引な並列は git コンフリクトと互いの編集の上書きを生むだけだ。

プロジェクトをまたいで共通設定を変更しなければならないときは、手元の線を止めて、それが着地するのを待つほうを選ぶ。

検証は変更範囲で段階分けする

並列時に最も緩みやすいのが検証だ。どの線も早く終わらせたいからだ。

ルールは変更内容による段階分けであり、時間の余裕によるものではない。

  • 1ファイルの変更 → そのファイルだけ lint、そのテストファイルだけ実行。ローカルでできる。
  • モジュールをまたぐ変更 → ビルドマシンに上がってそのプロジェクトのリポジトリ全体チェックを回す(AstroDX の scripts/remote/verify.sh は 739 個のテスト + 719 ファイルの lint を回す)。
  • 副作用のあるもの(マイグレーション、デプロイ、データ修復)→ 実機での受け入れを追加する。たとえば av-site の再生まわりの変更は必ず check-live-playback.py を実行し、かつレート制限を一巡含めること。smoke が全緑でもそれでは足りない。

検証を段階を下げる理由になり得るのは「この変更にそのコストは見合わない」だけであって、「今は手が空いていない」ではない。 手が空いていないのはまさに並列がもたらしたものであり、空いていないことを理由に安い検証で済ませるのは、並列で節約した時間をまた賠償しているに等しい。

代償は、明るみに記す

ここまでは方法論、ここからは勘定だ。

コンテキストは互いに薄め合う。 八本の線を抱えれば、それぞれの記憶は薄くなる。だから各線が着地したら、すぐに「なぜこう変えたのか」をコードコメントかそのプロジェクトのドキュメントに書き残す——次に覚えていることを当てにしてはいけない。0055 のマイグレーションファイルは 86 行のうち 52 行がコメントで、AstroDX の docs/verification/service-worker-retirement-2026-10-30.md にはスクリプト自身が問題を起こしたことまで記録してある。どれもこの理由からだ。

レビューはボトルネックであり、しかも並列度に比例して増えない。 八本の線がそれぞれ成果を出す一方、マージの判断はすべて一人にのしかかる。だから AI にはむしろ遅く、成果を少なく出してもらうほうがいい。一度に 20 ファイルも変更させるのはごめんだ——分解できない巨大な変更は、一通り見るだけで自分でやるより長くかかる。

ビルドマシンのリソースは共有だ。 load average が 2〜7 でも高くは見えないが、それは 128 コアの余裕があってのこと。同じ時刻にいくつかの tmux セッションで二つ三つがブラウザ回帰を走らせ、メモリを取り合っている。全量テストを回す線は空いているコアに並べるべきで、taskset -c 0 のような単コアの用例は他の線に引きずられて遅くなる。遅い CPU でのタイミング問題はその条件下でしか露見しない——これは良いことでもあり、罠でもある。

タスクディレクトリは掃除しないと膨らむ。 372 個のディレクトリ、3.1 GB、起動から四日半。実行後に --keep で証跡を残し、一週間後に一度スキャンすべきところ;まだスキャンしていない時点で、もう借金をしている。

もう一つ見落とされがちなこと:並列は「完了」を安くする。 一本が赤くなっても誰も気にせず、別の線が緑ならそのまま進み、赤いのは埋もれる。だから毎日の終業時には「今日いくつ納品したか」ではなく、終了コードが非0のディレクトリを一通り見る。

こういうやり方が向く人

向かない人:変更が強く線形な人、一度に一つしか考えられない人、ルールファイルを書くのが好きでない人。

向く前提はただ一つ:プロジェクトがすでにリモート形態で検証できていること。ビルドマシンがあり、verify スクリプトがあり、「どこで実行するか」がファイルに書かれている——この三つが揃って初めて線を分割できる。どれか一つ欠ければ、並列度を上げても「詰まり」をローカルからリモートへ移すだけだ。

AI の側に至っては、他の線が走っていることなど知らないし、気にもしない。AI はルールファイルの「あのディレクトリに触るな」という制約と、タスクディレクトリの身元と、書き込み集合の非重複によって隔てられている。これらの隔壁はすべて人間が事前に組み立てたもので、AI はその中を走っているにすぎない。

八本の線が同時に走るとき、私が実際にやっていることは実に退屈だ。終了コードを読み、ログを見て、どの線を続けるべきか、どれを止めるべきかを判断し、理由をファイルに書き込む。本当の並列はディスクと CPU の上で起きていて、人間の部分は依然として直列だ——ただし直列なのは判断であって、キーボードを叩くことではない。

(これは同じ記事の前後編のうち後編です。)